此 XAML 代码将 XML 元素绑定到 DataGrid 的 C# 等价物是什么?
What is the C# equivalent of this XAML code binding XML elements to a DataGrid?
(Edited: My actual problem turns out to be about setting the ItemsSource correctly, not the bindings for each 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 is not allowed for this view")编辑其内容。使用 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>
1。数据网格 (WPF)
我创建了一个 xmlObjects class 来表示 "XML data" 并使用 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 通过数据源属性.
这非常快速和简单。可以用列表设置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 中的数据,而不会出现错误。
来源:
Unfortunately, the C# statement doesn't work as expected: While the data is being displayed in the DataGrid
, a System.InvalidOperationException
("EditItem is not allowed for this view") occurs once the user double-clicks a DataGrid
cell to edit its content.
该异常告诉您绑定的数据源是只读的。不允许您编辑该项目,因为 WPF 无法将您的编辑复制回源。
如果您查看 XElement.Elements()
方法,就很容易看出原因。该方法 returns 一个 IEnumerable<XElement>
。 IEnumerable<T>
界面是只读的。它只是 产生 值。它不提供修改值的原始来源的机制。所以,当然 DataGrid
不能修改元素。
但是! (您会惊呼 :) )为什么当您在 XAML 中提供完全相同的数据源时它会起作用?好吧,因为 WPF 正在努力确保您不必这样做。如果你要 运行 程序,在方便的时候中断调试器(比如,当你的 "Save XML" 按钮被点击时),你可以看看 dtaGrid.ItemsSource
属性 设置为,您会发现它 不是 IEnumerable<XElement>
的一个实例。相反,它是另一种类型,ReadOnlyObservableCollection<T>
.
WPF 已代表您将 IEnumerable<XElement>
对象的结果复制到一个新集合中,其中的元素 可以 被修改。
有趣的是,您会注意到这是 ReadOnlyObservableCollection<T>
(或更准确地说,ReadOnlyObservableCollection<object>
)。还有一个相关的类型,ObservableCollection<T>
。为什么 WPF 使用只读版本我不确定……可能是某种妥协,旨在平衡便利性 and/or 性能和混淆数据的可能性。无论如何,这就是它的作用。这很有趣,因为这意味着虽然您可以编辑网格中的单个单元格,但不能删除整行。可以在不修改集合本身的情况下更新单元格,但不能删除整行。
这一切都让我对您的代码进行了修复,这非常简单:绑定到适合您需要的集合类型。如果您想要通过 XAML 绑定时看到的行为,您可以创建集合的只读版本:
dtaGrid.ItemsSource = new ReadOnlyObservableCollection<XElement>(
new ObservableCollection<XElement>(xml.Elements("track")));
(只读集合只能用常规可写版本的实例初始化。)
另一方面,如果您希望用户也能够删除或插入行,您可以使用集合的可写版本(即不使用只读包装器即可):
dtaGrid.ItemsSource = new ObservableCollection<XElement>(xml.Elements("track"));
这解决了您提出的具体问题。我希望值得一走。 :) 但还有更多……
Since I don't know the actual XML file's structure at design time, I want to dynamically set the ItemSource at runtime in code behind (and thus be able to change the path used for binding).
您应该投入精力学习 WPF 中的 MVVM 模式。主题有很多合理的变化,我个人不一定总是严格遵守。从字面上看,它会导致大量重复工作,在您的 UI 和业务逻辑之间添加一个 "view model" 层。这种努力在非常简单的程序中通常是不值得的,其中业务逻辑模型对象可以充分用作视图模型对象。
但无论如何,MVVM 背后的基本思想是合理的,更重要的是,WPF 在设计 时特别考虑到了它。这意味着任何时候你不是 "doing it the MVVM way",你就是在与框架作斗争。这是一个陡峭的学习曲线,但我向你保证,当你到达山顶时(或者至少是半山腰的瞭望点,我认为我现在就在那里 :)),这是值得的。
在您的示例的上下文中,这意味着理想情况下您将拥有一个视图模型数据结构,该数据结构具有表示 XML 的属性(因此您可以设置 属性 并在XAML 将引用复制到 ItemsSource
),但也是一个集合类型 属性,其中包含根据 运行-time 需要配置列所需的信息。理想情况下,您 永远不会 在代码隐藏中创建 UI 对象(如 DataGridTextColumn
)。相反,您将让 WPF 完成将表示为视图模型的简单业务逻辑转换为显示所需的 UI 数据结构的艰苦工作。
将此与原始问题联系起来,您会发现您可以做出与原始修复中涉及的相同类型的决定,但在您的视图模型中,提供只读集合或可写集合,具体取决于关于您希望网格如何运行。
无论哪种方式,最终你应该以一种不需要在 UI 代码隐藏中手动设置任何这些的方式来实现你的程序,而是为你的所有实际状态使用视图模型,并使用 XAML 绑定语法将视图模型连接到 UI.
(Edited: My actual problem turns out to be about setting the ItemsSource correctly, not the bindings for each 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 is not allowed for this view")编辑其内容。使用 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>
1。数据网格 (WPF)
我创建了一个 xmlObjects class 来表示 "XML data" 并使用 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 通过数据源属性.
这非常快速和简单。可以用列表设置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 中的数据,而不会出现错误。
来源:
Unfortunately, the C# statement doesn't work as expected: While the data is being displayed in the
DataGrid
, aSystem.InvalidOperationException
("EditItem is not allowed for this view") occurs once the user double-clicks aDataGrid
cell to edit its content.
该异常告诉您绑定的数据源是只读的。不允许您编辑该项目,因为 WPF 无法将您的编辑复制回源。
如果您查看 XElement.Elements()
方法,就很容易看出原因。该方法 returns 一个 IEnumerable<XElement>
。 IEnumerable<T>
界面是只读的。它只是 产生 值。它不提供修改值的原始来源的机制。所以,当然 DataGrid
不能修改元素。
但是! (您会惊呼 :) )为什么当您在 XAML 中提供完全相同的数据源时它会起作用?好吧,因为 WPF 正在努力确保您不必这样做。如果你要 运行 程序,在方便的时候中断调试器(比如,当你的 "Save XML" 按钮被点击时),你可以看看 dtaGrid.ItemsSource
属性 设置为,您会发现它 不是 IEnumerable<XElement>
的一个实例。相反,它是另一种类型,ReadOnlyObservableCollection<T>
.
WPF 已代表您将 IEnumerable<XElement>
对象的结果复制到一个新集合中,其中的元素 可以 被修改。
有趣的是,您会注意到这是 ReadOnlyObservableCollection<T>
(或更准确地说,ReadOnlyObservableCollection<object>
)。还有一个相关的类型,ObservableCollection<T>
。为什么 WPF 使用只读版本我不确定……可能是某种妥协,旨在平衡便利性 and/or 性能和混淆数据的可能性。无论如何,这就是它的作用。这很有趣,因为这意味着虽然您可以编辑网格中的单个单元格,但不能删除整行。可以在不修改集合本身的情况下更新单元格,但不能删除整行。
这一切都让我对您的代码进行了修复,这非常简单:绑定到适合您需要的集合类型。如果您想要通过 XAML 绑定时看到的行为,您可以创建集合的只读版本:
dtaGrid.ItemsSource = new ReadOnlyObservableCollection<XElement>(
new ObservableCollection<XElement>(xml.Elements("track")));
(只读集合只能用常规可写版本的实例初始化。)
另一方面,如果您希望用户也能够删除或插入行,您可以使用集合的可写版本(即不使用只读包装器即可):
dtaGrid.ItemsSource = new ObservableCollection<XElement>(xml.Elements("track"));
这解决了您提出的具体问题。我希望值得一走。 :) 但还有更多……
Since I don't know the actual XML file's structure at design time, I want to dynamically set the ItemSource at runtime in code behind (and thus be able to change the path used for binding).
您应该投入精力学习 WPF 中的 MVVM 模式。主题有很多合理的变化,我个人不一定总是严格遵守。从字面上看,它会导致大量重复工作,在您的 UI 和业务逻辑之间添加一个 "view model" 层。这种努力在非常简单的程序中通常是不值得的,其中业务逻辑模型对象可以充分用作视图模型对象。
但无论如何,MVVM 背后的基本思想是合理的,更重要的是,WPF 在设计 时特别考虑到了它。这意味着任何时候你不是 "doing it the MVVM way",你就是在与框架作斗争。这是一个陡峭的学习曲线,但我向你保证,当你到达山顶时(或者至少是半山腰的瞭望点,我认为我现在就在那里 :)),这是值得的。
在您的示例的上下文中,这意味着理想情况下您将拥有一个视图模型数据结构,该数据结构具有表示 XML 的属性(因此您可以设置 属性 并在XAML 将引用复制到 ItemsSource
),但也是一个集合类型 属性,其中包含根据 运行-time 需要配置列所需的信息。理想情况下,您 永远不会 在代码隐藏中创建 UI 对象(如 DataGridTextColumn
)。相反,您将让 WPF 完成将表示为视图模型的简单业务逻辑转换为显示所需的 UI 数据结构的艰苦工作。
将此与原始问题联系起来,您会发现您可以做出与原始修复中涉及的相同类型的决定,但在您的视图模型中,提供只读集合或可写集合,具体取决于关于您希望网格如何运行。
无论哪种方式,最终你应该以一种不需要在 UI 代码隐藏中手动设置任何这些的方式来实现你的程序,而是为你的所有实际状态使用视图模型,并使用 XAML 绑定语法将视图模型连接到 UI.