【问题标题】:What is the C# equivalent of this XAML code binding XML elements to a DataGrid?此 XAML 代码将 XML 元素绑定到 DataGrid 的 C# 等效项是什么?
【发布时间】:2020-08-31 14:23:20
【问题描述】:

(已编辑:我的实际问题是关于正确设置 ItemsSource,而不是每个 DataGridTextColumn 的绑定!)

问题描述

我正在努力处理一个特定的数据绑定任务,我想将 XML 数据(使用 LINQ,解析为 XElement)绑定到 WPF DataGrid(不是 DataGridView),以便用户可以编辑它。我认为它可能归结为最核心的问题是:

以下 XAML 语句在 C# 代码中的等效项是什么?

<DataGrid x:Name="dtaGrid" ItemsSource="{Binding Path=Elements[track]}"/>

我想,应该是:

dtaGrid.ItemsSource = xml.Elements("track");

不幸的是,C# 语句没有按预期工作:当数据显示在 DataGrid 中时,一旦用户双击 DataGrid 单元格,就会发生 System.InvalidOperationException(“此视图不允许 EditItem”)编辑其内容。使用 XAML 变体,数据既可以显示也可以编辑而不会出错,并且更改会反映在 XML 源中。

由于我在设计时不知道实际 XML 文件的结构,我想在运行时在后面的代码中动态设置 ItemSource(从而能够更改用于绑定的路径) .


工作示例

这是一个工作示例(在 XAML 中完成 ItemsSource 绑定)。抱歉,代码引用很长,我只是认为这可能有助于在上下文中更好地澄清问题。

MainWindow.xaml(注意 DataGrid 的 ItemsSource 是如何在此处显式绑定的 - 我需要能够在运行时在后面的代码中更改此绑定):

<Window x:Class="linq_xml.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:linq_xml" mc:Ignorable="d"
        Title="MainWindow" Width="1000" Height="700" >

    <Grid Margin="8">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>

        <DataGrid x:Name="dtaGrid" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" 
                  ItemsSource="{Binding Path=Elements[track]}" AutoGenerateColumns="False"/>

        <Button x:Name="btn_Save" Grid.Row="1" Grid.Column="0" 
                Width="100" HorizontalAlignment="Left" Margin="0 8 0 0" 
                Content="Save XML" Click="Btn_Save_Click"/>
    </Grid>
</Window>

MainWindow.xaml.cs(注意未注释的ItemsSource 语句):

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Xml.Linq;

namespace linq_xml
{
    public partial class MainWindow : Window
    {
        private XElement xml;
        private readonly string filepath = @"D:\SynologyDrive\Dev\C#\linq-xml\XML-Beispiele\random.xml";

        public MainWindow()
        {
            InitializeComponent();

            xml = XElement.Load(filepath); // load xml file
            dtaGrid.DataContext = xml; // set LINQ to XML as data context

            /* If the following line is used rather than the ItemsSource being bound done in XAML, 
             * it doesn't work as expected: Once the user tries to edit a cell at runtime,
             * a System.InvalidOperationException ("EditItem is not allowed for this view") occurs. */
          // dtaGrid.ItemsSource = xml.Elements("track");

            List<DataGridTextColumn> columns = new List<DataGridTextColumn>();
            columns.Add(new DataGridTextColumn());
            columns[^1].Header = "Artist";
            columns[^1].Binding = new Binding("Element[artist_name].Value");

            columns.Add(new DataGridTextColumn());
            columns[^1].Header = "Album";
            columns[^1].Binding = new Binding("Element[album_name].Value");

            columns.Add(new DataGridTextColumn());
            columns[^1].Header = "Duration";
            columns[^1].Binding = new Binding("Element[duration].Value");

            foreach (DataGridTextColumn c in columns)
            {
                dtaGrid.Columns.Add(c);
            }                        
        }

        private void Btn_Save_Click(object sender, RoutedEventArgs e)
        {
            xml.Save(filepath);
        }
    }
}

example.xml

<?xml version="1.0" encoding="utf-8"?>
<data>
  <track>
    <id>1337</id>
    <name>Wonderful World</name>
    <duration>128</duration>
    <artist_id>13</artist_id>
    <artist_name>Trumpet</artist_name>
    <album_id>22</album_id>
    <album_name>Nice People</album_name>
  </track>
  <track>
    <id>4711</id>
    <name>Colorful World</name>
    <duration>256</duration>
    <artist_id>1</artist_id>
    <artist_name>Pink</artist_name>
    <album_id>11</album_id>
    <album_name>I like the blues</album_name>
  </track>
  <track>
    <id>0815</id>
    <name>World</name>
    <duration>512</duration>
    <artist_id>9</artist_id>
    <artist_name>CNN</artist_name>
    <album_id>33</album_id>
    <album_name>My Finger Is On The Button</album_name>
  </track>
