locked
Strange WPF Data Binding Issues RRS feed

  • Question

  • Hi everybody! I've been experiencing what I perceive to be some strange behaviour regarding Data Binding that would indicate some sort of misunderstanding of what the heck is going on. Here's some sample code to illustrate the issue:

    Main Window:
    <Window x:Class="Test.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <ListBox ItemsSource="{Binding Thing.Bar}" Name="ListBoxName"></ListBox>
        </Grid>
    </Window>

    Code behind for main window:
        public partial class Window1 : Window
        {
            Foo Thing;

            public Window1()
            {
                Thing = new Foo();
                Thing.Bar.Add("Test");

                InitializeComponent();

                //ListBoxName.ItemsSource = Thing.Bar; -- this is the important line!!!
            }
        }

    ... And finally, here's my "Foo" class:
        class Foo
        {
            public List<string> Bar;

            public Foo()
            {
                Bar = new List<string>();
            }
        }

    The problem is that if I uncomment the line in the code behind of the main window, the listbox shows "Test", as I would expect. However, if I leave it commented out, nothing shows up in the main window. I'm confused, because the object exists in my code-behind before the declarative XAML binding takes place -- trying to bind the items source of the listbox to Thing.Bar. I've tried all the combinations I can think of with the binding (... Source=Thing, Path=Bar; Source=Thing.Bar; Path=Thing.Bar... none of them work). 

    What is it that I'm fundamentally misunderstanding about binding? This ties into issues I've been having with binding to an object which is null, but which I then "fill in". Even when I implement INotifyPropertyChanged, I find PropertyChanged is null as if nobody is "listening". I've even got a book about WPF, and I've been going through the Data Binding chapter over and over again, but I can't seem to figure out why my bindings are so hit and miss. I don't even get an error in the output in this example to tell me what the ____ is going wrong!

    Can somebody please clear up what it is I'm missing here?

    Thanks so much in advance for your help!


    Friday, March 5, 2010 6:51 AM

Answers

  • Hi,

    I won't repeat the basics of data binding here, because they are covered quite well in the WPF documentation. However, you asked for background information for the suggestions the others made in their posts.

    The thing with a binding is that it has to know the binding source, i.e. the object it points to, and the binding path, i.e. which property of that object should be the value of the binding.

    Now there are several ways to set the source of a binding in XAML. The way you tried here is not mentioning the binding source at all. In this case the binding assumes that the source should be the DataContext, i.e. whatever object is referenced by the DataContext property of the FrameworkElement where the binding is defined in XAML. In your first example, you don't set the DataContext, so this property doesn't contain a value, and the property path you specify is applied to a null reference, which obviously can't produce a result.

    In your code you assumed that the binding would use the Window object as default source, and would know all its properties, since this is the code behind for the XAML. However, even though this is not the standard behaviour as I explained above, it is quite simple to make the Binding refer to the Window (or in fact any other Frameworkelement on the current page) as its source: if you set the x:Name attribute on the window, like x:Name="MyWindow", you can specify your binding as {Binding ElementName=MyWindow, Path=Thing.Bar}, which would translate to: I want to bind to the Bar property of the object which is referenced by the Thing property of the XAML element which has the x:Name MyWindow. This is what Console.Writeline suggests.

    You mentioned running into trouble with binding to objects that are null in the beginning and then set to a value, even though these objects implement change notification. Again this goes back to the matter of source and path, and to how change notification is supposed to work. Actually, a binding assumes that the source is constant, and the content of whatever the path points to might change. So, when you create a binding, the binding will check whether the object you use as source supports change notification, and if yes, it will change its value every time this object reports a change. However, if you set the source to the value of a variable and then change that value, the binding source will behave as any other copy of a reference: it will keep pointing to the old source, because it didn't notice that there was a change in the other variable. So, it will change nothing if the object the variable points to implements INotifyPropertyChanged. The binding won't even know that the variable it got its source from in the first place now contains an object that implements this interface.

    The confusing thing here is that there is one important exception to that rule: if you don't specify the source, as I said above the binding will use the DataContext of the XAML element it is defined on, and the XAML elements have a built-in mechanism which tells all children of an XAML element and their bindings when the parent's DataContext is changed. This makes it look as if the bindings magically knew about changes to their sources, but this is more or less a coincidence, and one should always choose sources and paths in a way that the source is not supposed to change during the lifetime of the binding, and everything that could change is pointed to by the path.

    Now there is one problem with setting the DataContext in XAML: in order to work properly 100%, the XAML needs to be read completely and the system I mentioned above, which is responsible for inheriting the DataContext, needs to be initialized. This is what happens in the InitializeComponent method. Therefore William Han set the ListBox's DataContext after this line in the constructor in the first answer. If you try to set the DataContext in XAML or anywhere before this method has completed, some things that are supposed to happen when the DataContext changes are not happening. As far as I know, this only affects ValidationRules and the Validation.ErrorTemplate, and it will be fixed in WPF 4.0, so this might not be very important to you right now. Therefore, it would be safe to do something like William Han suggested in the second answer, i.e. setting DataContext="{StaticResource list}" or something like that.

    I hope this bit of background information helps to understand what is going on...
    http://wpfglue.wordpress.com
    Saturday, March 6, 2010 10:24 AM

