12

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?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
baris1892
  • 981
  • 1
  • 16
  • 29
  • 1
    Unlike forum sites, we don't use "Thanks", or "Any help appreciated", or signatures on [so]. See "[Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?](http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be-removed-from-posts). – John Saunders Jan 01 '15 at 14:55
  • raise the change for MyModel property after you are initializing it. everything else is correct. – Kylo Ren Jan 20 '16 at 13:24

2 Answers2

5

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.

idursun
  • 6,261
  • 1
  • 37
  • 51
  • Thanks! This works fine. Could you please tell me how it would look like setting the data context in the XAML (and not in C#)? Is this possible? – baris1892 Jan 01 '15 at 14:22
  • Take a look at this: http://stackoverflow.com/questions/1521699/how-to-instantiate-datacontext-object-in-xaml – idursun Jan 01 '15 at 14:32
  • I sense another question is coming:) You can access that model from code like this `var myModel = this.Resources["myModal"] as MyModel;` – idursun Jan 01 '15 at 14:36
  • Hmm, I've tried the first and second approach, but none of them is working (I've deleted the data binding in C# code of course^^). ` ` and then ``...same like in the other topic. – baris1892 Jan 01 '15 at 14:48
  • Try again by moving your datacontext attribute onto stackpanel element rather than the window. – idursun Jan 01 '15 at 14:49
  • @idursun it is not the problem of setting the datacontext from XAML. the change is not being raised is actual problem. – Kylo Ren Jan 20 '16 at 13:29
  • @radon66 it is not the problem of setting the datacontext from XAML. the change is not being raised is actual problem. – Kylo Ren Jan 20 '16 at 13:30
  • @KyloRen Read the question and the answer again. – idursun Jan 22 '16 at 07:29
  • @idursun can you directly made your point. I'm not sure what are you pointing to. – Kylo Ren Jan 22 '16 at 08:03
  • 1
    First question was about binding and yes binding was correct. Nested binding do work. second question was DataContext was set correct , Yes it has set correct in XAML. @idursun Just declare MyModel property as I have mentioned and then see the result. that was the problem in the code. – Kylo Ren Jan 22 '16 at 08:12
  • @KyloRen Thanks for shedding some light on this issue. – idursun Jan 28 '16 at 07:40
  • @idursun No Problem. – Kylo Ren Jan 28 '16 at 08:56
0

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.

Kylo Ren
  • 8,551
  • 6
  • 41
  • 66