I have a WPF app that binds a DataGrid to an XML document using XmlDataProvider. Everything works fine except that I’m trying to “save”/write to XML file on NodeChanged event, instead of having a button or other user-triggered mechanism. Unfortunately, NodeChanged handler that I created is not being picked up. Would appreciate any help to figure out why it’s not fired.
XAML:
<Grid Name="Grid" Loaded="Grid_Loaded">
<Grid.DataContext>
<XmlDataProvider x:Name="MyData" Source="Data.xml" XPath="Nodes/Node" />
</Grid.DataContext>
<DataGrid AutoGenerateColumns="False" Name="dataGrid1" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" SelectionUnit="FullRow" SelectionMode="Single" Margin="0,0,0,50">
<DataGrid.Columns>
<DataGridTextColumn Header="Header" Binding="{Binding XPath=@Value, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
</DataGrid.Columns>
</DataGrid>
<Button Content="Save" Height="23" HorizontalAlignment="Left" Margin="416,276,0,0" Name="SaveButton" VerticalAlignment="Top" Width="75" Click="SaveButton_Click" />
</Grid>
Code-behind:
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
MyData.Source = new Uri(appPath + "/Data.xml");
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
string source = MyData.Source.LocalPath;
MyData.Document.Save(source);
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
MyData.Document.NodeChanged += new XmlNodeChangedEventHandler(Document_NodeChanged);
}
void Document_NodeChanged(object sender, XmlNodeChangedEventArgs e)
{
MessageBox.Show("Node changed");
}
}
}
XML data file:
<Nodes>
<Node Value="test1" />
<Node Value="test3" />
<Node Value="ttt" />
</Nodes>
To reproduce, you can just paste the above into a new WPF app, name XML data file Data.xml, place it into bin/Debug of the project, and add it to the project as “existing item”.
UPDATE
Thanks Robert for pointing out the issue!
Here’s an even more interesting situation. If you just add a LayoutUpdated event handler to the MainWindow to my sample code as below, you will get very weird behavior in that NodeChanged event will actually be fired if a totally unrelated MessageBox is displayed:
public partial class MainWindow : Window
{
private bool event_activated = false;
public MainWindow()
{
InitializeComponent();
string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
MyData.Source = new Uri(appPath + "/Data.xml");
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
string source = MyData.Source.LocalPath;
MyData.Document.Save(source);
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
MyData.Document.NodeChanged += new XmlNodeChangedEventHandler(Document_NodeChanged);
}
private void Document_NodeChanged(object sender, XmlNodeChangedEventArgs e)
{
MessageBox.Show("Node changed");
}
private void Window_LayoutUpdated(object sender, EventArgs e)
{
if (!event_activated)
{
event_activated = true;
MessageBox.Show("LayoutUpdated"); // <-- NodeChanged is fired with this one line of code, and not fired if you comment this line out!!
}
}
}
It doesn’t fire because the value of the node doesn’t change, only the attribute that happens to be named Value. It is stated in the documentation:
How about using DataGrid.CellEditEnding or DataGrid.RowEditEnding instead?
Update:
You are correct, the NodeChanged will fire for attributes as well. I did find the problem in your nice repro app though. If I comment out your constructor code, it all works. You already set the source in Xaml. It seems that this code fires an internally handled exception which puts the XmlDataProvider in a corrupt state.
You can see the exception in debug output. WPF seems to like to fail silently and leave you wondering.