9
votes

I'm pretty new to WPF and XAML and now I'm stuck with data binding for days! I just wanted to bind some nested properties to a TextBox and ListView (via XAML), but I'm doing it wrong. Here's my Sample Code:

MainWindow.xaml.cs

namespace CounterTestNestedDataBinding
{
    public partial class MainWindow : Window
    {
        public MyModel MyModel { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            MyModel = new MyModel { MyCounter = new Counter() };
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyModel.MyCounter.incrementCounter();
        }
    }
}

MyModel.cs

namespace CounterTestNestedDataBinding
{
    public class MyModel : INotifyPropertyChanged
    {
        public Counter _myCounter;
        public Counter MyCounter
        {
            get { return _myCounter; }
            set
            {
                _myCounter = value;
                NotifyPropertyChanged("MyCounter");
            }
        }

        // some other members and properties ...

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }

    }
}

Counter.cs

namespace CounterTestNestedDataBinding
{
    public class Counter : INotifyPropertyChanged
    {
        #region Members
        private int _currentNumber;
        private ObservableCollection<int> _historyList;
        #endregion

        #region Constructor
        public Counter()
        {
            _currentNumber = 0;
            _historyList = new ObservableCollection<int>();
        }
        #endregion

        #region Properties
        public int CurrentNumber
        {
            get { return _currentNumber; }
            set
            {
                _currentNumber = value;
                NotifyPropertyChanged("CurrentNumber");
            }
        }

        public ObservableCollection<int> HistoryList
        {
            get { return _historyList; }
            set
            {
                _historyList = value;
                NotifyPropertyChanged("HistoryList");
            }
        }
        #endregion

        public void incrementCounter()
        {
            HistoryList.Add(CurrentNumber);
            CurrentNumber++;
        }

        public override string ToString()
        {
            return string.Format("CurrentNumber: {0}, HistoryList: {1}", _currentNumber, String.Join(",", _historyList));
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}

MainWindow.xaml

<Window x:Class="CounterTestNestedDataBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:counterTestNestedDataBinding="clr-namespace:CounterTestNestedDataBinding"
        Title="MainWindow" Height="350" Width="200" ResizeMode="NoResize" WindowStartupLocation="CenterScreen"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        >
    <StackPanel Orientation="Vertical">
        <TextBox x:Name="TextBoxCounterCurrent" Text="{Binding MyModel.MyCounter.CurrentNumber}"/>
        <Button Content="Button" Click="Button_Click"/>
        <ListView x:Name="ListViewCounterHistory" Height="75" ItemsSource="{Binding MyModel.MyCounter.HistoryList}"></ListView>
    </StackPanel>
</Window>

My questions:

  1. How can I bind nested properties? Is it possible? Why does something like

    Text="{Binding MyModel.MyCounter.CurrentNumber}"
    

    not work?

  2. Is the "DataContext" set correct in the XAML?

2
Unlike forum sites, we don't use "Thanks", or "Any help appreciated", or signatures on Stack Overflow. See "Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?. - John Saunders
raise the change for MyModel property after you are initializing it. everything else is correct. - Kylo Ren

2 Answers

5
votes

Set your data context in the constructor like this:

public MainWindow()
{
    InitializeComponent();
    MyModel = new MyModel { MyCounter = new Counter() };
    this.DataContext = MyModel;
}

and then of course the path to your data changes because the data that you are binding is under MyModel. Your bindings should be changed as follows:

<StackPanel Orientation="Vertical">
    <TextBox x:Name="TextBoxCounterCurrent" Text="{Binding MyCounter.CurrentNumber}"/>
    <Button Content="Button" Click="Button_Click"/>
    <ListView x:Name="ListViewCounterHistory" Height="75" ItemsSource="{Binding MyCounter.HistoryList}"></ListView>
</StackPanel>

EDIT:

This is how you do it by using XAML.

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    xmlns:system="clr-namespace:System;assembly=mscorlib"
    Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
    <local:MyModel x:Key="myModal" />
</Window.Resources>

<StackPanel Orientation="Vertical" DataContext="{StaticResource myModal}">
    <TextBox x:Name="TextBoxCounterCurrent" Text="{Binding MyCounter.CurrentNumber}"/>
    <Button Content="Button" Click="Button_Click"/>
    <ListView x:Name="ListViewCounterHistory" Height="75" ItemsSource="{Binding MyCounter.HistoryList}"></ListView>
</StackPanel>

and code changes like this:

 public partial class MainWindow : Window
{
    public MyModel MyModel { get; set; }
    public MainWindow()
    {
        InitializeComponent();
        //MyModel = new MyModel { MyCounter = new Counter() };
        //this.DataContext = MyModel;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var myModel = this.Resources["myModal"] as MyModel;
        if (myModel != null)
        {
            myModel.MyCounter.incrementCounter();
        }
    }
}

By the way you should initialize _myCounter in MyModel constructor.

0
votes

I don't think that your conclusions are entirely right and you have actually ignored the real problem here.

just create your property as

private MyModel myModel;

    public MyModel MyModel
    {
        get { return myModel;}
        set { myModel = value;
            NotifyPropertyChanged("MyModel");
        }
    }

the problem was the MyModel property was initialized after the control has been initialized but how would the binding engine know that it has to refresh the view, for it this property is still null as you haven't tell the engine to pull the bound value.

or just initialize the value of property before the view that will work too. i. e.

public MainWindow()
    {
        MyModel = new MyModel { MyCounter = new Counter() };    
        InitializeComponent();                   
    }

So WPF does support the nested/dotted binding.