在 WPF 中,当 xml 文档在运行时发生更改时,如何刷新 xmlDataProvider?

发布于 2024-12-17 19:44:32 字数 2227 浏览 0 评论 0原文

我正在尝试在 Visual Studio WPF 中制作图像查看器/相册创建器。每个相册的图像路径存储在一个 xml 文档中,我绑定到该文档以在列表框中显示每个相册中的图像。 问题是当我在运行时添加图像或相册并将其写入 xml 文档时。我似乎无法更新 xml 文档的绑定,因此它们也会显示新的图像和相册。 在 XmlDataProvider 上调用 Refresh() 不会改变任何内容。 我不想重做 XmlDataProvider 的绑定,只需让它再次从同一源读取即可。

XAML:

...
<Grid.DataContext>
            <XmlDataProvider x:Name="Images" Source="Data/images.xml" XPath="/albums/album[@name='no album']/image" />
</Grid.DataContext>
...
<Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Bottom" Padding="0" Margin="0,0,0,5" Content="{x:Static resx:Resource.AddImageLabel}"/>
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Name="newImagePath" Margin="0" />
<Button Grid.Row="1" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Bottom" Name="newImagePathButton" Content="{x:Static resx:Resource.BrowseImageButton}" Click="newImagePathButton_Click" />
...
<ListBox Grid.Column="0" Grid.ColumnSpan="4" Grid.Row="3" HorizontalAlignment="Stretch" Name="thumbnailList" VerticalAlignment="Bottom" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding BindingGroupName=Images}" SelectedIndex="0" Background="#FFE0E0E0" Height="110">
...

代码隐藏:

private void newImagePathButton_Click(object sender, RoutedEventArgs e)
{
    string imagePath = newImagePath.Text;

    albumCreator.addImage(imagePath, null);

    //Reset import image elements to default
    newImagePath.Text = "";

    //Refresh thumbnail listbox
    Images.Refresh();

    Console.WriteLine("Image added!");
}

public void addImage(string source, XmlElement parent)
{
    if (parent == null)
    {
        //Use default album
        parent = (XmlElement)root.FirstChild;
    }

    //Create image element with source element within
    XmlElement newImage = xmlDoc.CreateElement(null, "image", null);
    XmlElement newSource = xmlDoc.CreateElement(null, "source", null);
    newSource.InnerText = source;
    newImage.AppendChild(newSource);

    //Add image element to parent
    parent.AppendChild(newImage);

    xmlDoc.Save(xmlFile);

}

非常感谢您的帮助!

I am trying to make a image viewer/album creator in visual studio, wpf. The image paths for each album is stored in an xml document which i bind to to show the images from each album in a listbox.
The problem is when i add a image or an album at runtime and write it to the xml document. I can't seem to make the bindings to the xml document update so they show the new images and albums aswell.
Calling Refresh() on the XmlDataProvider doesn't change anything.
I don't wish to redo the binding of the XmlDataProvider, just make it read from the same source again.

XAML:

...
<Grid.DataContext>
            <XmlDataProvider x:Name="Images" Source="Data/images.xml" XPath="/albums/album[@name='no album']/image" />
</Grid.DataContext>
...
<Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Bottom" Padding="0" Margin="0,0,0,5" Content="{x:Static resx:Resource.AddImageLabel}"/>
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Name="newImagePath" Margin="0" />
<Button Grid.Row="1" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Bottom" Name="newImagePathButton" Content="{x:Static resx:Resource.BrowseImageButton}" Click="newImagePathButton_Click" />
...
<ListBox Grid.Column="0" Grid.ColumnSpan="4" Grid.Row="3" HorizontalAlignment="Stretch" Name="thumbnailList" VerticalAlignment="Bottom" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding BindingGroupName=Images}" SelectedIndex="0" Background="#FFE0E0E0" Height="110">
...

Code behind:

private void newImagePathButton_Click(object sender, RoutedEventArgs e)
{
    string imagePath = newImagePath.Text;

    albumCreator.addImage(imagePath, null);

    //Reset import image elements to default
    newImagePath.Text = "";

    //Refresh thumbnail listbox
    Images.Refresh();

    Console.WriteLine("Image added!");
}

public void addImage(string source, XmlElement parent)
{
    if (parent == null)
    {
        //Use default album
        parent = (XmlElement)root.FirstChild;
    }

    //Create image element with source element within
    XmlElement newImage = xmlDoc.CreateElement(null, "image", null);
    XmlElement newSource = xmlDoc.CreateElement(null, "source", null);
    newSource.InnerText = source;
    newImage.AppendChild(newSource);

    //Add image element to parent
    parent.AppendChild(newImage);

    xmlDoc.Save(xmlFile);

}

