locked
databinding question RRS feed

  • Question

  • Hi all,

     

    I have a databinding question. I watched WPF Soup To Nuts: Databinding Webcast by William Steele recently and I was confused by one particular example Steele showed. Consider the following XAML snippet and code behind (I copied this from the example):

    <StackPanel x:Name="MyStackPanel">
      <TextBox Text="{Binding Name}" />
      <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
    </StackPanel>

     

    List<Person> people = new List<Person>();

    people.Add(new Person("Bill Steele"));
    people.Add(new Person("Amanda Steele"));
    people.Add(new Person("Ron Cundiff"));

    this.MyStackPanel.DataContext = people;

     

    The stackpanels DataContext is set to a collection of Person objects. The listbox is bound to this collection, showing the Persons (steele uses a datatemplate for that, but i left it out here).

    The textbox is where I've scratched my head several times. It binds to Name, but the List<Person> object has no Name property, the Person object does. How does this work?

    Also the IsSynchronizedWithCurrentItem must be set to True.

     

    Please help me understand this!

     

    http://blogs.msdn.com/wsteele/archive/2007/10/01/wpf-soup-to-nuts-databinding-webcast.aspx

    Thursday, October 4, 2007 8:07 AM

Answers

  • Each IEnumerator has a 'Current' value.  So does List<Person>.  With IsSynchronizedWithCurrentItem = true, you tell the Selector object (in this case, the ListBox), to keep it's SelectedItem in sync with the 'Current' value of the List object.  This means, each time you change the current selection in the listbox, the 'Current' value of the actual list is also changed.

    With the TextBox, the binding first goes to search for the Property on the DataContext, if it can't find it there, it tries to locate it in the 'Curren't value of the DataContext.  Bindings know about this principle and take it into account when searching for a property.

    Thursday, October 4, 2007 8:34 AM
  • Excellent question!

     

    When binding to a collection, WPF will first look for the specified property (in this case, the 'Name' property) on the collection itself.  If such a property does not exist, it will then look for the property on the "current item" within the collection.  If found, the binding is established just as if you had said Path=CurrentItem.Name.

     

    So there are a couple of things to note here.  First, WPF imposes this concept of currency on a databound collection.  Second, when binding a Selector to a collection, you can easily synchronize the collection view's "CurrentItem" with the Selector's SelectedItem by setting the IsSynchronizedWithCurrentItem property to true.

     

    Noteworthy Tidbit: An ItemsControl is actually never bound directly to a collection... Rather, it is always bound to a CollectionView.  When an ItemsSource is specified, the given collection serves as the source for the CollectionView.  If the collection implements INotifyCollectionChanged, then CollectionView will monitor the collection for changes and keep the view in sync with it.  If the ItemsSource property is not specified, then the Items property is used as the source collection for the CollectionView.  This construct is what allows for advanced collection binding scenarios (grouping, sorting, currency, etc).

    Thursday, October 4, 2007 8:59 AM
  • Thanks a lot, I figured that last one out reading about (list)collectionviews.

    I came up with a different example, I'll post it just te be complete (should someone search this forum for the same question).

    The Window's DataContext is set to an instance of a class that contains the ObservableCollection<Person> TheList {} property.

     

    Code Block

    <Window x:Class="WindowsApplication1.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="WindowsApplication1" Height="300" Width="300">

    <Grid>

    <Grid.RowDefinitions>

    <RowDefinition Height="*"/>

    <RowDefinition Height="Auto"/>

    </Grid.RowDefinitions>

    <ListView x:Name="asdf" ItemsSource="{Binding TheList}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"/>

    <StackPanel Grid.Row="1" DataContext="{Binding TheList.CurrentItem}">

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

    </StackPanel>

    </Grid>

    </Window>

     

     

    The Grid I mentioned is a StackPanel in this example, makes no difference Smile
    Thursday, October 4, 2007 9:39 AM
  • Nicely done.

     

    And just for an extra tidbit, you could also set the DataContext on the StackPanel like this:

     

    <StackPanel Grid.Row="1" DataContext="{Binding TheList/}">

     

    Note that there is a forward slash (/) added to the binding path:  {Binding TheList/}  This is a shorthand notation for "current item". 

     

    So if you ever see a strange looking binding path that almost resembles XPath:  {Binding TheList/Name}  This simply means to take the Name property of the current item in the TheList collection.

    Thursday, October 4, 2007 10:04 AM

