.netwpfdata-bindinguser-controls

WPF DataBinding of a whole object in a UserControl


i have a question regarding databinding. I created a WPF application with a ViewModel. inside this viewModel I have an object from a class which i wrote which contains several attributes. i want to create a UserControl with some labels on it, which display the values of the attributes of the object. Then I want to add this UserControl to the main Window and i want to pass the whole object to this UserControl where i want to bind the label contents to the different attributes of the object.

to make this more clear i wrote a small example here.

My class is called Person:

    public class Person
    {
        public int[] data;
        public string name;
        public int age;

        public Person(string name, int age)
        {
            this.name = name;
            this.age = age;
        }
    }

I then created the Usercontrol UC_Person with the following two labels:

    <Grid>
        <StackPanel>
            <Label Name="label1" Background="Yellow"></Label>
            <Label Name="label2" Background="Red"></Label>
        </StackPanel>
    </Grid>

In the UC_Person.cs code i added the following where i want to "store" one object of Person. To show you the problem, i also added an additional string LabelContent, since there my databinding is working.

        public Person person
        {
            get { return (Person)GetValue(PersonProperty); }
            set { SetValue(PersonProperty, value); }
        }

        public static readonly DependencyProperty PersonProperty =
            DependencyProperty.Register("person", typeof(Person), typeof(UC_Person),// null);
                new PropertyMetadata(new PropertyChangedCallback(UC_Person.PersonPropertyChanged)));




        public string LabelContent
        {
            get { return (string)GetValue(LabelContentProperty); }
            set { SetValue(LabelContentProperty, value); }
        }

        public static readonly DependencyProperty LabelContentProperty =
            DependencyProperty.Register("LabelContent", typeof(string), typeof(UC_Person),// null);
                new PropertyMetadata(new PropertyChangedCallback(UC_Person.LabelContentChanged)));



        private static void LabelContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UC_Person person = (UC_Person)d;
            person.label1.Content = person.LabelContent;

            Console.WriteLine("label changed");


        }

        private static void PersonPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Console.WriteLine("Person changed");
        }

Inside my MainWindowViewModel, I also created two objects person and labelContent with get and set and the invoking of the PropertyChanged.

        private Person _person = new Person("Peter", 20);
        public Person person
        {
            get { return _person; }
            set
            {
                _person = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("person"));
            }
        }


        private string _labelContent = "";
        public string labelContent
        {
            get { return _labelContent; }
            set
            {
                _labelContent = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("labelContent"));
            }
        }

now i added the usercontrol to my MainWindow.xaml, where I try to bind the person from the ViewModel to the UserControl UC_Person.

 <local:UC_Person person="{Binding person}" LabelContent="{Binding labelContent}" HorizontalAlignment="Left" Height="62" Margin="73,156,0,0" VerticalAlignment="Top" Width="143"/>

The goal is that I can for example display the age of the person on the label of my usercontrol and it should be binded to the live data of the age of the person. I think the problem that I have is that changing the persons age (for example by pressing a button on the mainwindow which makes person.age++; the setter of person is not used as the object still stays the same but only its attribute changed. so my propertychanged does not fire i guess and the code-line with "Console.WriteLine("Person changed");" is never running.

So i added a button to the MainWindow with a Method that changes the labelContent and the persons age of the model.

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this._model.person.age++;
            this._model.labelContent = cnt + "something";
            cnt++;
        }

However, the label is perfectly changing its value on the UI, whereas the persons age is (and stays) empty.

I know that it would be possible by binding just the attributes to the userinterface, but since this is just an example and in reality I have much more attributes in my object, this would be a mess and i would prefer to somehow "pass" or bind the whole object to the usercontrol. Does anybody know how i can detect changes in the attributes of the object or how i can go around my problem?

thank you very much for your help!!


Solution

  • You don't actually need the person property in the UserControl.

    Turn the fields of the Person class into properties, so that you can use them with data binding. Use Pascal casing for their names, because that is a widely accepted coding convention. The properties shown below are also declared read-only.

    public class Person
    {
        public string Name { get; }
        public int Age { get; }
    
        public Person(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }
    

    In the UserControl's XAML, bind a UI element to a Person property like this:

    <TextBlock Text="{Binding Name}"/>
    

    To make that binding work, assign a Person object to the DataContext property of the UserControl. Note that the line below requires a public Person property on the object that is assigned to the DataContext of the parent element of the UserControl (e.g. the Window in which it is used).

    <local:UC_Person DataContext="{Binding Person}" />
    

    With your original approach of having a Person property in the UserControl, its XAML would have to use the control itself as source object of the binding, e.g. like this:

    <TextBlock Text="{Binding Person.Name,
                      RelativeSource={RelativeSource AncestorType=UserControl}}"/>
    

    Equivalent for LabelContent (where the UI element should also be a TextBlock, not a Label):

    <Label Content="{Binding LabelContent,
                     RelativeSource={RelativeSource AncestorType=UserControl}}"/>