xamlwinui-3winuiwindows-app-sdkwinui-xaml

Grouping items in a WinUI 3 ComboBox control


I have tried grouping the items in the combobox like below

Fruits
 Apple 
 Banana
Vegetables
 Carrot
 Brinjal

I have used collectionview source to created the grouped source like below

    public ObservableCollection<GroupInfoList> GetGroupedItems(List<Eatables> eatables)
    {
        var query = eatables.GroupBy(item => item.Type).Select(g => new GroupInfoList(g) { Key = g.Key });
        return new ObservableCollection<GroupInfoList>(query);
    }

    public class GroupInfoList : List<object>
    {
        public GroupInfoList(IEnumerable<object> items)
            : base(items)
        {
    
        }
    
        public object Key { get; set; }
    }

If I use in listview , the group headers and items are showing correctly

    <ListView>
        <ListView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate x:DataType="model:GroupInfoList">
                        <StackPanel IsTabStop="False" Margin="0,0,0,10">
                            <TextBlock Text="{x:Bind Key}" />
                        </StackPanel>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListView.GroupStyle>
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="model:Eatables">
                    <TextBlock Text="{x:Bind Name}" />
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

But if I try to do the same thing in combobox it is not working. Internally both has ItemsPresenter I suppose it should also work for combobox. How can I fix this?


Solution

  • As @Nick mentioned, grouping is not supported.

    Let me show you a workaround which might not be perfect but should be worth considering it.

    Here, I'm using:

    public partial class GroupInfoListToFlatCollectionConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            List<object> flatList = [];
    
            if (value is IEnumerable<GroupInfoList> groups)
            {
                foreach (GroupInfoList group in groups)
                {
                    flatList.Add(group);
    
                    foreach (var item in group)
                    {
                        flatList.Add(item);
                    }
                }
            }
    
            return flatList.ToArray();
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
    
    public partial class ComboBoxContainerStyleSelector : StyleSelector
    {
        public Style GroupHeaderStyle { get; set; } = new();
    
        public Style ItemStyle { get; set; } = new();
    
        protected override Style SelectStyleCore(object item, DependencyObject container)
        {
            return item is GroupInfoList ? GroupHeaderStyle : ItemStyle;
        }
    }
    
    public partial class ComboBoxItemTemplateSelector : DataTemplateSelector
    {
        public DataTemplate GroupHeaderTemplate { get; set; } = new();
    
        public DataTemplate ItemTemplate { get; set; } = new();
    
        protected override DataTemplate SelectTemplateCore(object item)
        {
            return item is GroupInfoList ? GroupHeaderTemplate : ItemTemplate;
        }
    
        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            return SelectTemplate(item);
        }
    }
    
    public ObservableCollection<GroupInfoList> GroupInfoLists { get; } =
    [
        new(["Apple", "Banana"]) { Key = "Fruits" },
        new(["Carrot", "Brinjal"]) { Key = "Vegetables" },
    ];
    
    <Page.Resources>
    
        <local:GroupInfoListToFlatCollectionConverter x:Key="GroupInfoListToFlatCollectionConverter" />
    
        <local:ComboBoxItemTemplateSelector x:Key="ComboBoxItemTemplateSelector">
            <local:ComboBoxItemTemplateSelector.GroupHeaderTemplate>
                <DataTemplate x:DataType="local:GroupInfoList">
                    <TextBlock
                        FontWeight="Bold"
                        Foreground="SkyBlue"
                        IsHitTestVisible="False"
                        Text="{x:Bind Key}" />
                </DataTemplate>
            </local:ComboBoxItemTemplateSelector.GroupHeaderTemplate>
            <local:ComboBoxItemTemplateSelector.ItemTemplate>
                <DataTemplate x:DataType="x:String">
                    <TextBlock Text="{x:Bind}" />
                </DataTemplate>
            </local:ComboBoxItemTemplateSelector.ItemTemplate>
        </local:ComboBoxItemTemplateSelector>
    
        <local:ComboBoxContainerStyleSelector x:Key="ComboBoxContainerStyleSelector">
            <local:ComboBoxContainerStyleSelector.GroupHeaderStyle>
                <Style BasedOn="{StaticResource DefaultComboBoxItemStyle}" TargetType="ComboBoxItem">
                    <Setter Property="IsEnabled" Value="False" />
                </Style>
            </local:ComboBoxContainerStyleSelector.GroupHeaderStyle>
            <local:ComboBoxContainerStyleSelector.ItemStyle>
                <Style BasedOn="{StaticResource DefaultComboBoxItemStyle}" TargetType="ComboBoxItem">
                    <Setter Property="Padding" Value="24,0,0,0" />
                </Style>
            </local:ComboBoxContainerStyleSelector.ItemStyle>
        </local:ComboBoxContainerStyleSelector>
    
    </Page.Resources>
    
    <ComboBox
        ItemContainerStyleSelector="{StaticResource ComboBoxContainerStyleSelector}"
        ItemTemplateSelector="{StaticResource ComboBoxItemTemplateSelector}"
        ItemsSource="{x:Bind GroupInfoLists, Mode=OneWay, Converter={StaticResource GroupInfoListToFlatCollectionConverter}}" />
    

    enter image description here