All replies

  • Each IEnumerator has a 'Current' value.  So does List<Person>.  With IsSynchronizedWithCurrentItem = true, you tell the Selector object (in this case, the ListBox), to keep it's SelectedItem in sync with the 'Current' value of the List object.  This means, each time you change the current selection in the listbox, the 'Current' value of the actual list is also changed.

    With the TextBox, the binding first goes to search for the Property on the DataContext, if it can't find it there, it tries to locate it in the 'Curren't value of the DataContext.  Bindings know about this principle and take it into account when searching for a property.

    Thursday, October 4, 2007 8:34 AM
  • Nice. How do I explicitly specify the Current? Because I would like to set the DataContext of my Grid to this Current (It's much clearer what's going on in my opinion).

    This is needed when an IEnumarator implementation shares a property with the class it contains:

    class Holder : List<Info> {

    public Name { get; set; }

    }

     

    class Info {

    public Name { get; set; }

    }

     

    Thanks!

     

    Thursday, October 4, 2007 8:40 AM
  • Excellent question!

     

    When binding to a collection, WPF will first look for the specified property (in this case, the 'Name' property) on the collection itself.  If such a property does not exist, it will then look for the property on the "current item" within the collection.  If found, the binding is established just as if you had said Path=CurrentItem.Name.

     

    So there are a couple of things to note here.  First, WPF imposes this concept of currency on a databound collection.  Second, when binding a Selector to a collection, you can easily synchronize the collection view's "CurrentItem" with the Selector's SelectedItem by setting the IsSynchronizedWithCurrentItem property to true.

     

    Noteworthy Tidbit: An ItemsControl is actually never bound directly to a collection... Rather, it is always bound to a CollectionView.  When an ItemsSource is specified, the given collection serves as the source for the CollectionView.  If the collection implements INotifyCollectionChanged, then CollectionView will monitor the collection for changes and keep the view in sync with it.  If the ItemsSource property is not specified, then the Items property is used as the source collection for the CollectionView.  This construct is what allows for advanced collection binding scenarios (grouping, sorting, currency, etc).

    Thursday, October 4, 2007 8:59 AM
  • The List doesn't have the notion of CurrentItem... only the ListCollectionView.

     

    If you post your markup, we can probably tell you how to bind your Grid's DataContext to the CurrentItem of the collection view.  It might be possible by simply setting DataContext="{Binding /}"... but that totally depends on your scenario.

    Thursday, October 4, 2007 9:05 AM
  • Thanks a lot, I figured that last one out reading about (list)collectionviews.

    I came up with a different example, I'll post it just te be complete (should someone search this forum for the same question).

    The Window's DataContext is set to an instance of a class that contains the ObservableCollection<Person> TheList {} property.

     

    Code Block

    <Window x:Class="WindowsApplication1.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="WindowsApplication1" Height="300" Width="300">

    <Grid>

    <Grid.RowDefinitions>

    <RowDefinition Height="*"/>

    <RowDefinition Height="Auto"/>

    </Grid.RowDefinitions>

    <ListView x:Name="asdf" ItemsSource="{Binding TheList}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"/>

    <StackPanel Grid.Row="1" DataContext="{Binding TheList.CurrentItem}">

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

    </StackPanel>

    </Grid>

    </Window>

     

     

    The Grid I mentioned is a StackPanel in this example, makes no difference Smile
    Thursday, October 4, 2007 9:39 AM
  • Nicely done.

     

    And just for an extra tidbit, you could also set the DataContext on the StackPanel like this:

     

    <StackPanel Grid.Row="1" DataContext="{Binding TheList/}">

     

    Note that there is a forward slash (/) added to the binding path:  {Binding TheList/}  This is a shorthand notation for "current item". 

     

    So if you ever see a strange looking binding path that almost resembles XPath:  {Binding TheList/Name}  This simply means to take the Name property of the current item in the TheList collection.

    Thursday, October 4, 2007 10:04 AM
  • I don't think "{Binding TheList/}" is very clear, but I agree that "{Binding TheList/Name}" seems very usable.

    Thanks a lot!

    Thursday, October 4, 2007 10:13 AM