Search code examples
c#wpfenumscomboboxdatagrid

WPF DataGrid with a one click ComboBox showing enumeration values sorted by enumeration names


Requirements

I would like to add a column with comboboxes to a WPF DataGrid meeting the following requirements:

  1. The value displayed in the ComboBox should be the name of the enumeration constant
  2. The entries in the ComboBox should be sorted by the name of the enumeration constant
  3. The property type in the underlying object should be enum, not string
  4. The number of clicks should be reduced. When I use DataGridComboBoxColumn, I need about 4 clicks to change a value.
  5. I actually like code behind solutions, although XAML based solutions are fine too.
  6. It should run under .NET 5 WPF

Sample Application

The application uses the code provided in the DataGridComboBoxColumn. It works, but has 2 problems:

  1. the Details DropDown lists the entries alphabetically. In my real application I have many more entries and it is very difficult to find the right one when they are not sorted.

  2. It takes 4 mouse clicks to change a ComboBox value.

Code

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>

Enum DetailEnum:

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

Sample 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 code behind:

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

What I have tried already

I tried Displaying sorted enum values in a ComboBox. This sorts enums nicely, but to do so it converts the enum values into strings, then sorts those strings. If the user clicks on a different entry, the grid returns a string and not an enumeration value.

I tried various solutions I found on stackoverflow to reduce the number of tricks, but could not get one working properly with sorted (!) enums.

I wonder if it would be better to use for the ComboBox a list of class instances having the enum value and the enum name as its properties instead of a List ?

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

Please read this before marking this question as a duplicate

I am aware there are already many answers on stackoverflow regarding one or the other problem I mentioned here. However, I am not able to come up with a working solution which covers all requirements. So please only mark this question as a duplicate if you have found an answer which provides full code for the complete problem. Thanks.


Solution

  • You could use a DataGridTemplateColumn with a ComboBox that binds to a sorted IEnumerable<DetailEnum> that you create yourself in the code-behind:

    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>