【问题标题】:WPF DataGrid with a one click ComboBox showing enumeration values sorted by enumeration namesWPF DataGrid 带有一键组合框,显示按枚举名称排序的枚举值
【发布时间】:2021-11-30 02:40:46
【问题描述】:

要求

我想向 WPF DataGrid 添加一个带有组合框的列,以满足以下要求:

  1. ComboBox中显示的值应该是枚举常量的名称
  2. ComboBox 中的条目应按枚举常量的名称排序
  3. 底层对象中的属性类型应该是枚举,而不是字符串
  4. 应减少点击次数。当我使用 DataGridComboBoxColumn 时,我需要大约 4 次单击来更改一个值。
  5. 我实际上喜欢代码隐藏解决方案,尽管基于 XAML 的解决方案也很好。
  6. 它应该在 .NET 5 WPF 下运行

示例应用程序

应用程序使用 DataGridComboBoxColumn 中提供的代码。它有效,但有两个问题:

  1. 详细信息下拉列表按字母顺序列出条目。在我的实际应用程序中,我有更多条目,当它们没有排序时很难找到正确的条目。

  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>

枚举DetailEnum:

namespace SortedComboBoxDataGrid {
  public enum DetailEnum {
    No,
    Some,
    Many,
    All
  }

示例类:

  public class Sample {
    public string SomeText { get; set; }
    public DetailEnum Detail { get; set; }

    public Sample(string someText, DetailEnum detail) {
      SomeText = someText;
      Detail = detail;
    }
  }
}

后面的窗口代码:

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。这很好地对枚举进行了排序,但这样做会将枚举值转换为字符串,然后对这些字符串进行排序。如果用户点击不同的条目,网格会返回一个字符串而不是枚举值。

我尝试了在 stackoverflow 上找到的各种解决方案来减少技巧的数量,但无法让其中一种与排序 (!) 枚举一起正常工作。

我想知道对于 ComboBox 使用具有枚举值和枚举名称作为其属性而不是 List 的类实例列表是否会更好?

  public class DetailEnumClass {
    public DetailEnum EnumValue { get; set; }
    public string EnumName { get; set; }
  }

请在将此问题标记为重复问题之前阅读此内容

我知道关于我在这里提到的一个或另一个问题,stackoverflow 上已经有很多答案。但是,我无法提出涵盖所有要求的可行解决方案。因此,如果您找到了为完整问题提供完整代码的答案,请仅将此问题标记为重复。谢谢。

【问题讨论】:

    标签: c# wpf enums combobox datagrid


    【解决方案1】:

    您可以将DataGridTemplateColumnComboBox 结合使用,该ComboBox 绑定到您在代码隐藏中创建的已排序IEnumerable&lt;DetailEnum&gt;

    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>
    

    【讨论】:

    • 就这么简单?谢谢你的建议。不幸的是,我的电脑正在维修中,因此需要 2 周以上的时间才能试用。
    • 我想知道将 IEnumerable 转换为 Array 是否会更好地提高性能?如果网格中有许多组合框,每个组合框都必须执行 GetValues(),即创建数组并对该数组进行多次排序,涉及调用 ToString() 并因此创建大量字符串?一个 ComboBox 可能需要多次这样做:首先是当它显示已选择的值时,然后是当用户更改该值时。如果源是可枚举的,ComboBox 没有其他选择,只能在搜索某些内容时运行包括排序在内的整个查询?
    【解决方案2】:

    当我使用我接受的答案时,我遇到了一个有趣的问题。以下代码工作正常:

    <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>
    

    之后我无法再运行该应用程序。我收到错误消息“找不到名为“枚举”的资源。资源名称区分大小写。

    我花了 2 天时间才弄清楚这里发生了什么。通常,不需要将&lt;ResourceDictionary&gt; 添加到&lt;Window.Resources&gt;,XAML 会在幕后为您完成。为了能够使用MergedDictionaries,您必须自己动手。这会改变 XAML 的行为方式。也许我应该在这里补充一下,因为这类问题,我讨厌 XAML 已有 10 多年了。

    我猜想&lt;ResourceDictionary&gt; XAML 会用新的ResourceDictionary 删除自动创建的ResourceDictionary,旧的内容会丢失,即enums,它在调用InitializeComponent() 之前已添加到Windows.Resources

    我认为没什么大不了的,并尝试在InitializeComponent() 之后添加enums,因为只有在我使用以下行将数据填充到DataGrid.DataContext 时才需要enums

    samplesViewSource.Source = samples;
    

    但我仍然收到相同的错误消息。我的猜测是 DataGrid.Resources 中定义的 ComboBox 的 DataTemplate 在创建 DataGrid 时被执行。此时enums尚未添加。

    我可以通过使用 dynamic 而不是 static 资源来消除错误:

    <ComboBox ItemsSource="{DynamicResource enums}"
    

    此时,我对 XAML 给出的所有问题感到非常恼火,并决定不使用 Resources 代替 ComboBox.ItemsSource,而是使用 DataContext

    当然,这又带来了另一个问题。我在文档中找不到它,但使用调试器我发现ComboBox.DataContextSample 而不是从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>
    

    如果您像我一样尝试尽量减少 XAML 造成的问题,这里是您如何在 DataGrid 中使用 ComboBoxes 而不使用 Resources 来保存数据的完整代码:

    <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);
        }
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-18
      • 2012-03-03
      • 2015-08-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多