xamlwinui-3winuiwindows-community-toolkit

Correct approach to bind ComboBox in DataGrid to viewmodel List or Enum


I am trying to let the user choose in a combobox in the datagrid, but can not find the correct approach. I have tried using both binding and x:bind. What would be the best approach? I would imagine the following code to be correct, but I am missing something.

ViewModel

    public partial class CustomerModel : ObservableObject
     {
         private ObservableCollection<DataType> _objects;
         public ObservableCollection<DataType> Objects
         {
             get => _objects;
             set => SetProperty(ref _objects, value);
         }
   
         private ObservableCollection<string> _field;
         public ObservableCollection<string> Field
         {
             get => _field;
             set => SetProperty(ref _field, value);
         }
    
    
         public CustomerModel()
         {
             Objects = new();
             Field = new ObservableCollection<string> { "Test", "Test" };
         }
    
    
         public class DataType : ObservableObject
         {
             public int ID { get; set; }
             public string Name { get; set; }
             public string Caption { get; set; }
             public bool Numeric { get; set; }
            
    
             public DataType()
             {
    
             }
           
         }
     }

.xaml

     <controls:DataGrid
         x:Name="dataGrid1" KeyDown="dataGrid1_KeyDown"
         ItemsSource="{x:Bind Model.Objects}" AutoGenerateColumns="False" >
         <controls:DataGrid.Columns>
             <controls:DataGridComboBoxColumn Header="Field" ItemsSource="{x:Bind Model.Field}" Tag="Field"/>
         </controls:DataGrid.Columns>
     </controls:DataGrid>

Updated code which is now working. Does this look okay?

        public partial class CustomerModel : ObservableObject
        {
            
            public enum Choices { A, B, C }
            [ObservableProperty]
            private ObservableCollection<DataType> _objects;
        
            
    
            public CustomerModel()
            {
                Objects = new();
                var data = new DataType();
                Objects.Add(data);
          
            }
            public static Choices[] ChoicesOptions { get; } = Enum.GetValues<Choices>();
    
    
            public partial class DataType : ObservableObject
            {
                public int ID { get; set; }
                public string Name { get; set; }
                public string Caption { get; set; }
                public bool Numeric { get; set; }
    
                [ObservableProperty]
                private  Choices _choice;
            }
        }

.xaml

      <controls:DataGridComboBoxColumn Header="Field" Binding="{Binding Choice}" ItemsSource="{x:Bind local:CustomerModel.ChoicesOptions, Mode=OneTime}" Tag="Field"

Also, I notice you instantiate the viewmodel in the codebehind file, is this necessary? I do not:

       public sealed partial class DataGridPage : Page
         {
             public DataGridPage()
             {
                 this.InitializeComponent();
             }
   }

Solution

  • Try making the Enum options static, just like Function Bindings. For example:

    public enum Sections { A, B, C }
    
    public partial class Item : ObservableObject
    {
        [ObservableProperty]
        private string _name = string.Empty;
    
        [ObservableProperty]
        private Sections _section;
    }
    
    public partial class SomeViewModel : ObservableObject
    {
        [ObservableProperty]
        private ObservableCollection<Item> _items = [];
    
        public SomeViewModel()
        {
            Items.Add(new Item { Name = "Item 1", Section = Sections.A });
            Items.Add(new Item { Name = "Item 2", Section = Sections.B });
            Items.Add(new Item { Name = "Item 3", Section = Sections.C });
        }
    
        public static Section[] SectionOptions { get; } = Enum.GetValues<SectionOptions>();
    }
    
    public sealed partial class SomePage : Page
    {
        public SomePage()
        {
            InitializeComponent();
        }
    
        public SomeViewModel ViewModel { get; } = new();
    }
    

    Then you can bind it with x:Bind:

    <Page
        x:Class="DataGridDemo.SomePage"
        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:local="using:DataGridDemo"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
        mc:Ignorable="d">
        <toolkit:DataGrid
            AutoGenerateColumns="False"
            ItemsSource="{x:Bind ViewModel.Items}">
            <toolkit:DataGrid.Columns>
                <toolkit:DataGridComboBoxColumn
                    Binding="{Binding Section}"
                    Header="Section"
                    ItemsSource="{x:Bind local:SomeViewModel.SectionOptions}" />
            </toolkit:DataGrid.Columns>
        </toolkit:DataGrid>
    </Page>
    

    BTW, as you are using the ObservableObject, you should be using the ObservableProperty attribute instead of implementing properties by yourself.