GridView representation of XML
- Hi,
I am trying to display the contents of an Xml Document in a GridView. An example of the the Xml is as follows;
<Table>
<Column>Name
<Cell>Dave</Cell>
<Cell>Pete</Cell>
</Column>
<Column>Age
<Cell>32</Cell>
<Cell>26</Cell>
</Column>
</Table>
I want the GridView to look as follows;
[Name] [Age]
Dave 32
Pete 26
That is, in tabular form.
My Xaml contains an XmlDataProvider;
<XmlDataProvider x:Key="TableModel" />
and the C#
XmlDataProvider xml = (XmlDataProvider)FindResource("TableModel");
xml.Document = doc;
Each GridViewColumn has the following DataTemplate;
<DataTemplate x:Key="TableCellTemplate">
<TextBlock Text="{Binding XPath=/Table/Column/Cell}" />
</DataTemplate>
The nearest I have got to achieving what I want is;
[Name] [Age]
Dave Dave
Dave Dave
I assume my data binding is wrong. My question is, what data binding do i have to do to achieve this? Any help anybody can provide will be gratefully received.
Answers
So you mean in the same application you're using the same ListView/GridView pair to display heterogeneous types of data that can be loaded out of any random XML file?? That's a tough one. :)
If that's really the case then you probably have no special template needs per column anyway, right? Meaning, you wouldn't know to format a Person/Age any differently than a Planet/DistanceFromSun. So in that case wouldn't you have one universal column template anyway? If that's the case then you should still define it in XAML and just use:
column.CellTemplate = (DataTemplate)gridView.FindResource("SomeWellKnownName");
As you're building the columns out dynamically.In the end I guess I need to know more about the needs of your app to be able to make any kind of valuable recommendation as to how to proceed.
Later,
Drew
All Replies
- How about defining your data in xml as follows:
<Payroll>
<Employee>
<Name>Dave</Name>
<Age>32</Age>
</Employee>
<Employee>
<Name>Pete</Name>
<Age>26</Age>
</Employee>
</Payroll>
Just make your xml data as more structural as possible, then xpath can refer to the individual data very easily.
Sheva First, are you expecting to be able to dynamically create the columns based in the XML input as well? Because that won't work. Second, I have to be honest, it isn't straight forward to solve because your data is not actually structured in a way that lends itself to being shaped in the UI this way. You actually have to transpose <Cells> in your XML into rows. In fact, the XML is more like relational data. If you have the choice, you should change it to be structured something like this instead:
<Person>
<Name>...</Name>
<Age>...</Age>
</Person>All that said, here's something that almost works given your XML structure. The only thing I'm missing (because my XPath is rusty) is the right way to correlate the <Cell> position XPath for the Age to the position of the <Cell> that is the context node of the current row:
<Window .../>
Window.Resources>
<<
XmlDataProvider x:Key="TableModel" XPath="/Table/Column[1]/Cell"><
Table xmlns=""><
Column>Name<
Cell>Dave</Cell><
Cell>Pete</Cell></
Column><
Column>Age<
Cell>32</Cell><
Cell>26</Cell></
Column></
Table></
XmlDataProvider></
Window.Resources><
ListView ItemsSource="{Binding Source={StaticResource TableModel}}"><
ListView.View><
GridView><
GridView.Columns><
GridViewColumn><
GridViewColumn.Header>Name</GridViewColumn.Header><
GridViewColumn.CellTemplate><
DataTemplate>
<TextBlock><TextBlock.Text><Binding XPath="text()"/></TextBlock.Text></TextBlock></
DataTemplate></
GridViewColumn.CellTemplate></
GridViewColumn><
GridViewColumn><
GridViewColumn.Header>Age</GridViewColumn.Header><
GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock><TextBlock.Text><Binding XPath="../../Column[2]/Cell"/></TextBlock.Text></TextBlock>
</DataTemplate></
GridViewColumn.CellTemplate></
GridViewColumn></
GridView.Columns></
GridView></
ListView.View></
ListView></Window>
As you'll see if you try this both rows will end up with the Age of 32 which is because I didn't have time to figure out how to grab the <Cell> in the Age column that corresponds to the position of the current <Cell> in the Name column. If I have time to figure it out later I'll post back, but this should get u started.
HTH,
Drew- unfortunately, the example of the XML I gave you is about as structured as it can be but thank you for your help anyway.
- thank you for your excellent reply Drew.
Both yourself and Footballism above are quire right. The XML should be more structured. Unfortunately, because I do not know contents of the XML and, like you said, I want to be able to add columns to the GridView dynamically then I maynot be able to make the XML more structured.
I'll take a look at the wonderful example code you have given. Thank you for your help. - I had a rethink and decided to impose more structure on the XML. Given the original example the XML will now look like;
<People>
<Person>
<Name>Dave</Name>
<Age>32</Age>
</Person>
<Person>
<Name>Pete</Name>
<Age>26</Age>
</Person>
</People>
Which looks more sensible.
I cannot hard code the columns in the Xaml because I do not know that this is the structure of the XML until the control has it. As a result most the work is done programmatically in C#.
The Xaml declares the XmlDataProvider as stated in my original post and the C# navigates through the XML to set the XPath on the provider. For example,
xml.XPath = /People/Person;
I am programatically creating the columns thus,
GridViewColumn column = new GridViewColumn();
column.Header = element.Name;
gridView.Columns.Add(column);
Binding binding = new Binding();
binding.Source = xml;
binding.Mode = BindingMode.TwoWay;
binding.XPath = element.Name;
column.DisplayMemberPath = binding.XPath;
and this works.
My table looks thus;
[Name] [Age]
Dave 32
Pete 26
But...
I want to make the extend the functionality of the cell. When I create my columns I create a DataTemplate and add it as the column's CellTemplate property. The DataTemplate contains a TextBlock (for now, it will be extended later);
DataTemplate template = new DataTemplate();
FrameworkElementFactory textblock = new
FrameworkElementFactory(typeof(TextBlock));
textblock.SetValue(TextBlock.TextProperty, binding);
template.VisualTree = new FrameworkElementFactory(typeof(Grid));
template.VisualTree.AppendChild(textblock);
column.CellTemplate = template;
When I first view the table it looks as follows;
[Name] [Age]
Dave 32
Dave 32
and when I select the second row the table changes and shows
[Name] [Age]
Pete 26
Pete 26
My question this time is, what am I doing wrong with the binding in the textblock FrameworkElementFactory that causes this problem?
Any help would be greatly appreciated.
Barry Alright, you're headed in the right direction now! In the first part of your example I don't quite understand why you're constructing a Binding instance when you're building the columns. You don't actually use it, nor do u need it because you're using DisplayMemberPath, that's all you need to do... it actually ends up creating a binding for you under the covers. DisplayMemberPath is just a quick way to have the column output whatever the value of the node is in a TextBlock without any need to declare a template of your own. Therefore you pretty much use them in a mutually exclusive matter.
Next, I would change the way you're working with the templates because you're basically hardcoding your style which goes against some of the primary goals of WPF. Instead I recommend you actually just let the styles resolve themselves by using the DataType property of DataTemplate. That way your designers can design the styles outside of the code in XAML and tweak them til their hearts are content. The templates will resolve according to element name, so they would simply need to do something like the following in a resource dictionary that is in the scope of the Grid:
<DataTemplate DataType="Age">
<!-- your templae here -->
</DataTemplate>Finally, the reason you get all Daves and then all Petes is because you've got something wrong with your binding logic. Seems like what's happening is that all rows are being bound to the current record for some reason. Since you didn't supply all of the logic you're using to bind, I'm not sure what's going wrong. You mention in the first part of the example that you're setting the XPath of the XmlDataProvider programatically, you shouldn't need to do that. The XPath of the XmlDataProvider basically just points to the node-set that represents the elements that you want to bind to and that really shouldn't change over time in your case. The ListView will loop over the items for you and for each item it will create the appropriate UI elements according to your GridView templates.
If you're still struggling with this later today, let me know and I'll see if I can whip up some sample code for you.
HTH,
Drew- Thank you for replying again.
I understand that if the format of the XML is not to change then everything can be done in the Xaml. I have a working example of what I want to achieve where everything is in Xaml. Unfortunately the example XML I gave was just one example of the XML to be displayed. It could just as easily be
<Planets>
<Planet>
<Size>...</Size>
<DistanceFromSun>...</DistanceFromSun>
</Planet>
...
</Planets>
[Size][DistanceFromSun]
In a nutshell the control is ignorant of the XML it is attempting to display and, hence, why I am having to do most things programatically and wrong.
You help is greatly appreciated. So you mean in the same application you're using the same ListView/GridView pair to display heterogeneous types of data that can be loaded out of any random XML file?? That's a tough one. :)
If that's really the case then you probably have no special template needs per column anyway, right? Meaning, you wouldn't know to format a Person/Age any differently than a Planet/DistanceFromSun. So in that case wouldn't you have one universal column template anyway? If that's the case then you should still define it in XAML and just use:
column.CellTemplate = (DataTemplate)gridView.FindResource("SomeWellKnownName");
As you're building the columns out dynamically.In the end I guess I need to know more about the needs of your app to be able to make any kind of valuable recommendation as to how to proceed.
Later,
DrewThat really is the case.
You're correct. I don't have to format one form of XML differently from another as they will follow the same basic structure so I can have one cell template. I guess the problem now is me and my lack of knowledge (new to WPF, thrown in at the deep end, etc). Could you help me with this?
As for the application, the user defines a set of columns which they want displayed in a table. The user can then fill the table using a DataTemplate that contains a TextBlock and TextBox.
I hope this information helps and I am very grateful for your help and time.
Barry
Ok, so I think you're 100% on track. You've got the XML information structured heirarchically now, you've got your GridViewColumn building logic that allows you to accept any form of structured XML and present it in row form and now we just need to apply the DataTemplate.
Going back to my previous post, I would definitely recommend that you keep the DataTemplate definition in pure XAML so that it's easier for you or your designers to work with. You can use the FindResource method that I pointed out and pass in a "well known" string key to get the DataTemplate and assign to the CellTemplate property whenever you build out the GridViewColumns.
You could even do some really cool stuff where people could actually pick from a set of templates that format certain data differently and choose how they want a specific column formatted. For example, you probably would want to right align numbers or format currency a special way. Heck you could even let power users supply DataTemplate definitions of their own through an external ResourceDictionary.
Cheers,
Drew- Thanks.
I was going to look into it over the weekend but PC is suffering from the black window bug.
One question. If I define a DataTemplate thus,
<DataTemplate>
<TextBlock />
</DataTemplate>
What do I need to set as the binding in the TextBlock? Do I need to set a binding in the template or do I just specify one on the ListView element? All the examples I have seen have bindings in both. You need to bind the ListView's ItemsSource property to the node set of elements which, given your sample structures, would be an XPath as simple as "/*/*". Then, since you're building the columns dynamically by looping over the child elements of each item, you can simply set the XPath to the element name like you already figured out and then all your TextBlock needs is Text="{Binding XPath=text()}".
HTH,
Drew- Hi Drew,
thank you for all you help. I think I have enough information to solve this problem now.
I am very grateful for all the help and time you have given me over the past few days.
Barry - almost there.
Given the following XML,
<People>
<Person>
<Name>Bob</Name>
<Age>32</Age>
</Person>
<Person>
<Name>Alice</Name>
<Age>23</Age>
</Person>
</People>
the table looks as follows,
[Name][Age]
32 32
23 23
My Xaml is as follows;
<Window.Resources>
<XmlDataProvider x:Key="TableModel" />
<DataTemplate x:Key="TableCellTemplate">
<Grid>
<TextBlock Text="{Binding XPath=text()}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ScrollViewer>
<ListView ItemsSource="{Binding Source={StaticResource TableModel}}">
<ListView.View>
<GridView x:Name="GV">
<GridView.Columns />
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
</Grid>
and the C# method that creates the columns is;
XmlElement docElement = xml.Document.DocumentElement;
XmlElement indElement = (XmlElement)docElement.ChildNodes[0];
foreach (XmlElement element in indElement.ChildNodes)
{
xml.XPath = "//" + element.Name;
GridViewColumn column = new GridViewColumn();
column.Header = element.Name;
column.CellTemplate = (DataTemplate)FindResource("TableCellTemplate");
gridview.Columns.Add(column);
}
Any ideas?