</data>

【问题讨论】:

    标签: c# xml xaml data-binding datagrid


    【解决方案1】:

    不幸的是,C# 语句没有按预期工作:当数据显示在 DataGrid 中时,出现 System.InvalidOperationException“此视图不允许 EditItem”)一旦用户双击 DataGrid 单元格以编辑其内容。

    该异常告诉您绑定的数据源是只读的。不允许您编辑该项目,因为 WPF 无法将您的编辑复制回源中。

    如果您查看XElement.Elements() 方法,很容易看出原因。该方法返回一个IEnumerable&lt;XElement&gt;IEnumerable&lt;T&gt; 接口是只读的。它只是产生值。它没有提供修改值的原始来源的机制。所以,DataGrid 当然不能修改元素。

    但是! (你会惊呼:))为什么当你在 XAML 中提供完全相同的数据源时它会起作用?好吧,因为 WPF 正在努力确保您不必这样做。如果您要运行该程序,请在方便的时候中断调试器(例如,单击“保存 XML”按钮时),您可以查看 dtaGrid.ItemsSource 属性设置的内容,然后您将发现它不是IEnumerable&lt;XElement&gt; 的实例。相反,它是另一种类型,ReadOnlyObservableCollection&lt;T&gt;

    WPF 已代表您将 IEnumerable&lt;XElement&gt; 对象的结果复制到一个新集合中,其中元素可以被修改。

    有趣的是,您会注意到这是ReadOnlyObservableCollection&lt;T&gt;(或更准确地说是ReadOnlyObservableCollection&lt;object&gt;)。还有一个相关类型,ObservableCollection&lt;T&gt;。为什么 WPF 使用只读版本我不确定……可能是某种妥协,旨在平衡便利性和/或性能以及混乱数据的可能性。无论如何,这就是它的作用。这很有趣,因为这意味着虽然您可以编辑网格中的单个单元格,但不能删除整行。可以在不修改集合本身的情况下更新单元格,但不能删除整行。

    这一切都让我想到了您的代码的修复,这非常简单:绑定到适合您需要的集合类型。如果您希望在通过 XAML 绑定时看到确切的行为,您可以创建该集合的只读版本:

    dtaGrid.ItemsSource = new ReadOnlyObservableCollection<XElement>(
        new ObservableCollection<XElement>(xml.Elements("track")));
    

    (只读集合只能使用常规可写版本的实例进行初始化。)

    另一方面,如果您希望用户也能够删除或插入行,则可以使用集合的可写版本(即,无需只读包装器即可):

    dtaGrid.ItemsSource = new ObservableCollection<XElement>(xml.Elements("track"));
    

    这解决了您提出的具体问题。我希望值得一游。 :) 但还有更多……

    由于我在设计时不知道实际 XML 文件的结构,我想在运行时在代码后面动态设置 ItemSource(从而能够更改用于绑定的路径)。

    您应该投入精力来学习 WPF 中的 MVVM 模式。这个主题有很多合理的变化,我个人并不总是严格遵守它。从字面上看,它可能会导致大量重复工作,在您的 UI 和业务逻辑之间添加一个“视图模型”层。在业务逻辑模型对象可以充分用作视图模型对象的非常简单的程序中,这种努力通常是不值得的。

    但无论如何,MVVM 背后的基本思想是合理的,更重要的是,WPF 是设计专门考虑到​​它的。这意味着任何时候您不“以 MVVM 方式进行操作”,您都在与框架作斗争。这是一个陡峭的学习曲线,但我向你保证,当你到达顶峰(或者至少是中途的了望点,我想我现在就在那儿:)时,这是值得的。

    在您的示例上下文中,这意味着理想情况下您将拥有一个视图模型数据结构,该结构具有表示 XML 的属性(因此您可以设置属性并让 XAML 中的绑定复制对ItemsSource 的引用),还有一个集合类型属性,其中包含根据运行时需要配置列所需的信息。理想情况下,您将永远在代码隐藏中创建 UI 对象(如 DataGridTextColumn)。相反,您将让 WPF 完成将表示为视图模型的简单业务逻辑转换为显示所需的 UI 数据结构的艰苦工作。

    将此与原始问题联系起来,您可以看到您可以做出与原始修复相同的决策,但在您的视图模型中,提供只读集合或可写集合,具体取决于如何您希望网格能够正常运行。

    无论哪种方式,最终您都应该致力于以一种不需要在 UI 代码隐藏中手动设置任何内容的方式来实现您的程序,而是使用视图模型来处理您的所有实际状态,并使用 XAML 绑定将视图模型连接到 UI 的语法。

    【讨论】:

    • @peter_duniho 这是一个很棒的答案,我非常感谢!它确实拥有一切:不仅是一个解决方案,而且还有非常有用的“背景信息”,此外,它还提供了进一步阅读(MVVM 模式)以成为更好的程序员的良好提示。非常感谢您花时间以如此详细和易于理解的方式写下来 - 非常感谢!由于我刚刚开始我的 C# 之旅,所以我不知道有太多的线索 :-) 您是否偶然知道一本可以推荐的好书(英文或德文)以了解有关 MVVM 和 WPF 的更多信息?
    • @bubunetnet:对不起,我实际上不知道我会推荐任何特定的资源,更不用说特定的书了。不管是好是坏,我的 WPF 教育经历了很多试验和错误,再加上对各种博客、WPF 相关网站的大量临时阅读,当然还有 Stack Overflow 上的问答。我不会说任何特别突出的“必看”。但是那里有很多有用的信息。
    【解决方案2】:

    1。数据网格 (WPF)

    我创建了一个 xmlObjects 类来表示“XML 数据”,并使用 ItemsSource 属性来设置 DataGrid 的数据:

    1.1 xmlObject

    public class xmlObject
    {
        public int ID { get; set; }
        public string UserName { get; set; }
        public string Country { get; set; }
    
        public xmlObject(int id, string userName, string country)
        {
            ID = id;
            UserName = userName;
            Country = country;
        }
    }
    

    1.2 XAML

    <DataGrid x:Name="DataGrid1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100"
              AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="ID" Binding="{ Binding ID }"></DataGridTextColumn>
            <DataGridTextColumn Header="UserName" Binding="{ Binding UserName }"></DataGridTextColumn>
            <DataGridTextColumn Header="Country" Binding="{ Binding Country }"></DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>
    

    1.3 C 中的数据绑定

    List<xmlObject> xmlObjects = new List<xmlObject>()
    {
        new xmlObject(1, "Dennis", "Amerika"),
        new xmlObject(2, "Youssef", "Algeria"),
        new xmlObject(3, "Craig", "Ireland"),
        new xmlObject(4, "Ron", "Russia")
    };
    
    DataGrid1.ItemsSource = xmlObjects;
    

    2。 DataGridView(Windows 窗体)

    2.1 通过 DataSource 属性。

    这是相当快速和容易的。您可以使用列表设置 datagridview 控件的 DataSource 属性:

    List<XMLObject> xmlObjects = new List<XMLObject>()
    {
        new XMLObject(1, "Dennis", "Amerika"),
        new XMLObject(2, "Youssef", "Algeria"),
        new XMLObject(3, "Craig", "Ireland"),
        new XMLObject(4, "Ron", "Russia")
    };
    
    dataGridView1.DataSource = xmlObjectsList;
    

    2.2 通过 BindingSource 实例。

    private BindingSource xmlObjectsBindingSource = new BindingSource();
    
    List<XMLObject> xmlObjects = new List<XMLObject>()
    {
        new XMLObject(1, "Dennis", "Amerika"),
        new XMLObject(2, "Youssef", "Algeria"),
        new XMLObject(3, "Craig", "Ireland"),
        new XMLObject(4, "Ron", "Russia")
    };
    
    xmlObjectsBindingSource.DataSource = xmlObjects;
    
    dataGridView1.DataSource = xmlObjectsBindingSource;
    

    两种方式都会自动生成列,您可以编辑存储在datagridview中的数据,而不会出错。

    来源:

    1. https://docs.microsoft.com/en-us/dotnet/desktop-wpf/data/data-binding-overview;
    2. https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.bindingsource.datasource?view=netcore-3.1;
    3. https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridview.datasource?view=netcore-3.1;

    【讨论】:

    • 非常感谢您花时间回答这个问题!但是,除非我弄错了,否则您建议使用 DataGridView,而不是 DataGrid。由于我不想使用 Windows 窗体,而是使用 WPF,这似乎不符合我的需求。我实际上真的试图了解如何在 C# 代码中而不是在 XAML 中将 XDocument 绑定到 DataGrid(它在哪里按预期工作),因为我需要在运行时根据提供的实际 XML 文档动态调整绑定(目前尚不清楚会有多少列以及它们的名称是什么),所以(至少我认为)我在 XAML 中无法做到这一点。
    • 另外:XMLObject 是什么类? Visual Studio 2019 无法识别它并建议“XObject”(这是抽象的,因此您的代码示例不起作用)或在包 IKVM 中使用 javax.xml.crypto.desig?!那么,C# 中的 Java 类?
    • @bubunetnet 我使用了一个名为 xmlObject 的自定义类作为导入 xml 数据的示例。
    • @bubunetnet 我为数据网格 (wpf) 添加了一个示例。
    • 再次感谢!不幸的是,这也不能解决我的问题,因为您在 XAML 而不是 C# 中进行绑定。但是 - 玩弄我的代码,我刚刚发现了我的实际问题:如何在 C# 代码而不是 XAML 中正确设置 ItemsSource。我将相应地更新我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    • 2012-07-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多