WPF DataGrid 一键组合框显示按枚举名称排序的枚举值
WPF DataGrid with a one click ComboBox showing enumeration values sorted by enumeration names
要求
我想将带有组合框的列添加到满足以下要求的 WPF DataGrid:
- ComboBox中显示的值应该是枚举常量的名称
- ComboBox 中的条目应按枚举常量的名称排序
- 底层对象中的属性类型应该是枚举,而不是字符串
- 应该减少点击次数。当我使用 DataGridComboBoxColumn 时,我需要大约 4 次点击才能更改一个值。
- 我实际上喜欢代码隐藏解决方案,尽管基于 XAML 的解决方案也很好。
- 在.NET 5 WPF
下应该运行
示例应用程序
应用程序使用 DataGridComboBoxColumn 中提供的代码。它有效,但有 2 个问题:
详细信息下拉列表按字母顺序列出条目。在我的实际应用程序中,我有更多条目,如果不对它们进行排序,很难找到正确的条目。
需要单击 4 次鼠标才能更改 ComboBox 值。
代码
XAML:
<Window x:Class="SortedComboBoxDataGrid.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:SortedComboBoxDataGrid"
xmlns:core="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.Resources>
<CollectionViewSource x:Key="SamplesViewSource" CollectionViewType="ListCollectionView"/>
<ObjectDataProvider x:Key="myEnum" MethodName="GetValues" ObjectType="{x:Type core:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type Type="local:DetailEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<DataGrid x:Name="MainDataGrid" DataContext="{StaticResource SamplesViewSource}" ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=SomeText}" Header="SomeText"/>
<DataGridComboBoxColumn Header="Detail" SelectedItemBinding="{Binding Detail}"
ItemsSource="{Binding Source={StaticResource myEnum}}"/>
</DataGrid.Columns>
</DataGrid>
</Window>
枚举详细枚举:
namespace SortedComboBoxDataGrid {
public enum DetailEnum {
No,
Some,
Many,
All
}
样本class:
public class Sample {
public string SomeText { get; set; }
public DetailEnum Detail { get; set; }
public Sample(string someText, DetailEnum detail) {
SomeText = someText;
Detail = detail;
}
}
}
Window 后面的代码:
using System.Collections.Generic;
using System.Windows;
namespace SortedComboBoxDataGrid {
public partial class MainWindow: Window {
public MainWindow() {
InitializeComponent();
var samples = new List<Sample>() {
new Sample("first", DetailEnum.All),
new Sample("second", DetailEnum.Many),
new Sample("any", DetailEnum.Some),
new Sample("last", DetailEnum.No),
};
var samplesViewSource = ((System.Windows.Data.CollectionViewSource)(FindResource("SamplesViewSource")));
samplesViewSource.Source = samples;
}
}
}
我已经试过了
我试过了Displaying sorted enum values in a ComboBox。这很好地对枚举进行了排序,但这样做会将枚举值转换为字符串,然后对这些字符串进行排序。如果用户点击不同的条目,网格 returns 一个字符串而不是一个枚举值。
我尝试了在 Whosebug 上找到的各种解决方案来减少技巧的数量,但无法使一个解决方案与排序的 (!) 枚举一起正常工作。
我想知道是否为 ComboBox 使用具有枚举值和枚举名称作为其属性的 class 实例列表而不是列表会更好?
public class DetailEnumClass {
public DetailEnum EnumValue { get; set; }
public string EnumName { get; set; }
}
请在将此问题标记为重复之前阅读此内容
我知道在 Whosebug 上已经有很多关于我在这里提到的一个或另一个问题的答案。但是,我无法提出一个涵盖所有要求的可行解决方案。因此,如果您找到了提供完整问题的完整代码的答案,请仅将此问题标记为重复。谢谢。
您可以将 DataGridTemplateColumn
与 ComboBox
结合使用,后者绑定到您在代码隐藏中自己创建的已排序 IEnumerable<DetailEnum>
:
public partial class MainWindow : Window
{
public MainWindow()
{
Resources.Add("enums",
Enum.GetValues(typeof(DetailEnum)).Cast<DetailEnum>().OrderBy(x => x.ToString()));
InitializeComponent();
...
}
}
XAML:
<DataGrid x:Name="MainDataGrid" AutoGenerateColumns="False">
<DataGrid.Resources>
<DataTemplate x:Key="dt">
<ComboBox ItemsSource="{StaticResource enums}"
SelectedItem="{Binding Detail, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=SomeText}" Header="SomeText"/>
<DataGridTemplateColumn Header="Detail" CellTemplate="{StaticResource dt}" CellEditingTemplate="{StaticResource dt}" />
</DataGrid.Columns>
</DataGrid>
当我使用我接受的答案时,我 运行 陷入了一个有趣的问题。以下代码工作正常:
<Window.Resources>
<CollectionViewSource x:Key="SamplesViewSource"
CollectionViewType="ListCollectionView"/>
</Window.Resources>
但我不得不合并一些其他资源。第一步是像这样更改上面的代码:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
<CollectionViewSource x:Key="SamplesViewSource"
CollectionViewType="ListCollectionView"/>
</ResourceDictionary>
</Window.Resources>
之后我无法再运行 应用程序。我收到错误消息“找不到名为 'enums' 的资源。资源名称区分大小写。”
我花了 2 天时间才弄清楚这里发生了什么。通常,不需要将 <ResourceDictionary>
添加到 <Window.Resources>
,XAML 会在后台为您完成。为了能够使用MergedDictionaries
,你必须自己动手。这改变了 XAML 的行为方式。也许我应该在这里补充一下,因为这种问题,我讨厌 XAML 超过 10 年了。
我猜 <ResourceDictionary>
XAML 用新的删除了自动创建的 ResourceDictionary
并且旧的内容丢失了,即添加的 enums
在调用 InitializeComponent()
.
之前到 Windows.Resources
我认为没什么大不了的,并尝试在 InitializeComponent()
之后添加 enums
,因为 enums
只有在我使用以下行将数据填充到 DataGrid.DataContext
后才需要:
samplesViewSource.Source = samples;
但我仍然收到相同的错误消息。我的猜测是 DataGrid.Resources
中定义的 ComboBox 的 DataTemplate
在 DataGrid
被创建时被执行。此时enums
还没有添加
我可以通过使用 dynamic 而不是 static 资源来消除错误:
<ComboBox ItemsSource="{DynamicResource enums}"
在这一点上,我对 XAML 给出的所有问题感到非常恼火,并决定不将 Resources
用于 ComboBox.ItemsSource
,而是将 DataContext
.
当然,这又带来了另一个问题。我在文档中找不到它,但是通过调试器我发现 ComboBox.DataContext
是一个 Sample
而不是从 DataGrid
继承的 DataContext
,这是有道理的.每行需要访问 DataGrid.ItemsSource
.
中的不同项目
又过了一天,我想出了如何从 Combobox DataTemplate
:
访问 DataGrid.DataContext
<DataTemplate x:Key="dt">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}},
Path=DataContext.DetailEnums}"
SelectedItem="{Binding Detail, UpdateSourceTrigger=PropertyChanged}"
</DataTemplate>
这里是完整的代码,告诉你如何在 DataGrid
中使用 ComboBoxes
而不使用 Resources
来保存数据,如果你像我一样并尽量减少问题XAML 创建:
<Window x:Class="SortedComboBoxDataGrid.Window2"
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" SizeToContent="WidthAndHeight">
<DataGrid x:Name="MainDataGrid" ItemsSource="{Binding SamplesViewSource}" AutoGenerateColumns="False">
<DataGrid.Resources>
<DataTemplate x:Key="dt">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}},
Path=DataContext.DetailEnums}"
SelectedItem="{Binding Detail, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=SomeText}" Header="SomeText"/>
<DataGridTemplateColumn Header="Detail" CellTemplate="{StaticResource dt}" CellEditingTemplate="{StaticResource dt}" />
</DataGrid.Columns>
</DataGrid>
</Window>
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace SortedComboBoxDataGrid {
public partial class Window2: Window {
public record MainDataGridDataContext(ListCollectionView SamplesViewSource, DetailEnum[] DetailEnums);
public Window2() {
InitializeComponent();
var samples = new List<Sample>() {
new Sample("first", DetailEnum.All),
new Sample("second", DetailEnum.Many),
new Sample("any", DetailEnum.Some),
new Sample("last", DetailEnum.No),
};
var samplesViewSource = new ListCollectionView(samples);
var detailEnums = Enum.GetValues(typeof(DetailEnum)).Cast<DetailEnum>().OrderBy(x => x.ToString()).ToArray(); ;
MainDataGrid.DataContext = new MainDataGridDataContext(samplesViewSource, detailEnums);
}
}
}
要求
我想将带有组合框的列添加到满足以下要求的 WPF DataGrid:
- ComboBox中显示的值应该是枚举常量的名称
- ComboBox 中的条目应按枚举常量的名称排序
- 底层对象中的属性类型应该是枚举,而不是字符串
- 应该减少点击次数。当我使用 DataGridComboBoxColumn 时,我需要大约 4 次点击才能更改一个值。
- 我实际上喜欢代码隐藏解决方案,尽管基于 XAML 的解决方案也很好。
- 在.NET 5 WPF 下应该运行
示例应用程序
应用程序使用 DataGridComboBoxColumn 中提供的代码。它有效,但有 2 个问题:
详细信息下拉列表按字母顺序列出条目。在我的实际应用程序中,我有更多条目,如果不对它们进行排序,很难找到正确的条目。
需要单击 4 次鼠标才能更改 ComboBox 值。
代码
XAML:
<Window x:Class="SortedComboBoxDataGrid.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:SortedComboBoxDataGrid"
xmlns:core="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.Resources>
<CollectionViewSource x:Key="SamplesViewSource" CollectionViewType="ListCollectionView"/>
<ObjectDataProvider x:Key="myEnum" MethodName="GetValues" ObjectType="{x:Type core:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type Type="local:DetailEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<DataGrid x:Name="MainDataGrid" DataContext="{StaticResource SamplesViewSource}" ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=SomeText}" Header="SomeText"/>
<DataGridComboBoxColumn Header="Detail" SelectedItemBinding="{Binding Detail}"
ItemsSource="{Binding Source={StaticResource myEnum}}"/>
</DataGrid.Columns>
</DataGrid>
</Window>
枚举详细枚举:
namespace SortedComboBoxDataGrid {
public enum DetailEnum {
No,
Some,
Many,
All
}
样本class:
public class Sample {
public string SomeText { get; set; }
public DetailEnum Detail { get; set; }
public Sample(string someText, DetailEnum detail) {
SomeText = someText;
Detail = detail;
}
}
}
Window 后面的代码:
using System.Collections.Generic;
using System.Windows;
namespace SortedComboBoxDataGrid {
public partial class MainWindow: Window {
public MainWindow() {
InitializeComponent();
var samples = new List<Sample>() {
new Sample("first", DetailEnum.All),
new Sample("second", DetailEnum.Many),
new Sample("any", DetailEnum.Some),
new Sample("last", DetailEnum.No),
};
var samplesViewSource = ((System.Windows.Data.CollectionViewSource)(FindResource("SamplesViewSource")));
samplesViewSource.Source = samples;
}
}
}
我已经试过了
我试过了Displaying sorted enum values in a ComboBox。这很好地对枚举进行了排序,但这样做会将枚举值转换为字符串,然后对这些字符串进行排序。如果用户点击不同的条目,网格 returns 一个字符串而不是一个枚举值。
我尝试了在 Whosebug 上找到的各种解决方案来减少技巧的数量,但无法使一个解决方案与排序的 (!) 枚举一起正常工作。
我想知道是否为 ComboBox 使用具有枚举值和枚举名称作为其属性的 class 实例列表而不是列表会更好?
public class DetailEnumClass {
public DetailEnum EnumValue { get; set; }
public string EnumName { get; set; }
}
请在将此问题标记为重复之前阅读此内容
我知道在 Whosebug 上已经有很多关于我在这里提到的一个或另一个问题的答案。但是,我无法提出一个涵盖所有要求的可行解决方案。因此,如果您找到了提供完整问题的完整代码的答案,请仅将此问题标记为重复。谢谢。
您可以将 DataGridTemplateColumn
与 ComboBox
结合使用,后者绑定到您在代码隐藏中自己创建的已排序 IEnumerable<DetailEnum>
:
public partial class MainWindow : Window
{
public MainWindow()
{
Resources.Add("enums",
Enum.GetValues(typeof(DetailEnum)).Cast<DetailEnum>().OrderBy(x => x.ToString()));
InitializeComponent();
...
}
}
XAML:
<DataGrid x:Name="MainDataGrid" AutoGenerateColumns="False">
<DataGrid.Resources>
<DataTemplate x:Key="dt">
<ComboBox ItemsSource="{StaticResource enums}"
SelectedItem="{Binding Detail, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=SomeText}" Header="SomeText"/>
<DataGridTemplateColumn Header="Detail" CellTemplate="{StaticResource dt}" CellEditingTemplate="{StaticResource dt}" />
</DataGrid.Columns>
</DataGrid>
当我使用我接受的答案时,我 运行 陷入了一个有趣的问题。以下代码工作正常:
<Window.Resources>
<CollectionViewSource x:Key="SamplesViewSource"
CollectionViewType="ListCollectionView"/>
</Window.Resources>
但我不得不合并一些其他资源。第一步是像这样更改上面的代码:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
<CollectionViewSource x:Key="SamplesViewSource"
CollectionViewType="ListCollectionView"/>
</ResourceDictionary>
</Window.Resources>
之后我无法再运行 应用程序。我收到错误消息“找不到名为 'enums' 的资源。资源名称区分大小写。”
我花了 2 天时间才弄清楚这里发生了什么。通常,不需要将 <ResourceDictionary>
添加到 <Window.Resources>
,XAML 会在后台为您完成。为了能够使用MergedDictionaries
,你必须自己动手。这改变了 XAML 的行为方式。也许我应该在这里补充一下,因为这种问题,我讨厌 XAML 超过 10 年了。
我猜 <ResourceDictionary>
XAML 用新的删除了自动创建的 ResourceDictionary
并且旧的内容丢失了,即添加的 enums
在调用 InitializeComponent()
.
Windows.Resources
我认为没什么大不了的,并尝试在 InitializeComponent()
之后添加 enums
,因为 enums
只有在我使用以下行将数据填充到 DataGrid.DataContext
后才需要:
samplesViewSource.Source = samples;
但我仍然收到相同的错误消息。我的猜测是 DataGrid.Resources
中定义的 ComboBox 的 DataTemplate
在 DataGrid
被创建时被执行。此时enums
还没有添加
我可以通过使用 dynamic 而不是 static 资源来消除错误:
<ComboBox ItemsSource="{DynamicResource enums}"
在这一点上,我对 XAML 给出的所有问题感到非常恼火,并决定不将 Resources
用于 ComboBox.ItemsSource
,而是将 DataContext
.
当然,这又带来了另一个问题。我在文档中找不到它,但是通过调试器我发现 ComboBox.DataContext
是一个 Sample
而不是从 DataGrid
继承的 DataContext
,这是有道理的.每行需要访问 DataGrid.ItemsSource
.
又过了一天,我想出了如何从 Combobox DataTemplate
:
DataGrid.DataContext
<DataTemplate x:Key="dt">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}},
Path=DataContext.DetailEnums}"
SelectedItem="{Binding Detail, UpdateSourceTrigger=PropertyChanged}"
</DataTemplate>
这里是完整的代码,告诉你如何在 DataGrid
中使用 ComboBoxes
而不使用 Resources
来保存数据,如果你像我一样并尽量减少问题XAML 创建:
<Window x:Class="SortedComboBoxDataGrid.Window2"
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" SizeToContent="WidthAndHeight">
<DataGrid x:Name="MainDataGrid" ItemsSource="{Binding SamplesViewSource}" AutoGenerateColumns="False">
<DataGrid.Resources>
<DataTemplate x:Key="dt">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}},
Path=DataContext.DetailEnums}"
SelectedItem="{Binding Detail, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=SomeText}" Header="SomeText"/>
<DataGridTemplateColumn Header="Detail" CellTemplate="{StaticResource dt}" CellEditingTemplate="{StaticResource dt}" />
</DataGrid.Columns>
</DataGrid>
</Window>
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace SortedComboBoxDataGrid {
public partial class Window2: Window {
public record MainDataGridDataContext(ListCollectionView SamplesViewSource, DetailEnum[] DetailEnums);
public Window2() {
InitializeComponent();
var samples = new List<Sample>() {
new Sample("first", DetailEnum.All),
new Sample("second", DetailEnum.Many),
new Sample("any", DetailEnum.Some),
new Sample("last", DetailEnum.No),
};
var samplesViewSource = new ListCollectionView(samples);
var detailEnums = Enum.GetValues(typeof(DetailEnum)).Cast<DetailEnum>().OrderBy(x => x.ToString()).ToArray(); ;
MainDataGrid.DataContext = new MainDataGridDataContext(samplesViewSource, detailEnums);
}
}
}