I am working towards producing a small application that parses XML from a URL and populates a Grid panel based on the contents of the XML. Currently, I have many other elements working properly, but still lack the knowledge needed to hide or show certain columns within the table and having it resize properly. Here's the basic structure of my XAML thus far.
Currently, I feel as though my solution is very poor. I have hard coded each coulmn and row within the Grid and tied their Visibility to a code behind Converter. Under certain conditions, this Converter will return a Visibility of Hidden, but under other conditions it returns the value to display within the table. This feels very sloppy to me, so I assume I've designed this system incorrectly.
My question is more about the proper way to setup this type of system. I am much more familiar with generating the document structure itself within some business logic and then token swapping that generated structure with a token inside the raw document itself. What is a best way to accomplish the goal I'm pursuing?
You could provide the XDocument or XElement retrieved from the web service as the DataContext of an ItemsControl with a Grid. You would then use a DataTemplate to display the information.
XML:
<Entities>
<Person Name="Ted" Age="42" />
<Person Name="Sam" Age="19" />
<Person Name="Bob" Age="25" />
<Person Name="Angie" Age="38" />
</Entities>
Code Behind:
this.DataContext = xdoc;
XAML:
<ItemsControl ItemsSource="{Binding Root.Elements[Person]}"
Grid.IsSharedSizeGroup="True">
<ItemsControl.Resources>
<DataTemplate DataType="Person">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="NameColumn"/>
<ColumnDefinition SharedSizeGroup="AgeColumn" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Path=Attribute[Name].Value}" />
<TextBox Text="{Binding Path=Attribute[Age].Value}"
Grid.Column="1"/>
</Grid>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
Linq to XML resources:
WPF Data Binding with LINQ to XML Overview
WPF Data Binding Using LINQ to XML Example
How to: Bind to XDocument, XElement, or LINQ for XML Query Results
Related
I have a derived devexpress grid control which sets some template for the indicator row:
<dxg:GridControl.Resources>
<sys:Double x:Key="{dxgt:TableViewThemeKey ResourceKey=IndicatorWidth, ThemeName=Office2016White}">300</sys:Double>
<DataTemplate x:Key="{dxgt:RowIndicatorThemeKey ResourceKey=RowTemplate, ThemeName=Office2016White}">
<Grid Name="IndicatorGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35" Name="IndicatorColumnRowNumber" SharedSizeGroup="RowNumberGroup" />
<ColumnDefinition Width="200" Name="IndicatorColumnDescription" SharedSizeGroup="DescriptionGroup" />
<ColumnDefinition Width="200" Name="IndicatorColumnSource" SharedSizeGroup="SourceGroup" />
</Grid.ColumnDefinitions>
//...
</DataTemplate>
</dxg:GridControl.Resources>
Right now the width of the indicator column is fixed to 300. Now I want the width of the row being calculated by the columns defined in the second part. I know I can access the indicator width of the view via the code behind as well but I'm not able to access the template controls in the code behind
var view = ((TableView)this.View);
view.IndicatorWidth = IndicatorColumnRowNumber.Width /* can not be found */ + ...
Since they live in the resources in the xaml. As I understand this is also not supposed to happen. What is the best way to implement this? Maybe extract the definitions in the code behind?
Edit:
Here is a picture of the table
I am not 100% sure what you are trying to achieve but if you want to access your resource in code behind you can go about it like this:
XAML:
<Grid x:Name="MyGrid">
<Grid.Resources>
<DataTemplate x:Key="MyResource">
<TextBlock Text="Hello"></TextBlock>
</DataTemplate>
</Grid.Resources>
</Grid>
Code behind:
public MainWindow()
{
InitializeComponent();
var resource = MyGrid.Resources["MyResource"];
var dataTemplate = (DataTemplate) resource;
}
You have additional tools to obtain any defined resources.
The methods FrameworkElement.FindResource and FrameworkElement.TryFindResource will search for resources from the element you specified and up the visual tree until the main application and including any themes you have setup. From the MS reference:
FrameworkElement.FindResource documentation
Also notice that the Resources dictionary and the FindResource methods accept an object key (not a string). This suits your case since your key is a RowIndicatorThemeKey object. You can instantiate such an object and set the ResourceKey property to the "RowTemplate" value and search for your resource like that.
In general solutions for problems such as yours can be achieved without requiring code behind though, if you provide some more information perhaps we can find a solution based on bindings.
I have a wpf window where I am using xml data through an XMLDataProvider. The screen is based on a grid and all the data is being displayed correctly, having defined the xml as follows...
<Grid.DataContext>
<XmlDataProvider x:Name="Data" XPath="Book/Page" />
</Grid.DataContext>
With the xml source being set in code behind as follows...
InitializeComponent();
string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
Data.Source = new Uri(appPath + #"\SNG.xml");
All so good so far. But now I have a need to read one of the elements from the xml file in the code behind. All my searching and the only way I've found to do it is to bind it to an invisible control then read the data out of the control. e.g. to read the BookRef from the xml I have the following in the xaml...
TextBlock Name="BookRefTextBox" Visibility="Hidden" Text="{Binding XPath=#BookRef}"/>
Then in the code behind...
string bookRef = BookRefTextBox.Text;
This works, I can then use the data that came from the xml file... but it really feels like a fudge. Is there a better way to get the value of parts of the xml file from within the code behind section.
EDIT:
Forgot to say that I've also tried putting the XmlDataProvider in Windows.Resources instead of in Grid.DataContext as some examples I've found do.
However I then can't find a way to set the path to the xml file in code behind. Added to which putting it in Windows.Resource does not make it any easier to find how to access the data from the Xml file.
EDIT2:
Here is an example of the XML file. Note there are multiple books.
<Books>
<Book Id="1" BookRef="12345" Name="My Book Name" Author="Author" Pages="2" >
<Page PageId="1"/>
<Page PageId="2"/>
</Book>
<Book Id="1" BookRef="67890" Name="My Second Book Name" Author="Author 2" Pages="1" >
<Page PageId="1"/>
</Book>
</Books>
OK, here is another way, more complicated, though.
You are correct that you need to synchronize the currently displayed page with the text of the BookRefTextBox. So on top of XmlDataProvider, I define a CollectionViewSource, which can be used to keep track of the current displayed page.
In the XAML code below, both BookRefTextBox and listBox1 (I use ListBox to display pages) are bound to the same CollectionViewSource, and by setting IsSynchronizedWithCurrentItem="True", the current item is updated when the selected page is changed.
The interesting point is the XPath expression for BookRefTextBox.Test, XPath=../#BookRef means the Text of BookRefTextBox is Find the parent element of the current page- which is Book, and display its BookRef attribute".
The whole XAML of the window.
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<XmlDataProvider x:Key="userDataXmlDataProvider1" Source="/Data/XMLFile1.xml" XPath="Books/Book/Page"/>
<CollectionViewSource x:Key="userDataCollectionViewSource1" Source="{StaticResource userDataXmlDataProvider1}"/>
</Window.Resources>
<Grid DataContext="{StaticResource userDataXmlDataProvider1}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="BookRefTextBox" Grid.Row="0" Text="{Binding XPath=../#BookRef}" />
<ListBox x:Name="listBox1" Grid.Row="1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8,0,8,0">
<Label Content="{Binding XPath=#PageId}" Width="100" Margin="5" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And from code behind, you can get the text of the BookRefTextBox.
Edit:
In order to set the source from code behind:
If XmlDataProvider is declared in Window.Resources, it has a x:Key attribute, you access a resource via Key, not Name.
XmlDataProvider xdp = this.Resources["userDataXmlDataProvider1"] as XmlDataProvider;
xdp.Source = new Uri(...);
I believe I have finally found the answer that avoids the use of a hidden control. First off many thanks to kennyzx for his answer which while it still used a hidden control was invaluable in leading me to this answer.
Instead of putting the XmlDataProvider in the Grid.Context it has been moved to the Window.Resources and a CollectionViewSource added to accompany it.
<Window.Resources>
<XmlDataProvider x:Name="books" x:Key="bookData" XPath="Books/Book/Page"/>
<CollectionViewSource x:Key="collBookData" Source="{StaticResource bookData}"/>
</Window.Resources>
A new XmlDataProvider is defined in the code behind and in the constructor of the window is set to be a reference to the one defined in the XAML Windows.Resources.
XmlDataProvider bookData;
public BookPage()
{
InitializeComponent();
string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
bookData = (XmlDataProvider)this.Resources["bookData"];
bookData.Source = new Uri(appPath + #"\SNG.xml");
}
The DataContext of the Grid is set to be the CollectionViewSource.
<Grid.DataContext>
<Binding Source="{StaticResource collBookData}"/>
</Grid.DataContext>
The above is not 100% necessary as it could be specified on each control instead, but this way makes for simpler binding on each control on the form. (No hidden controls in this solution, only the ones I want to actually show). For example...
<TextBlock Name="myTextBlockName" Style="{StaticResource MyTextBlockStyle}" Text="{Binding XPath=../#BookRef}" />
Finally the bit to read the data from the XML in code behind.
XmlNode currentXmlNode = bookData.Document.SelectNodes("Books/Book/Page").Item(collBookData.View.CurrentPosition);
string currentBookRef = currentXmlNode.ParentNode.Attributes["BookRef"].Value;
Just as an aside, this solution also allows me to use MoveCurrentToPrevious and MoveCurrentToNext against collBookData.View to change the current page being displayed (previously had a hidden listbox control to do that and wasn't happy with that solution either).
I'm trying to bind to some XML data from my WPF application. I've set up the data context so that the XmlElement I'm trying to bind to ends up looking like this:
<Item name="Potion" classes="Healing Item" value="200">
<Classes>
<Class value="HealingItem" />
</Classes>
<Description value="A low quality potion, it restores a small amount of health" />
<Components>
<HealingComponent>
<BattleHealingComponent>
<HPHealingComponent value="500" type="Absolute"/>
</BattleHealingComponent>
</HealingComponent>
</Components>
</Item>
Now here's the problem. I can't figure out an XPath query I can bind with that will return only the Component Subnodes.
I know it'll go something like this:
ItemsSource="{Binding XPath=Components/*/????}"
I'm stuck on what to use for ????
The result of this query should display "HealingComponent" I've tried playing around with various different parameters on an online XPath visualizer, but I can't figure this one out. I ready about name(), but I can't seem to get that to work.
Any help would be appreciated
In addition to the ItemsSource you probably need an ItemTemplate, this should work:
<ListBox ItemsSource="{Binding XPath=Components/*}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
If you do not intend to do anything fancy you can also use the DisplayMemberPath, though in this case the binding ensures that Name is not interpreted as an XPath, you may not have that kind of control with DisplayMemberPath.
I have a XML file that i'm going to use as a database for my project, this is the xml file i have:
http://pastebin.com/JgyYkn4E
I found this question and tryed it:
How to bind xml to the WPF DataGrid correctly?
I want to display Cadeiras of each Semestre, separately, one in each DataGrid. Changed some minors things to fit my project but it didn't worked, after spending some hours i manage to put it working querying the XElement with this:
XElement db = XElement.Load("db.xml");
var cadeira = from elem in db.Descendants("Semestre")
where elem.Element("Nome").Value == "Semestre 1"
select elem.Element("Cadeiras");
dataGrid1.DataContext = cadeira;
First Question: This worked but i just want to know if this is the best thing to do because, this DataGrid is inside a TabItem (which is inside a TabControl), later i will have to create new TabItems (for each Semestre, with a DataGrid inside with Cadeiras of that respective Semestre) at runtime, without having the XAML binding aid.
Second Question: In XAML, what's the difference between binding as in here How to bind xml to the WPF DataGrid correctly? and binding as this WPF Datagrid binding to xml ?
Thanks in advance.
Best regards,
-N
You can do a lot via data templating, try this example in a XAML-Parser:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Page.Resources>
<XmlDataProvider x:Key="data" XPath="GPA/Semestre" Source="http://pastebin.com/raw.php?i=JgyYkn4E"/>
</Page.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TabControl ItemsSource="{Binding Source={StaticResource data}}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=Nome}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding XPath=Cadeiras/Cadeira}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Activa" Binding="{Binding XPath=Activa}"/>
<DataGridTextColumn Header="Nome" Binding="{Binding XPath=Nome}"/>
<DataGridTextColumn Header="Nota" Binding="{Binding XPath=Nota}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</ScrollViewer>
</Page>
This will create the TabControl and all the DataGrids for you. (In your sample the Cadeiras do not look very complex so each of them are just one row in a DataGrid, if you need one DataGrid per Cadeira you can do that as well by creating an ItemsControl whose ItemTemplate is a DataGrid)
The difference between the methods in those questions is that they use different classes to represent the XML, normally you would not use XElement since that will not support XPath in the bindings.
LINQ to XML objects XDocument and XElement do not use XPath. For details, see How to: Bind to XDocument, XElement, or LINQ for XML Query Results.
There is no inherently correct way to do this but as there is native binding support for XmlDataProviders and thus XmlDocuments i would go with that unless i have a significant reason not to.
I have an XML file with the following structure:
<Products>
<Product name="MyProduct1">
<Components>
<Component name="MyComponent1">
<SubComponents>
<SubComponent name="MySubComponent1"/>
<SubComponent name="MySubComponent2"/>
...more SubComponent nodes...
</SubComponents>
</Component>
...more Component nodes...
</Components>
</Product>
...more Product nodes...
</Products>
I'm trying to create a WPF app that has a ComboBox with the Product names in it. I am completely new to WPF so I don't know if I'm going about things the right way. When a Product is chosen, a second ComboBox should be populated with all the Components for that Product. And when a Component is chosen, a third ComboBox should be populated with all the SubComponents for that Component.
I don't know how to set up a dependency between ComboBoxes except to populate the dependent ComboBox inside an event handler triggered by the independent ComboBox. This seems to imply I need to be able to read the XML in C#, so I have [Serializable] classes for Products, Product, Component, and SubComponent. However, I was trying to do XML databinding in my XAML:
<Window.Resources>
<XmlDataProvider Source="Products.xml"
XPath="Products/Product"
x:Key="productsXml"/>
</Window.Resources>
I'm currently not seeing a list of Product names in my first ComboBox, whose XAML is the following:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="138,116,0,0"
Name="cbo_product" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source=productsXml, XPath=#name}"
SelectionChanged="product_SelectionChanged"/>
The Products XML should be read-only--the user won't be able to change any values in the XML from the app. I just want to read the XML data and display it in the app.
I have a few questions:
Am I going about this correctly? Having a standalone XML file from which my WPF app reads, having [Serializable] classes representing the nodes in the XML file for the purpose of extracting data from those nodes in C#, using an event handler to code dependencies between ComboBoxes, etc.
Why don't my product names, e.g., MyProduct1, show up in my ComboBox? Currently it just shows up empty.
It seems almost like having [Serializable] classes for representing my XML nodes is redundant/unnecessary since XAML already has the XmlDataProvider/XPath stuff. Is this the case?
Edit:
Updated my ComboBox XAML to the following and now I see my list of Product names in the ComboBox, thanks to decyclone's answer:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="138,116,0,0"
Name="cbo_product" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource productsXml}}"
DisplayMemberPath="#name"
SelectionChanged="product_SelectionChanged"/>
The ItemsSource property should be set to a collection of items that are to be displayed in the list which is XmlDataProvider in your case. User StaticResource to locate it since it is defined as a resource. The DisplayMemberPath property should be used to select what property should be used to display text in combobox.
Regarding your 1st (& 3ed) question, I personally like create classes than using raw XML. It gives me few benefits like
Adding wrapper properties. For example, FullName = FirstName + " " + LastName property.
I dont have to check for empty values and type safety everytime I want to access the values (which are always strings).
I can add my own behaviors as methods which can be really useful for doing little tasks.
Control the serialization method and inject custom logic in it using attributes.
The point is, is it worth it? Do you really care for these benefits? It's just like choosing between DataReader and DataSet. For readonly and displayonly purpose, use raw XML and if you are going to play with it a lot, use classes.
Okay, since I found an answer to my more specific question, I think I know the answer to this question, too. I don't need [Serializable] classes for the different nodes in my XML, because I can just use XAML and XPath to create cascading/dependent ComboBoxes:
<!-- Independent -->
<ComboBox Height="23" HorizontalAlignment="Left" Margin="138,116,0,0"
Name="cbo_product" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource productsXml}}"
DisplayMemberPath="#name"/>
<!-- Dependent -->
<ComboBox Height="23" HorizontalAlignment="Left" Margin="138,151,0,0"
Name="cbo_component" VerticalAlignment="Top" Width="201"
DataContext="{Binding ElementName=cbo_product, Path=SelectedItem}"
ItemsSource="{Binding XPath=Components/Component}"
DisplayMemberPath="#name"/>