Custom UserControl / List Control in WPF

I am developing a custom control. My custom control has a list box that is supposed to show multiple fields from an EF Entity binding. The result will be something like this:

enter image description here

Now what would be the datasource for the listview as all my objects have different properties so I'm not sure about the binding properties.

Currently my ListView has one image control, two text boxes and one link labels, how should I determine which control which property should bind to. >

I like to use this control in the RecordListing screen, for example "Client" binding screen to client entity "and" binding employee screen "to employee entity.There should be one entity behind my control.

Please advise me how I can do this, it is very logical and logical.

thank

+3


source to share


2 answers


As Fovanadil suggested, I would provide a DependencyProperty for the DataSource. But beyond that, I also exposed 5 more string dependency properties. The consumer of the control then puts the names of the properties they want to use in specific controls.

Let me clarify:

Think about how a Combobox works, where you can bind your DataSource, but you can also specify DisplayMemberPath

and SelectedValuePath

which determine which properties in the data source to use.

You can do the same with your control:

Open the ImagePathMember property. This will be the name of the property that contains the path for the image that goes in the image control

Open the LinkPathMember property. This property will be the name of the property containing the link path

Open the LinkDisplayMember property. This property will be the name of the property that contains the text that the link will look like.

Open the TopTextBlockMember property. This property will be the name of the property that contains the text for the top text block

Open the "BottomTextBlockMember" property. This property will be the name of the property that contains the text for the bottom text block

Then you just use reflection in the control to define a value for each list item and then bind it to the control with an image, link and 2 text boxes.

Hope it helps

u_u


EDIT

Okay, you asked for some code to point you in the right direction.

First Up: Dependency Properties

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);
    }
}

      

Code for

As it turns out, we don't even need to think. When you use bindings in code, you are actually specifying a string name for that property, which is convenient because the dependency properties we create are string property names. Lucky!

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);

      }
}

      

I wrote this code by hand, so its probably all buggy. Also, I'm a little worried about binding, I didn't install Source

because I thought it would bind to an item in the collection, but I'm not sure about that.

So, I'm going to post this now and then give it a dry run and adapt it as needed.

Luck

u_u


EDIT 2

Ok, so it turned out to be much more complicated than I imagined. When the TextBlocks are in the DataTemplate, you cannot access them simply by calling their name in code.

You need to wait for the ListBox / ListView to create containers of its items, and then use the visualtreehelper to loop through all the child items of the list to find the specific controls you are looking for, and then bind to them.



It took me a long time because I couldn't find the controls because I attached an event handler to the ListView's ItemsSourceChanged, which meant that I looked like the ItemsSource property changed, but before the containers for those items.

Eventually I found a solution:

XAML:

In your ListView / ListBox template where you have your controls, you need to call them like this:

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

      

You also need to give a name to your listview / listview, for example (and bind its ItemsSource to the DataSource property):

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

      

You will see what the binding has ElementName=me

. This is because I am binding to the actual control I am (i.e. MyCustomControl). Mine UserControl

has x:Name="me"

over xmlns

, as it makes it easier for me to bind to properties in code.

RecreateBindings:

Basically you need to update the RecreateBindings method. I made a big mistake with my first post, as it should be a static method to run in the DependencyProperty PropertyChangedCallBack (I really shouldn't have been doing the code by hand).

This is what I ended up with:

 //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);


                    }
                }

            }



        }

    }

      

As you can see, now you need a RecreateBindings method for all control types in your list / list. There is a fairly common way to do this, but you can figure it out for yourself. I can't do all the work: P

What the code does is it loops through the items in the list and gets their container. ImageControl, once generated, will be a child of this container. Therefore, we go with ImageControl using the FindDescendants method, which I adapted from this post .

This method is:

FindDescendant Method

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;


    }

      

The only thing I did was add a check for the name of the control. We have 2 TextBlocks in our ListBoxItem, so the original method will only return the first one. We need to validate the names so that we can bind on both.

PropertyCallBack methods:

So, since the RecreateBindings method has been split, we need to change the PropertyChangedCallBacks to invoke the RecreateBindings methods specific to each property. The datasource property will contain all the RecreateBindings methods.

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);

    }

      

Note that MyCustomControl is the type of this control that you are creating.

Constructor and attached event handler:

Finally, we need to add a line to the constructor that adds an event handler to the ListBox ItemContainerGenerator, so we can check when the item container is created and we can bind our bindings .:

    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);

        }
    }

      

It should be like this. If you need help / have any questions, let me know

u_u

+4


source


If it is truly a custom control that is intended to be reused in multiple locations with different data sources, then you should leave it for the consumer user to set the DataSource.

You need to add a custom dependency property to your custom control called DataSource. This will give the controlling consumer something set when using the control.

Then when someone uses your control, they'll do something like this:

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

      

Again, if it is a true custom control, it will not directly set its inner elements to the DataSource. He would leave it for the monitoring consumer to fix it.



Think about how WPF's built-in ListBox works. If you just do this:

<ListBox />

      

No dataset, but if you do

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

      

Then a data source will be provided for the ListBox. This was indicated by the person using the ListBox control. It makes sense?

+1


source







All Articles