Пользовательский элемент управления UserControl / List View в WPF - PullRequest
3 голосов
/ 28 марта 2012

Я занимаюсь разработкой пользовательского элемента управления. В моем пользовательском элементе управления есть список, в котором предполагается отображать несколько полей из связывающего объекта EF. На выходе будет что-то вроде этого

enter image description here

Теперь, что будет источником данных для просмотра списка, поскольку все мои сущности имеют разные свойства, поэтому я не уверен насчет свойств привязки.

Прямо сейчас мой ListView имеет один элемент управления изображением, два текстовых блока и один ссылки ссылки, как я должен определить, с каким контролем, какое свойство должен связать. >

Мне нравится использовать этот элемент управления на экране RecordListing, скажем, «Клиент». привязка экрана к объекту «Клиент» и «привязка экрана сотрудника к сотруднику» сущность ". В моем контроле должна быть одна сущность,

Пожалуйста, объясните мне, как я могу это сделать, это очень родовой и логичный способ.

Спасибо

Ответы [ 2 ]

4 голосов
/ 29 марта 2012

Как предложил Фуванадил, я бы раскрыл свойство DataSource DependencyProperty.Но помимо этого я бы также раскрыл еще 5 свойств зависимости строк.Потребитель элемента управления затем помещает имена свойств, которые он хочет использовать, в конкретные элементы управления.

Позвольте мне быть более точным:

Подумайте о том, как работает Combobox, где вы можетесвязать их DataSource, но вы также можете указать DisplayMemberPath и SelectedValuePath, которые определяют, какие свойства в источнике данных использовать.

Вы можете сделать то же самое с вашим элементом управления:

Предоставьте свойство ImagePathMember.Это будет Имя свойства, которое содержит путь к изображению, которое идет в элементе управления изображением

Предоставить свойство "LinkPathMember".Это свойство будет Name свойства, содержащего путь ссылки

Предоставить свойство LinkDisplayMember.Это свойство будет Name свойства, содержащего текст, на который будет похожа ссылка.

Предоставляет свойство "TopTextBlockMember".Это свойство будет Name свойства, содержащего текст для верхнего текстового блока

Предоставить свойство "BottomTextBlockMember".Это свойство будет Name свойства, содержащего текст для нижнего текстового блока

Затем вы просто используете отражение в элементе управления, чтобы определить значение для каждого элемента списка, а затем привязать его кэлемент управления изображением listboxitem, ссылка и 2 текстовых блока

Надеюсь, что это поможет

u_u


РЕДАКТИРОВАТЬ

Хорошо, вы попросили указать некоторый кодВы в правильном направлении.

Прежде всего: свойства зависимостей

public static DependencyProperty ImagePathMemberProperty = DependencyProperty.Register("ImagePathMember", typeof(string), typeof(MyCustomControl), new PropertyMetadata("",ImagePathMemberPropertyChanged));

public static DependencyProperty DataSourceProperty = DependencyProperty.Register("DataSource", typeof(string), typeof(MyCustomControl), new PropertyMetadata("",DataSourceChanged));
public string ImagePathMember
{
    get
    {
        return (string)GetValue(ImagePathMemberProperty);
    }
    set
    {
        SetValue(ImagePathMemberProperty, value);
    }
}
public string DataSource
{
    get
    {
        return (string)GetValue(DataSourceProperty);
    }
    set
    {
        SetValue(DataSourceProperty, value);
    }
}

Код позади

Как оказалось, нам даже не нужно отражение.Когда вы используете привязки в коде, вы фактически предоставляете строковое имя для свойства, что удобно, потому что созданные нами свойства зависимостей являются действительно строковыми именами свойств.К счастью для нас!

private void DataSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    RecreateBindings();
}

//Repeat this for all the dependencyproperties
private void ImagePathMemberPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    RecreateBindings();

}

private void RecreateBindings()
{
      //Repeat this for all dependency properties
      if(ImagePathMember!=null)
      {
           Binding ImagePathBinding= new Binding(ImagePathMember);
           ImagePathBinding.UpdateSourceTrigger =  UpdateSourceTrigger.PropertyChanged;

           MyImageControl.SetBinding(ImageControl.ImagePathProperty, ImagePathBinding);

      }
}

Я написал этот код от руки, так что, вероятно, все глючит.Также меня немного беспокоит привязка, я не установил Source, потому что думал, что он будет привязан к элементу в коллекции, но я не уверен в этом поведении.

Я собираюсь опубликовать это сейчас, а затем провести тестовый прогон и адаптировать его там, где требуется.

Удачи

u_u


РЕДАКТИРОВАТЬ 2

Хорошо, так что все оказалось намного сложнее, чем я себе представлял.Когда TextBlocks находятся в DataTemplate, вы не можете получить к ним доступ, просто вызвав их имя в коде позади.

Что вам нужно сделать, это дождаться, пока ListBox / ListView сгенерирует контейнеры своих элементов, затемиспользуйте VisualTreeHelper, чтобы пройтись по всем дочерним элементам списка, чтобы найти конкретные элементы управления, которые вы ищете, и затем связать их.

Это заняло у меня так много времени, потому что яне удалось найти элементы управления, потому что я прикрепил обработчик события к событию ItemsSourceChanged в ListView, что означало, что я посмотрел, как изменилось свойство ItemsSource, но за до были созданы контейнеры для этих элементов.

В конце концов я нашел решение:

XAML:

В шаблоне вашего ListView / ListBox, где у вас есть элементы управления, вы должны назвать их так:

      <ImageControl x:Name="MyImageControl" [...]></ImageControl>