All replies

  • After you commented out that line of code, the binding doesn’t know where to bind to.  In code, we usually assign data source to DataContext. In binding, we bind to data type property.

    Example(fix your code, and shows a simple example that binds to property):
    Markup:
    <Window x:Class="ListBoxBindingTest.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
        <StackPanel>
            <ListBox ItemsSource="{Binding}" Name="ListBoxName"></ListBox>
            <ListBox ItemsSource="{Binding Books}" Name="newlistbox"></ListBox>
        </StackPanel>
    </Window>

    Code:
    using System.Collections.Generic;
    using System.Windows;

    namespace ListBoxBindingTest
    {
        public partial class Window1 : Window
        {
            Foo Thing;
            public Window1()
            {
                Thing = new Foo();
                Thing.Bar.Add("Test");
                InitializeComponent();
                ListBoxName.DataContext = Thing.Bar;

                newlistbox.DataContext = new Person();
            }
        }
        public class Foo
        {
            public List<string> Bar;
            public Foo()
            {
                Bar = new List<string>();
            }
        }
        public class Person
        {
            private List<string> books = new List<string>();
            public Person()
            {
                books.Add("book1");
                books.Add("book2");
                books.Add("book3");
            }
            public List<string> Books { get { return books; } }
        }
    }

    Hope it helps.


    William
    Friday, March 5, 2010 7:37 AM
  • Hi William,

    Thanks for your response! Indeed, I'm able to get the example working by setting a binding in code. However, what I'm trying to do is avoid having to set anything in code: I'd like all the bindings to be declarative and reside in the XAML. Is there a way to get this example working without setting the DataContext inside the code behind? For instance, if I understand correctly, the XAML code is "executed" within InitializeComponent(). If I've already "made" an object and assigned it to Thing.Bar before that function runs, why am I not able to declare a binding to it inside the XAML? Bar is also a public property of Thing, so why do I even need to create an object at all?

    I appreciate your help in getting the example to work, but my question was trying to go a bit deeper: what are the requirements for a binding to an object to work in XAML? Most examples I see just declare an object as a static resource and set up the binding that way, but I'd like to bind to a specific object that I know is a part of the window class (even if it's null!), and have it react when that object is "filled in".

    Thanks for your time and assistance, I really appreciate it.
    Friday, March 5, 2010 1:42 PM
  • Here is an example how to initialize the collection type in markup and pointing ListBox.DataContext to it.

    Markup:
    <Window x:Class="ListBoxBindingTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:ListBoxBindingTest"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <local:Person x:Key="person">
                <local:Person.Books>
                    <sys:String>book1</sys:String>
                    <sys:String>book2</sys:String>
                    <sys:String>book3</sys:String>
                </local:Person.Books>
            </local:Person>
        </Window.Resources>
        <StackPanel>
            <ListBox DataContext="{StaticResource person}" ItemsSource="{Binding Books}"/>
        </StackPanel>
    </Window>

    Code:
    using System.Collections.Generic;
    using System.Windows;

    namespace ListBoxBindingTest
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        }
        public class Person
        {
            public Person()
            {
                Books = new List<string>();
            }
            public List<string> Books { get; set; }
        }
    }

    Hope it helps.


    William
    Friday, March 5, 2010 11:34 PM
  • You would have to use dependency property to bind that way :

    public static readonly DependencyProperty ThingProperty = DependencyProperty.Register("Thing", typeof(Foo), typeof(Window1));
    
    public Foo Thing
    {
      get{return (Foo)GetValue(ThingProperty);}
      set{SetValue(ThingProperty, value);}
    }

    <Window x:Class="Test.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300"
        Name="aardvarkk">
        <Grid>
            <ListBox ItemsSource="{Binding ElementName=aardvarkk, Path=Thing.Bar}" Name="ListBoxName"></ListBox>
        </Grid>
    </Window>
    

    I have used this kind of code to bind directly .. Have not tested with composite contained types. May have to tweak something a little bit.

    INotifyPropertyChanged or INotifyCollectionChanged implementation would also work - but it gets complicated when you have more than one type of item bound to different controls. You can explore these waters if your interested and have aversion to using dependency properties ... which are super cool when you know how to use them ..

    AT

    Saturday, March 6, 2010 4:52 AM
  • Hi,

    I won't repeat the basics of data binding here, because they are covered quite well in the WPF documentation. However, you asked for background information for the suggestions the others made in their posts.

    The thing with a binding is that it has to know the binding source, i.e. the object it points to, and the binding path, i.e. which property of that object should be the value of the binding.

    Now there are several ways to set the source of a binding in XAML. The way you tried here is not mentioning the binding source at all. In this case the binding assumes that the source should be the DataContext, i.e. whatever object is referenced by the DataContext property of the FrameworkElement where the binding is defined in XAML. In your first example, you don't set the DataContext, so this property doesn't contain a value, and the property path you specify is applied to a null reference, which obviously can't produce a result.

    In your code you assumed that the binding would use the Window object as default source, and would know all its properties, since this is the code behind for the XAML. However, even though this is not the standard behaviour as I explained above, it is quite simple to make the Binding refer to the Window (or in fact any other Frameworkelement on the current page) as its source: if you set the x:Name attribute on the window, like x:Name="MyWindow", you can specify your binding as {Binding ElementName=MyWindow, Path=Thing.Bar}, which would translate to: I want to bind to the Bar property of the object which is referenced by the Thing property of the XAML element which has the x:Name MyWindow. This is what Console.Writeline suggests.

    You mentioned running into trouble with binding to objects that are null in the beginning and then set to a value, even though these objects implement change notification. Again this goes back to the matter of source and path, and to how change notification is supposed to work. Actually, a binding assumes that the source is constant, and the content of whatever the path points to might change. So, when you create a binding, the binding will check whether the object you use as source supports change notification, and if yes, it will change its value every time this object reports a change. However, if you set the source to the value of a variable and then change that value, the binding source will behave as any other copy of a reference: it will keep pointing to the old source, because it didn't notice that there was a change in the other variable. So, it will change nothing if the object the variable points to implements INotifyPropertyChanged. The binding won't even know that the variable it got its source from in the first place now contains an object that implements this interface.

    The confusing thing here is that there is one important exception to that rule: if you don't specify the source, as I said above the binding will use the DataContext of the XAML element it is defined on, and the XAML elements have a built-in mechanism which tells all children of an XAML element and their bindings when the parent's DataContext is changed. This makes it look as if the bindings magically knew about changes to their sources, but this is more or less a coincidence, and one should always choose sources and paths in a way that the source is not supposed to change during the lifetime of the binding, and everything that could change is pointed to by the path.

    Now there is one problem with setting the DataContext in XAML: in order to work properly 100%, the XAML needs to be read completely and the system I mentioned above, which is responsible for inheriting the DataContext, needs to be initialized. This is what happens in the InitializeComponent method. Therefore William Han set the ListBox's DataContext after this line in the constructor in the first answer. If you try to set the DataContext in XAML or anywhere before this method has completed, some things that are supposed to happen when the DataContext changes are not happening. As far as I know, this only affects ValidationRules and the Validation.ErrorTemplate, and it will be fixed in WPF 4.0, so this might not be very important to you right now. Therefore, it would be safe to do something like William Han suggested in the second answer, i.e. setting DataContext="{StaticResource list}" or something like that.

    I hope this bit of background information helps to understand what is going on...
    http://wpfglue.wordpress.com
    Saturday, March 6, 2010 10:24 AM