Thank you very much for any help!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不爱素颜 2024-12-24 19:44:32

在这种情况下,我相信正确的方法是使用 ObservableCollection 并将其绑定到 ListViewItemsSource 属性。因此,只需使用对象即可,无需使用 XML 文件。

编辑:

整个概念是与Refresh()一起使用的。下一个示例是有效的。检查文档保存后是否调用了Refresh()

<ListView x:Name="uiList" ItemsSource="{Binding}">
    <ListView.DataContext>
        <XmlDataProvider x:Name="DataSource" Source="c:\XMLFile.xml" XPath="/root/item"  />
    </ListView.DataContext>
    <ListView.ItemTemplate>
        <DataTemplate>
            <Border Width="40" Height="40" Background="Gray">
                <Label Content="{Binding Attributes[0]}" />
            </Border>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

...

public MainWindow()
{
    InitializeComponent();
    uiList.SelectionChanged += new SelectionChangedEventHandler(uiList_SelectionChanged);
}

void uiList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string sFile = @"c:\XMLFile.xml";
    XDocument oDoc = XDocument.Load(sFile);
    oDoc.Root.Add(
        new XElement("item", new XAttribute("name", "test3"))
    );
    oDoc.Save(sFile);

    XmlDataProvider oProv = uiList.DataContext as XmlDataProvider;
    oProv.Refresh();
}

The right way in this situation I beleive is to use ObservableCollection and bind it to ItemsSource property of your ListView. So, just play with objects and no tricks with XML files.

Edit:

Entire concept is work with Refresh(). Next sample is works. Check if Refresh() call is made after document saving.

<ListView x:Name="uiList" ItemsSource="{Binding}">
    <ListView.DataContext>
        <XmlDataProvider x:Name="DataSource" Source="c:\XMLFile.xml" XPath="/root/item"  />
    </ListView.DataContext>
    <ListView.ItemTemplate>
        <DataTemplate>
            <Border Width="40" Height="40" Background="Gray">
                <Label Content="{Binding Attributes[0]}" />
            </Border>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

...

public MainWindow()
{
    InitializeComponent();
    uiList.SelectionChanged += new SelectionChangedEventHandler(uiList_SelectionChanged);
}

void uiList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string sFile = @"c:\XMLFile.xml";
    XDocument oDoc = XDocument.Load(sFile);
    oDoc.Root.Add(
        new XElement("item", new XAttribute("name", "test3"))
    );
    oDoc.Save(sFile);

    XmlDataProvider oProv = uiList.DataContext as XmlDataProvider;
    oProv.Refresh();
}
留蓝 2024-12-24 19:44:32

可能的问题和解决方案#1

您是否也在 Application.Resources 块中声明了此 XmlDataProvider 资源?

如果这样做,XAML UI ListBox 元素“thumbnailList”将引用网格面板的 XmlDataProvider 实例。我猜测,由于我看不到 Window CS 文件构造函数中的代码,因此当您在 XmlDataProvider 中寻址 XmlDataProvider 时,您会引用 XmlDataProvider 的应用程序级实例,如