Вам также нужно дать имя вашему списку / списку, например, (и связать его ItemsSource с вашим свойством DataSource):

      <ListBox x:Name="listbox"  ItemsSource="{Binding ElementName=me, Path=DataSource, UpdateSourceTrigger=PropertyChanged}" [...]></ListBox> 

Вы увидите, что привязка имеет ElementName=me.Это связано с тем, что я привязан к фактическому элементу управления , находящемуся в (т.е. MyCustomControl).У моего UserControl * x:Name="me" выше xmlns, так как это облегчает мне привязку к свойствам в коде.

RecreateBindings:

В основном вам необходимо обновить метод RecreateBindings. Я допустил большую ошибку в своем первом посте, так как он должен быть статическим методом для запуска в PropertyChangedCallBack объекта DependencyProperty (я действительно не должен был делать код вручную).

Вот чем я закончил:

 //Repeat this for all types of controls in your listbox.
 private static void RecreateImageControlBindings(ListBox listbox, string controlName, string newPropertyName)
    {

        if (!string.IsNullOrEmpty(newPropertyName))
        {
            if (listbox.Items.Count > 0)
            {
                for (int i = 0; i < listbox.Items.Count; i++)
                {

                    ListBoxItem item = listbox.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
                    if (item != null)
                    {
                        Binding imageControlBinding = new Binding(newPropertyName);
                        imageControlBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;

                        ImageControl t = FindDescendant<ImageControl>(item, controlName);
                        if (t != null)
                            BindingOperations.SetBinding(t, ImageControl.ImagePath, imageControlBinding);


                    }
                }

            }



        }

    }

Как видите, теперь вам нужен метод RecreateBindings для всех различных типов элементов управления в вашем списке / списке. Существует более общий способ сделать это, но вы можете разобраться с этим самостоятельно. Я не могу делать всю работу: P

Что делает код, так это просматривает элементы в списке и получает их контейнер. После генерации ImageControl будет дочерним элементом этого контейнера. Итак, мы получаем ImageControl с помощью метода FindDescendants, который я адаптировал из этого поста .

Вот этот метод:

Метод FindDescendant

public static T FindDescendant<T>(DependencyObject obj,string objectName) where T : FrameworkElement
    {

            // Check if this object is the specified type
            if (obj is T && ((T)obj).Name == objectName)
                return obj as T;

            // Check for children
            int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
            if (childrenCount < 1)
                return null;

            // First check all the children
            for (int i = 0; i < childrenCount; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child is T && ((T)child).Name == objectName)
                    return child as T;
            }

            // Then check the childrens children
            for (int i = 0; i < childrenCount; i++)
            {
                DependencyObject child = FindDescendant<T>(VisualTreeHelper.GetChild(obj, i), objectName);
                if (child != null && child is T && ((T)child).Name == objectName)
                    return child as T;
            }

            return null;


    }

Единственное приспособление, которое я сделал, это добавило проверку имени элемента управления. У нас есть 2 TextBlocks в нашем ListBoxItem, поэтому оригинальный метод будет возвращать только первый. Нам нужно проверить имена, чтобы мы могли связывать оба.

Методы PropertyCallBack:

Таким образом, поскольку метод RecreateBindings был разделен, нам нужно изменить PropertyChangedCallBacks для вызова методов RecreateBindings, специфичных для каждого свойства. Свойство источника данных будет содержать все методы RecreateBindings.

private static void DataSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {

       //Put the RecreateBindings for all the properties here:
        RecreateImageControlBindings(((MyCustomControl)sender).listbox, "MyImageControl", ((MyCustomControl)sender).ImagePathMember);

    }

    //Repeat this for all the dependencyproperties
    private void ImagePathMemberPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
         RecreateImageControlBindings(((MyCustomControl)sender).listbox, "MyImageControl", ((MyCustomControl)sender).ImagePathMember);

    }

Обратите внимание, что MyCustomControl - это тип создаваемого вами элемента управления.

Конструктор и прикрепленный обработчик событий:

Наконец, нам нужно добавить строку в конструктор, который добавляет обработчик события в ItemContainerGenerator в ListBox, чтобы мы могли проверять, когда сгенерирован контейнер элемента, и можем прикреплять наши привязки .:

    public MyCustomControl()
    {

        InitializeComponent();     

        listview.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);

    }

    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
    {
        if (listview.ItemContainerGenerator.Status
        == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
        {
            //Do this for all the different bindings we want
            RecreateImageControlBindings(listview, "MyImageControl", ImagePathMember);

        }
    }

Так и должно быть. Если вам нужна помощь / есть вопросы, дайте мне знать

u_u

2 голосов
/ 28 марта 2012

Если это действительно пользовательский элемент управления, предназначенный для повторного использования в нескольких местах с различными источниками данных, то вы можете оставить потребителю элемента управления установить DataSource.

Вы захотите добавить пользовательское свойство зависимости в свой пользовательский элемент управления с именем DataSource. Это даст потребителю элемента управления что-то, что нужно установить при использовании элемента управления.

Тогда, когда кто-то использует ваш контроль, он будет делать что-то вроде этого:

<SomeNamespace:YourCustomControl DataSource="{Binding ControlConsumerEFEntity}" />

Опять же, если это настоящий пользовательский элемент управления, он не будет напрямую устанавливать источник данных своего внутреннего элемента. Это оставит на усмотрение управляющего потребителя установить это.

Подумайте, как работает встроенный WPF ListBox. Если вы просто сделаете это:

<ListBox />

Нет источника данных, но если вы сделаете

<ListBox DataSource="{Binding MyCollection}" />

Тогда ListBox будет предоставлен источник данных для использования. Который был указан человеком, потребляющим элемент управления ListBox. Имеет смысл?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...