由字典支持的 WPF 可编辑数据网格
WPF Editable DataGrid backed by dictionary
我有一个由 Observable 字典集合支持的 DataGrid。我想通过网格进行编辑,目前 UI 似乎不允许这样做。我还观察到,将 IEditableObject 接口添加到 Dictionary 时,单击单元格时会调用接口方法,但 UI 本身没有任何反应。
这是我的最小示例:
class MainWindowViewModel
{
public MainWindowViewModel() {
ItemsSource = new ObservableCollection<DataGridData> {
new DataGridData() {{"Name", "Abe"},{"Age", "50"},{"Gender", "Male"}},
new DataGridData() {{"Name", "Shelly"},{"Age", "20"},{"Gender", "Female"}
}
};
}
public ObservableCollection<DataGridData> ItemsSource { get; set; }
}
public class DataGridData : Dictionary<string, string>, IEditableObject {
public void BeginEdit(){}
public void CancelEdit(){}
public void EndEdit(){}
}
public partial class MainWindow {
public MainWindow() {
DataContext = new MainWindowViewModel();
InitializeComponent();
foreach (var col in ViewModel.ItemsSource[0].Keys) {
AddColumns(col, col);
}
}
MainWindowViewModel ViewModel {
get { return DataContext as MainWindowViewModel; }
}
void AddColumns(string id, string name)
{
FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetValue(TextBlock.PaddingProperty, new Thickness(2));
textBlock.SetValue(TextBlock.TextProperty, new Binding(string.Format("[{0}]", id)));
Binding textDecorationBinding = new Binding();
textDecorationBinding.ElementName = "DataGrid";
textDecorationBinding.Path = new PropertyPath("DataContext.TextDecoration");
textBlock.SetValue(TextBlock.TextDecorationsProperty, textDecorationBinding);
DataTemplate cellTemplate = new DataTemplate();
cellTemplate.VisualTree = textBlock;
DataGridTemplateColumn column = new DataGridTemplateColumn();
column.Header = name;
column.SortMemberPath = name;
column.CellTemplate = cellTemplate;
DataGrid.Columns.Add(column);
}
}
<Window x:Class="Datagrid.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" mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid
Name="DataGrid"
ItemsSource="{Binding ItemsSource}"
AutoGenerateColumns="False"
>
</DataGrid>
</Grid>
</Window>
使用字典是因为我在编译时对列数据一无所知。上面的 ItemsSource 只是一个例子,但我可以在 运行 时间有任意数量的 key/values。
需要进行哪些更改才能编辑单元格数据?
编辑
我从未使用过它,但我正在阅读有关使用反射来发出可以替换字典 key/values 的实际 class。除非有人提出相反的建议,否则我可能会试一试。
在我看来你的代码有点"tortuous"。事实上,我不会使用字典作为我想要处理的对象的模型。在我看来,最好使用专门的 class(这样你就可以实现 "famous" INotifyPropertyChanged
接口)。
例如你可以使用 Person class(这里是它的一个非常快速的实现):
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Genders Gender { get; set; }
}
您可以实施IEditableObject
,但这不是强制性的。
最后(但并非最不重要)如果我是你,我将在我的 XAML 中而不是在我的代码中声明 DataGrid 列(我看不出在你的情况下使用代码的正当理由;我不知道也许你需要有动态列。在这种情况下你可以阅读这个很好 article).
所以我的 ViewModel 是(您可以轻松地将 Dictionary
替换为 Person
class):
public class MainWindowViewModel
{
public MainWindowViewModel()
{
People = new ObservableCollection<Dictionary<string, string>> {
new Dictionary<string, string>() {{"Name", "Abe"}, {"Age", "50"}, {"Gender", "Male"}},
new Dictionary<string, string>() {{"Name", "Shelly"}, {"Age", "20"}, {"Gender", "Female"}}
};
}
public ObservableCollection<Dictionary<string, string>> People { get; private set; }
}
我的 XAML(如果您将 Dictionary 替换为 Person,请从 bidings 列中删除方括号):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=People}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=[Name]}" Header="Name" Width="2*" />
<DataGridTextColumn Binding="{Binding Path=[Age]}" Header="Age" Width="*" />
<DataGridTextColumn Binding="{Binding Path=[Gender]}" Header="Gender" Width="*" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Window>
我的 window 代码隐藏:
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
这样,一切正常,您可以使用 DataGrid
编辑数据。
希望这个示例能帮到你。
编辑
如果您需要使用您的方法,可能在您的 AddColumns
方法中您没有声明 CellEditingTemplate
。您的方法将变为:
private void AddColumns(string id, string name)
{
FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetValue(TextBlock.PaddingProperty, new Thickness(2));
textBlock.SetValue(TextBlock.TextProperty, new Binding(String.Format("[{0}]", id)));
FrameworkElementFactory textBox = new FrameworkElementFactory(typeof(TextBox));
textBox.SetValue(TextBox.PaddingProperty, new Thickness(2));
textBox.SetValue(TextBox.TextProperty, new Binding(String.Format("[{0}]", id)));
Binding textDecorationBinding = new Binding();
textDecorationBinding.ElementName = "DataGrid";
textDecorationBinding.Path = new PropertyPath("DataContext.TextDecoration");
textBlock.SetValue(TextBlock.TextDecorationsProperty, textDecorationBinding);
DataTemplate cellTemplate = new DataTemplate();
cellTemplate.VisualTree = textBlock;
DataTemplate cellEditingTemplate = new DataTemplate();
cellEditingTemplate.VisualTree = textBox;
DataGridTemplateColumn column = new DataGridTemplateColumn();
column.Header = name;
column.SortMemberPath = name;
column.CellTemplate = cellTemplate;
column.CellEditingTemplate = cellEditingTemplate;
DataGrid.Columns.Add(column);
}
我了解到您正在考虑使用反射来发出一个实际的 class 来替换字典 key/values。当然这是一个解决方案,但我建议考虑 ICustomTypeDescriptor or CustomTypeDescriptor.
我有一个由 Observable 字典集合支持的 DataGrid。我想通过网格进行编辑,目前 UI 似乎不允许这样做。我还观察到,将 IEditableObject 接口添加到 Dictionary 时,单击单元格时会调用接口方法,但 UI 本身没有任何反应。
这是我的最小示例:
class MainWindowViewModel
{
public MainWindowViewModel() {
ItemsSource = new ObservableCollection<DataGridData> {
new DataGridData() {{"Name", "Abe"},{"Age", "50"},{"Gender", "Male"}},
new DataGridData() {{"Name", "Shelly"},{"Age", "20"},{"Gender", "Female"}
}
};
}
public ObservableCollection<DataGridData> ItemsSource { get; set; }
}
public class DataGridData : Dictionary<string, string>, IEditableObject {
public void BeginEdit(){}
public void CancelEdit(){}
public void EndEdit(){}
}
public partial class MainWindow {
public MainWindow() {
DataContext = new MainWindowViewModel();
InitializeComponent();
foreach (var col in ViewModel.ItemsSource[0].Keys) {
AddColumns(col, col);
}
}
MainWindowViewModel ViewModel {
get { return DataContext as MainWindowViewModel; }
}
void AddColumns(string id, string name)
{
FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetValue(TextBlock.PaddingProperty, new Thickness(2));
textBlock.SetValue(TextBlock.TextProperty, new Binding(string.Format("[{0}]", id)));
Binding textDecorationBinding = new Binding();
textDecorationBinding.ElementName = "DataGrid";
textDecorationBinding.Path = new PropertyPath("DataContext.TextDecoration");
textBlock.SetValue(TextBlock.TextDecorationsProperty, textDecorationBinding);
DataTemplate cellTemplate = new DataTemplate();
cellTemplate.VisualTree = textBlock;
DataGridTemplateColumn column = new DataGridTemplateColumn();
column.Header = name;
column.SortMemberPath = name;
column.CellTemplate = cellTemplate;
DataGrid.Columns.Add(column);
}
}
<Window x:Class="Datagrid.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" mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid
Name="DataGrid"
ItemsSource="{Binding ItemsSource}"
AutoGenerateColumns="False"
>
</DataGrid>
</Grid>
</Window>
使用字典是因为我在编译时对列数据一无所知。上面的 ItemsSource 只是一个例子,但我可以在 运行 时间有任意数量的 key/values。
需要进行哪些更改才能编辑单元格数据?
编辑 我从未使用过它,但我正在阅读有关使用反射来发出可以替换字典 key/values 的实际 class。除非有人提出相反的建议,否则我可能会试一试。
在我看来你的代码有点"tortuous"。事实上,我不会使用字典作为我想要处理的对象的模型。在我看来,最好使用专门的 class(这样你就可以实现 "famous" INotifyPropertyChanged
接口)。
例如你可以使用 Person class(这里是它的一个非常快速的实现):
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Genders Gender { get; set; }
}
您可以实施IEditableObject
,但这不是强制性的。
最后(但并非最不重要)如果我是你,我将在我的 XAML 中而不是在我的代码中声明 DataGrid 列(我看不出在你的情况下使用代码的正当理由;我不知道也许你需要有动态列。在这种情况下你可以阅读这个很好 article).
所以我的 ViewModel 是(您可以轻松地将 Dictionary
替换为 Person
class):
public class MainWindowViewModel
{
public MainWindowViewModel()
{
People = new ObservableCollection<Dictionary<string, string>> {
new Dictionary<string, string>() {{"Name", "Abe"}, {"Age", "50"}, {"Gender", "Male"}},
new Dictionary<string, string>() {{"Name", "Shelly"}, {"Age", "20"}, {"Gender", "Female"}}
};
}
public ObservableCollection<Dictionary<string, string>> People { get; private set; }
}
我的 XAML(如果您将 Dictionary 替换为 Person,请从 bidings 列中删除方括号):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=People}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=[Name]}" Header="Name" Width="2*" />
<DataGridTextColumn Binding="{Binding Path=[Age]}" Header="Age" Width="*" />
<DataGridTextColumn Binding="{Binding Path=[Gender]}" Header="Gender" Width="*" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Window>
我的 window 代码隐藏:
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
这样,一切正常,您可以使用 DataGrid
编辑数据。
希望这个示例能帮到你。
编辑
如果您需要使用您的方法,可能在您的 AddColumns
方法中您没有声明 CellEditingTemplate
。您的方法将变为:
private void AddColumns(string id, string name)
{
FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetValue(TextBlock.PaddingProperty, new Thickness(2));
textBlock.SetValue(TextBlock.TextProperty, new Binding(String.Format("[{0}]", id)));
FrameworkElementFactory textBox = new FrameworkElementFactory(typeof(TextBox));
textBox.SetValue(TextBox.PaddingProperty, new Thickness(2));
textBox.SetValue(TextBox.TextProperty, new Binding(String.Format("[{0}]", id)));
Binding textDecorationBinding = new Binding();
textDecorationBinding.ElementName = "DataGrid";
textDecorationBinding.Path = new PropertyPath("DataContext.TextDecoration");
textBlock.SetValue(TextBlock.TextDecorationsProperty, textDecorationBinding);
DataTemplate cellTemplate = new DataTemplate();
cellTemplate.VisualTree = textBlock;
DataTemplate cellEditingTemplate = new DataTemplate();
cellEditingTemplate.VisualTree = textBox;
DataGridTemplateColumn column = new DataGridTemplateColumn();
column.Header = name;
column.SortMemberPath = name;
column.CellTemplate = cellTemplate;
column.CellEditingTemplate = cellEditingTemplate;
DataGrid.Columns.Add(column);
}
我了解到您正在考虑使用反射来发出一个实际的 class 来替换字典 key/values。当然这是一个解决方案,但我建议考虑 ICustomTypeDescriptor or CustomTypeDescriptor.