2

I have 2 DataGrid and one XmlDataProvider for XML-file. XML-file structure looks like this:

<Setting>
  <Element Name="..." Offset="..." ID="...">
    <Item Name="..." Type="..." Count="..." ID="..." />
    <Item Name="..." Type="..." Count="..." ID="..." />
    <Item Name="..." Type="..." Count="..." ID="..." />
    ...
  </Element>
  <Element Name="..." Offset="..." ID="...">
    <Item Name="..." Type="..." Count="..." ID="..." />
    <Item Name="..." Type="..." Count="..." ID="..." />
  </Element>
  ...
</Setting>

I need to display the values of attributes of all Elements in first DataGrid, and the values of attributes of all Items in second DataGrid using same XmlDataProvider.

XAML to display the values of attributes of all Elements in first DataGrid:

...
<Grid.DataContext>
  <XmlDataProvider x:Name="xml_setting" XPath="/Setting/Element"/>
</Grid.DataContext>
<DataGrid ItemsSource="{Binding}">
  <DataGrid.Columns>
    <DataGridTextColumn Header="Name" Width="*" Binding="{Binding XPath=@Name}"/>
    <DataGridTextColumn Header="Offset" Width="80" Binding="{Binding XPath=@Offset}"/>
    <DataGridTextColumn Header="ID" Width="80" Binding="{Binding XPath=@ID}"/>
  </DataGrid.Columns>
</DataGrid>
...

I tried to set the XmlDataProvider XPath value in "/Setting" and the Columns XPath values in "/Element/@Name", "/Element/@Offset" and "/Element/@ID", but only first Element is displayed.

How can I working with one XmlDataProvider to binding to different columns of different DataGrids to display the values of attributes of different nodes of the XML-file?

kodwi
  • 97
  • 1
  • 10

1 Answers1

3

The problem is you have to set the default namespace of the XML root node to empty string:

<Setting xmlns="">

Otherwise it will use the xmlns assigned in the window scope (which is of course "http://schemas.microsoft.com/winfx/2006/xaml/presentation") and the elements' Path will be incorrect.

To fill data for the second grid, you can bind the ItemsSource to the first DataGrid, the Path should be SelectedItem, this is exactly an XmlElement which is an IEnumerable. So you can use XPath to bind each attribute of each Item element to the corresponding DataGridTextColumn like this:

<!-- the first Grid -->
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Name="grid1">
  <DataGrid.Columns>
    <DataGridTextColumn Header="Name" Width="*" Binding="{Binding XPath=@Name}"/>
    <DataGridTextColumn Header="Offset" Width="80" Binding="{Binding XPath=@Offset}"/>
    <DataGridTextColumn Header="ID" Width="80" Binding="{Binding XPath=@ID}"/>
  </DataGrid.Columns>
</DataGrid>
<!-- the second Grid -->
<DataGrid Grid.Column="1" ItemsSource="{Binding ElementName=grid1,Path=SelectedItem}"
          AutoGenerateColumns="False">
  <DataGrid.Columns>    
    <DataGridTextColumn Header="Name" Width="*" Binding="{Binding XPath=@Name}"/>
    <DataGridTextColumn Header="Type" Width="100" Binding="{Binding XPath=@Type}"/>
    <DataGridTextColumn Header="Count" Width="80" Binding="{Binding XPath=@Count}"/>
    <DataGridTextColumn Header="ID" Width="80" Binding="{Binding XPath=@ID}"/>      
  </DataGrid.Columns>
</DataGrid>

Update: The above code for the second DataGrid works OK for reading (showing) data but it does not support modifying. It's because of the way we bind the data, in fact the underlying collection is ChildNodes which is actually an XmlNodeList, this collection just implements IEnumerable interface. So it does not support editing. The underlying collection should implement the IEditableCollectionView interface. By changing the way of binding data we can set the underlying collection as an IEditableCollectionView. In fact the way XPath queries data can help us get an IEditableCollectionView. So in this case we try using XPath to get the underlying collection (instead of accessing via ChildNodes, which is implicitly enumerable via the SelectedItem of type XmlElement):

<DataGrid Grid.Column="1" 
          DataContext="{Binding ElementName=grid1,Path=SelectedItem}"
          ItemsSource="{Binding XPath=Item}"
          AutoGenerateColumns="False">
    <!-- .... -->
</DataGrid>

Now it works like a charm (tested).

Update: To filter the Elements by Name, you have to use property element syntax (not attribute syntax) like this:

<DataGrid Grid.Column="1" AutoGenerateColumns="False">
    <DataGrid.ItemsSource>
        <Binding XPath="Setting/Element[@Name='SomeName']"/>
    </DataGrid.ItemsSource>
    <!-- ...... -->
</DataGrid>

Note that the above code is just an example of how to specify the XPath, the actual expression depends on the implicit Source.

King King
  • 61,710
  • 16
  • 105
  • 130
  • 1
    Thanks for answer. The code works, but when I trying edit something in the second `DataGrid` `System.InvalidOperationException` throws - `"EditItem is not allowed for this view"`. What I should to do for solve the problem? – kodwi Jul 14 '14 at 21:56
  • Thank you! But I have a question how can I showing data in second `DataGrid` from the specific `Element` without binding second `DataGrid` to first `DataGrid`, just showing all `Items` from `Element` with specific `Name` attribute value (for example, for this `XPath` string: `Setting/Element[@Name='...']`), I tried to set the ItemsSource value in `{Binding XPath=Setting/Element[@Name='...']}` but this is incorrect syntax. – kodwi Jul 15 '14 at 10:02