Reputation: 669
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>
Upvotes: 2
Views: 2649
Reputation: 669
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).
Upvotes: 0
Reputation: 12993
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(...);
Upvotes: 2