Search code examples
wpfxamlbindingdatagrid

WPF Datagrid enum combobox column


In a WPF, if I use AutoGenerateColumns and bind it to my collection of class objects, it displays enums as a combobox automatically where I can choose between all options available in the enum:

<DataGrid ItemsSource="{Binding SettingsList}"/>

enter image description here

However, if I explicitly declare it in XAML:

<DataGrid.Columns>
    <DataGridComboBoxColumn Header="Mirroring" SelectedValuePath="{Binding Mirroring}"/>
</DataGrid.Columns>

enter image description here

It seems to not recognize the options available in the enum. I am certain I am missing something minor, like setting the item source to something, but I can't figure out what to set it to to make it work out of the box like when using auto generate, of course I can write a custom converter to get all values to set the item source, but it feels like I am missing a builtin way to make it work.


Solution

  • You said:

    I am certain I am missing something minor, like setting the item source to something.


    For setting the item source (and in general) what works best for me is managing the columns in code-behind, and since column definitions are part of the UI's presentation logic and don’t include business logic or state management, this doesn’t violate MVVM.


    XAML: DataGrid AutoGenerateColumns=False

    <Window ...>
        <Window.DataContext>
            <local:MainWindowViewModel x:Name="MainWindowViewModel"/>
        </Window.DataContext>
        <Grid>
            <DataGrid  
                Name="dataGrid" ItemsSource="{Binding Items}" AutoGenerateColumns="False"
                CanUserAddRows="False" FontSize="14" RowHeight="30">
            </DataGrid>
        </Grid>
    </Window>
    
    Configure columns in Code-Behind
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += (sender, e) =>
            {
                dataGrid.Columns.Add(new DataGridComboBoxColumn 
                {
                    Header = nameof(Item.Mirroring),
                    SelectedValueBinding = new Binding(nameof(Item.Mirroring)),
                    ItemsSource = Enum.GetValues<Mirroring>(),
                    Width = 150,
                });
                dataGrid.Columns.Add(new DataGridTextColumn
                {
                    Header = nameof(Item.Name),                    
                    Binding = new Binding(nameof(Item.Name)), 
                    Width = new DataGridLength(1, DataGridLengthUnitType.Star)
                });
            };
        }
    }
    

    XAML Approach

    If you prefer to stick with the XAML column declarations, one way to do it is have the view model provide the items source (here I made a singleton to do the enumeration one time). But now we strain a little to express in XAML what is so straightforward in code-behind.

    class MainWindowViewModel
    {
        public ObservableCollection<Item> Items { get; } =
            new(Enumerable.Range(0,5).Select(_=>new Item(_)));
    
        public Array ComboBoxValues
        {
            get
            {
                if (_comboBoxValues is null)
                {
                    _comboBoxValues = Enum.GetValues<Mirroring>();
                }
                return _comboBoxValues;
            }
        }
        static Array? _comboBoxValues = default;
    }
    

    <Window ...>
        <Window.DataContext>
            <local:MainWindowViewModel x:Name="MainWindowViewModel"/>
        </Window.DataContext>
        <Grid>
            <DataGrid  
                Name="dataGrid" ItemsSource="{Binding Items}" AutoGenerateColumns="False"
                CanUserAddRows="False" FontSize="14" RowHeight="30">
                <DataGrid.Columns>
                    <DataGridComboBoxColumn
                        Header="Mirroring" 
                        SelectedValueBinding="{Binding Mirroring}"               
                        ItemsSource="{Binding ComboBoxValues, Source={x:Reference MainWindowViewModel}}"
                        Width="150" />
                    <DataGridTextColumn
                        Header="Name" 
                        Binding="{Binding Name}"
                        Width="*"/>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>
    

    Either Way

    enum values showing