XmlDataProvider xmlDataProvider = Application.Current.FindResource("图像”)作为 XmlDataProvider;

XmlDocument xDoc = xmlDataProvider.Document;

如果是这种情况,请从 Grid 元素中删除 XmlDataProvider 资源。现在,当您的代码隐藏更新 XML 文件时,UI 将自动更新。


可能的问题和解决方案#2

我从您的 addImage() 方法中看到您引用了名为“xDoc”的实例变量。

另一种可能性是您在 Window 构造函数中创建一个 NEW XmlDocument,而不是引用 XAML 创建的 XmlDocument 对象。如果是这样,请获取当前 XmlDocument 的实例,而不是创建新实例。确保声明资源
在应用程序级别,并从网格元素 XmlDataProvider 中删除资源声明

XmlDataProvider xmlDataProvider = Application.Current.FindResource("Images") as XmlDataProvider;

或者在 Grid 元素处引用资源(您需要向 Grid 添加名称),并且不要在 Application.Resources 块中声明资源

XmlDataProvider xmlDataProvider = grid.FindResource("Images") as XmlDataProvider;

XmlDocument xDoc = xmlDataProvider.Document;

现在,当您的代码隐藏更新 XML 文件时,UI 将自动更新。


结论

如果您在代码隐藏

XmlDataProvider xmlDataProvider 中声明这两个类实例变量;

XmlDataProvider gridXmlDataProvider;

并将此代码放在您的 Window 构造函数中

xmlDataProvider = Application.Current.FindResource("Images") as XmlDataProvider;

gridXmlDataProvider = grid.FindResource("Images") as XmlDataProvider;

在添加节点并保存 XML 文档更改时,在 addImage 事件处理程序中停止。假设您最初从 xmlDataProvider 加载 oDoc,如上所示。在调试模式下运行并打开“监视”窗口并检查 xmlDataProvider 和 gridXmlDataProvider 的内容。打开每个文档的 Document 属性,并比较 InnerXml 属性的内容。在 xmlDataProvider(应用程序级别的资源)上,您会发现
反映了 XML 文件的最新节点更改。 gridXmlDataProvider(XAML UI 元素的资源)则不然。 InnerXml 属性显示没有变化。没有变化,不需要更新 UI。

仅供参考,我遇到了问题#1 - 在 Application.Resources 块和 Window.Resources 块中声明的相同 XmlDataProvider 资源。我从后一个声明开始,在通过 Application.Current.FindResource("name") 引用 XmlDataProvider 实例后遇到异常错误,将声明复制并粘贴到 Application.Resources 块中,留下在Window.Resources 块,造成两个引用问题。 XAML UI 使用 Window 数据上下文,而我的代码隐藏使用应用程序数据上下文更新了 XML 文件!每当我在 XML 文件中添加或删除节点时,UI (ListBox) 都不会更新!

BTW XmlDataProvider 已经实现了自己的通知机制,不需要使用 ObservableCollection。 oProv.Refresh() 不会导致绑定 UI 的刷新,因为它可能指向 XmlDataProvider(Grid 元素的)的不同实例,并且就该实例而言,没有发生任何更改。

这个答案对你来说可能来得太晚了,但我刚刚发现了这个东西,我想分享一下。


Possible Problem and Solution #1

Do you have this XmlDataProvider resource declared in the Application.Resources block as well?

If you do, the XAML UI ListBox element "thumbnailList" refers to the Grid panel's instance of the XmlDataProvider. I'm guessing, since I can't see the code in your Window CS file constructor, that you refer to the Application-level instance of the XmlDataProvider when you address the XmlDataProvider there as in

XmlDataProvider xmlDataProvider = Application.Current.FindResource("Images") as XmlDataProvider;

XmlDocument xDoc = xmlDataProvider.Document;

If this is the case, remove the XmlDataProvider resource from the Grid element. Now when your code-behind updates the XML file the UI will automatically update.


Possible Problem and Solution #2

I see from your addImage() method that you refer to an instance variable named "xDoc".

The other possibility is that you are creating a NEW XmlDocument in your Window constructor, instead of referencing the XAML created XmlDocument object. If so, get the instance of the current XmlDocument instead of creating a new instance. Make sure to declare the resource
at the Application level and remove the resource declaration from the Grid element

XmlDataProvider xmlDataProvider = Application.Current.FindResource("Images") as XmlDataProvider;

Or reference the resource at the Grid element (you will need to add a Name to the Grid) and do not declare the resource in the Application.Resources block

XmlDataProvider xmlDataProvider = grid.FindResource("Images") as XmlDataProvider;

XmlDocument xDoc = xmlDataProvider.Document;

Now when your code-behind updates the XML file the UI will automatically update.


Conclusions

If you declare these two class instance variables in your code-behind

XmlDataProvider xmlDataProvider;

XmlDataProvider gridXmlDataProvider;

and have this code in your Window constructor

xmlDataProvider = Application.Current.FindResource("Images") as XmlDataProvider;

gridXmlDataProvider = grid.FindResource("Images") as XmlDataProvider;

Put a stop in the addImage event handler right you add a Node and save the XML Document changes. Assuming you originally loaded oDoc from xmlDataProvider as shown above. Run in Debug Mode and open a Watch window and inspect the contents of xmlDataProvider and gridXmlDataProvider. Open the Document property on each, and compare the contents of the InnerXml property. On the xmlDataProvider (the Application-level's resource) you will find
the latest node changes to the XML file are reflected. Not so on the gridXmlDataProvider (the XAML UI element's resource). InnerXml property shows no changes. No changes, not need to update the UI.

FYI I had Problem #1 - the same XmlDataProvider resource declared in the Application.Resources block AND in the Window.Resources block. I started out with the latter declaration, ran into an exception error after I referred to the XmlDataProvider instance via Application.Current.FindResource("name"), copy and pasted the declaration into the Application.Resources block, LEAVING the resource declared in the Window.Resources block, creating a TWO REFERENCE problem. The XAML UI used the Window data context, while my code-behind updated the XML file with the Application data context! Whenever I added or removed nodes from the XML file the UI (ListBox) did not get updated!

BTW XmlDataProvider already implements its own notification mechanism, no need use an ObservableCollection. oProv.Refresh() does not cause the refresh of the bound UI because it may point to a different instance of the XmlDataProvider (the Grid element's), and as far as that instance is concerned, no changes have happened.

This answer probably comes too late for you, but I just found this stuff out, thought I share it.

尸血腥色 2024-12-24 19:44:32

在 XML 中

    <XmlDataProvider Source="XMLFile1.xml" XPath="Data"  DataChanged="XmlDataProvider_DataChanged"></XmlDataProvider>
</Window.DataContext>

在计算机科学中

  private void XmlDataProvider_DataChanged(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke((Action)(() =>
            {
                XmlDataProvider oProv = this.DataContext as XmlDataProvider;
                oProv.Refresh();
            }));
        }

in xml

    <XmlDataProvider Source="XMLFile1.xml" XPath="Data"  DataChanged="XmlDataProvider_DataChanged"></XmlDataProvider>
</Window.DataContext>

in cs

  private void XmlDataProvider_DataChanged(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke((Action)(() =>
            {
                XmlDataProvider oProv = this.DataContext as XmlDataProvider;
                oProv.Refresh();
            }));
        }
青柠芒果 2024-12-24 19:44:32

取自..http://www.infosysblogs.com/microsoft/2008/03 /wpf_updating_xmldataprovider_w.html

我在下面使用过;

        XmlDataProvider xdp = this.Resources["userDataXmlDataProvider1"]  as XmlDataProvider;
        xdp.Source = new Uri(MyPath + @"\Projects.xml");

        FileSystemWatcher watcher = new FileSystemWatcher();
        //set the path of the XML file appropriately as per your requirements
        watcher.Path = MyPath;

        //name of the file i am watching
        watcher.Filter = "Projects.xml";

        //watch for file changed events so that we can refresh the data provider
        watcher.Changed += new FileSystemEventHandler(file_Changed);

        //finally, don't forget to enable watching, else the events won't fire           
        watcher.EnableRaisingEvents = true;

    void file_Changed(object sender, FileSystemEventArgs e)
    {
        XmlDataProvider xdp = this.Resources["userDataXmlDataProvider1"] as XmlDataProvider;
        xdp.Refresh();

    }

在我的用户控件中;

    <UserControl.Resources>
    <XmlDataProvider x:Key="userDataXmlDataProvider1" XPath="Projects/Project" IsAsynchronous="True" />
    <CollectionViewSource x:Key="userDataCollectionViewSource1" Source="{StaticResource userDataXmlDataProvider1}"/>
    </UserControl.Resources>

    <Grid DataContext="{StaticResource userDataXmlDataProvider1}">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="3*"/>
    </Grid.RowDefinitions>
    <ListBox x:Name="listBox1" Grid.Row="1"
             ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" Margin="8,0,8,0">
                    <Label Content="{Binding XPath=ProjectName}" Width="100" Margin="5" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

</Grid>

Taken from..http://www.infosysblogs.com/microsoft/2008/03/wpf_updating_xmldataprovider_w.html

Ive used below;

        XmlDataProvider xdp = this.Resources["userDataXmlDataProvider1"]  as XmlDataProvider;
        xdp.Source = new Uri(MyPath + @"\Projects.xml");

        FileSystemWatcher watcher = new FileSystemWatcher();
        //set the path of the XML file appropriately as per your requirements
        watcher.Path = MyPath;

        //name of the file i am watching
        watcher.Filter = "Projects.xml";

        //watch for file changed events so that we can refresh the data provider
        watcher.Changed += new FileSystemEventHandler(file_Changed);

        //finally, don't forget to enable watching, else the events won't fire           
        watcher.EnableRaisingEvents = true;

and

    void file_Changed(object sender, FileSystemEventArgs e)
    {
        XmlDataProvider xdp = this.Resources["userDataXmlDataProvider1"] as XmlDataProvider;
        xdp.Refresh();

    }

and in my UserControl;

    <UserControl.Resources>
    <XmlDataProvider x:Key="userDataXmlDataProvider1" XPath="Projects/Project" IsAsynchronous="True" />
    <CollectionViewSource x:Key="userDataCollectionViewSource1" Source="{StaticResource userDataXmlDataProvider1}"/>
    </UserControl.Resources>

    <Grid DataContext="{StaticResource userDataXmlDataProvider1}">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="3*"/>
    </Grid.RowDefinitions>
    <ListBox x:Name="listBox1" Grid.Row="1"
             ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" Margin="8,0,8,0">
                    <Label Content="{Binding XPath=ProjectName}" Width="100" Margin="5" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

</Grid>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文