XAML Xml Binding
- Is it possible to work a master-detail where the detail gets it's Xml data from a different part of the tree and not a direct child of the original master element.
For example, I want to show a category list, and selecting a category will show me the reports in that category. However a report can be in more than one category.
Ideally I don't want to duplicate the report information so I want Xml that looks like:
<reports>
<categories>
<category name="cat1">
<reports>
<report key="rep1" />
<report key="rep2" />
</reports>
</category>
<category name="cat2">
<reports>
<report key="rep1" />
</reports>
</category>
</categories>
<reportdefinitions>
<report key="rep1" name="Report1" Description="blah blah" />
<report key="rep2" name="Report1" Description="blah blah" />
</reportdefinitions>
</reports>
Is this possible or am I expecting too much of the declarative xpath syntax?
Thanks,
Graeme
All Replies
- yes, it should be possible
Hi there,
What makes this problem difficult is that there doesn't appear to be anyway to parameterise the XPath property of a binding with variable values. You can put literals into the XPath string sure, but you can't get a variable into it - or atleast I haven't found a way of doing this. What's more, the XPath property isn't a DependencyProperty (perhaps understably), so you can't build the XPath dynamically. That's a double gotcha!
I spent a couple of hours on this and couldn't find a way to do it declaratively in the Xaml because of the limitations above. There's a workaround below which uses a Converter on the second binding to swap out the keys selected in the first (master) ItemsControl for the required node list to be consumed by the details control. The xaml for the complete page is as follows:
<Window x:Class="wpfforum01.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:wpfforum01"
Title="wpfforum01" Height="300" Width="300"
>
<Window.Resources>
<this:XmlConverter x:Key="xmlConverter"/>
<XmlDataProvider x:Key="xmlData" XPath=".">
<x:XData>
<root xmlns="">
<categories>
<category name="cat1">
<reports>
<report key="rep1" />
<report key="rep2" />
</reports>
</category>
<category name="cat2">
<reports>
<report key="rep1" />
</reports>
</category>
</categories>
<reportdefinitions>
<reportdef defkey="rep1" name="Report1" Description="This first report has the following prop..." />
<reportdef defkey="rep2" name="Report2" Description="For the second report I decided to..." />
</reportdefinitions>
</root>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<Grid><StackPanel>
<ComboBox Name="combo" DataContext="{StaticResource xmlData}"
ItemsSource="{Binding XPath=root/categories/category}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=@name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox DataContext="{Binding ElementName=combo, Path=SelectedItem}"
ItemsSource="{Binding XPath=reports/report/@key, Converter={StaticResource xmlConverter}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding XPath=@name}" />
<TextBlock Text=": " />
<TextBlock Text="{Binding XPath=@Description}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>You should recognise the resources section as just containing the data island you gave in your original question. I've changed some of the element names so that their aren't any duplicates although this isn't stricly necessary. In the body of the window I've declared a combobox as the master and a Listbox for the detail. Notice that the Listbox is bound to the selected item of the combobox, as you might expect, and xpath is used to select the keys from the category/report nodes. A converter is then specified (xmlConverter) which will search the xml document for those keys and instead return a list of reportdefinition nodes. The code for the xmlConverter is shown below:
public class XmlConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return null;String xpath = "/root/reportdefinitions/reportdef[{0}]";
String condition = "@defkey='{0}'";
XmlDocument doc = null;List<String> compoundCondition = new List<String>();
foreach(XmlNode node in (IEnumerable)value)
{
if(doc == null) doc = node.OwnerDocument;
compoundCondition.Add(String.Format(condition, node.InnerText));
}if (doc == null) { return null; }
else
{
String select = String.Format(xpath,
String.Join(" or ", compoundCondition.ToArray()));
return doc.SelectNodes(select);
}
}public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}The xmlConverter class uses the values passed in (the report/@key values) to select the reportdefinitions out of the same XmlDocument. The exmple above uses hardcoded xpath and condition strings at the moment, but everything else has been generalized. So if you were to pass the xpath and condition strings in to the Convert method using the ConverterParameter property of the binding class then you'd have a generalised solution. Admitedly some of the coding is a bit clumsy but hopefully you can see what I'm driving at.
I hope this helps.
- Wow - thanks for the detailed response Karl.
I took a wild goose chase trying to nest {Binding...} expressions but got nowhere. It's quite frustrating sometimes when all the examples show the simplest thing but never address slightly more complex scenarios!
Maybe I'm trying to do too much in Xaml and need to appreciate it's never going to allow me to do as much as code!!
Thanks again,
Graeme - Grae
I have found the XML binding to be very limited especially in complex scenarios. I started off trying to write a pure XAML solution but ended up somewhere in between. The converter solution outlined by Karl is a good one for this sort of selection scenario, and one I have used before. If XAML supported XQuery, you should be able to do natively perform "joins" like the one you are trying to do.
Also it gets even worse if you try to push stuff back into your XML document, especially as there is no way of creating elements in XAML, and converters seem to receive read only xml nodelists.
For the two way scenario I generally found what worked best is to make a smaller wrapper class around my XML document which exposes properties for the complex bits of information. The properties can then handle pushing stuff back into the XML.


