(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
There's a problem people often run into when using XML data binding in WPF. If you bind a ListBox
to a collection of items, the IsSynchronizedWithCurrentItem
property sometimes doesn't appear to work as you'd expect. Here's an example of the problem:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Page.Resources> <XmlDataProvider x:Key="src"> <x:XData> <data xmlns=''> <item name="One" /> <item name="Two" /> <item name="Three" /> </data> </x:XData> </XmlDataProvider> </Page.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource src}, XPath=/data/item}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding XPath=@name}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <TextBlock Grid.Column="1" Text="{Binding Source={StaticResource src}, XPath=/data/item/@name}" /> </Grid> </Page>
This example contains a simple embedded XML data source. It binds a ListBox
to the <item>
elements in that source, with a DataTemplate
showing the item's name attribute. There's also a TextBlock
that sits next to the ListBox
, bound to the same information. Since the list box's IsSynchronizedWithCurrentItem
property is set to True
, you might reasonably expect the text block to reflect the current selection in the list box. But as you can see, it doesn't:
The text block on the right has got stuck on the first item, even though the list box is on the second. What's happening? There are two things you need to understand which, between them, explain what's going on here:
XmlNodeList
containing the resultsXmlNodeList
objects), then even if those two collections contain the exact same sequence of objects (e.g. because they were both generated by evaluating the same XPath expression in the same context), they will be treated as two different collections when it comes to tracking the currently selected itemBearing this in mind, look at the two Binding
expressions in the example above. They both independently evaluate to a sequence of nodes. And they're not even the same nodes. The list box's ItemsSource
is bound with an XPath expression that evaluates to a sequence of XmlElement
objects, representing the <item>
elements in the source. The TextBlock
is bound with an XPath expression that evaluates to a sequence of XmlAttribute
objects, representing the name
attributes of the <item>
elements in the source.
So that's two distinct collection objects that contain two completely different sets of objects. We shouldn't really be surprised that WPF treats the two as being unrelated. Unfortunately, that's not really what we wanted here, but we can fix this fairly easily.
If you want to keep bound elements synchronized with the current selection, there's one simple rule to remember when using XML data binding: make sure you've only got one binding expression that generates the collection.
The problem with the earlier example is that it has two bindings that both generate their own version of the collection. Here's a modified version that only generates the collection once:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Page.Resources> <XmlDataProvider x:Key="src"> <x:XData> <data xmlns=''> <item name="One" /> <item name="Two" /> <item name="Three" /> </data> </x:XData> </XmlDataProvider> </Page.Resources> <Grid DataContext="{Binding Source={StaticResource src}, XPath=/data/item}"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding XPath=@name}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <TextBlock Grid.Column="1" Text="{Binding XPath=@name}" /> </Grid> </Page>
We've now got an extra binding. In the Grid
that contains the rest of the UI we've bound the DataContext
to the collection of <item>
elements. Critically, this is now the only binding that generates a collection. The ListBox
now just binds directly to the DataContext
. Its binding doesn't even contain an XPath expression, so it won't attempt to generate a collection of its own. And while the text block still contains an XPath expression, it's not an absolute path. It's a relative path that will be evaluated relative to the data context. And its data context is the exact same collection that the list box is bound to.
In short, the list box and text block now share the same underlying collection object. Consequently, they now remain in sync: the text block shows the name attribute for the currently selected item in the list box.
You won't see this problem when binding to objects. It is essentially a problem specific to XML data binding. This is because with XML data binding, collections are generated on the fly. XML documents don't have an intrinsic notion of a collection, so we rely on XPath expressions to build collections when we need to present lists. But an object model typically provides collections or arrays to represent sequences of items, so there's no need for the data binding layer to generate such things.
Since with object models, data binding will never generate a collection for you, you tend not to run into the problem discussed in this blog. But XML binding has to generate collections based on the XPath expressions you give it. And that's why you need to be careful about where you bind to a collection when using XML data sources.