Sunday, March 10, 2013

WPF - How to run XPath count() in XmlDataProvider binding

Hi,
Welcome to my very first (not really) blogging entry. I was monkeying around with an issue on internet and could not find any solution, however, I did find quite a few people looking for the same thing I was looking for. I tried something that worked, thought I would share that with the world.

So the issue is, You have the following Xml to bind to in your WPF application

 <MyData>  
  <Book Name="WPF Basics" Author="John Doe">  
   <Publishers>  
    <Publisher>  
     <Name>Wrox </Name>  
     <Name>Microsoft Press</Name>  
    </Publisher>  
   </Publishers>  
  </Book>  
  <Book Name="Thinking in WPF" Author="Johny Cage">  
   <Publishers>  
    <Publisher>  
     <Name>Wrox </Name>  
    </Publisher>  
   </Publishers>  
  </Book>  
 </MyData>  

Now you have a Listview that displays Book name in one column and number of Publishers in other column.

If WPF XmlDataProvider supported XPath functions you could do something like this for 2nd column

{Binding XPath=count(Publishers/Publisher)}

But XmlProvider binding uses SelectNodes() of XmlNode in System.Xml which does not take XPath functions.

Another way I was able to think of was to use converters and do something like this

{Binding XPath=Publishers/Publisher, Converter=myOwnNodeListToCountConverter}

But XmlDataProvider calls the .ToString() before passing it on to your converter. Which means, your converter won't get an XmlNodeList object, but just an empty string.


So lastly, I tried this which finally worked, Not an elegant way but it works. :)

The basic idea is to render all the items you want to count in an invisible (Visibility=Collapsed) ItemsControl and Bind a TextBlock to the Items.Count of that ItemsControl. Here is a little snippet to show the concept.


<ListView IsSynchronizedWithCurrentItem="True" Margin="8,8,0,0" ItemsSource="{Binding Source={StaticResource xmlFmlDataSource}, XPath=//Programs/Program, Mode=OneWay}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Book name" Width="200" DisplayMemberBinding="{Binding XPath=@Name}"  >
           
                    </GridViewColumn>
                    <GridViewColumn Header="Params" Width="50" >
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel>
                                <ItemsControl x:Name="dummyItemsControl" ItemsSource="{Binding XPath=Publishers/Publisher}" Visibility="Collapsed">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <TextBlock Text="" />
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                                <TextBlock Text="{Binding ElementName=dummyItemsControl, Path=Items.Count}" />
                                </StackPanel>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>



This is not my dream solution that I feel ecstatic about, but this works and won't probably a big deal if the data volume is not too large. I would love to read your comments.