Search code examples
c#wpfmvvmdatagrid

How to group datagrid items in WPF & MVVM?


I have an app on WPF / .Net Framework 4.8 and Prism for MVVM. And I dont understand how to group items in datagrid with MVVM. All guides that I found implement this feature only without MVVM. And the only one found with MVVM doesnt work for me (no errors, but no groups in the datagrid either)

The main window has a datagrid of following objects:

Product p = new Product() {
    Id = "H196A",   //Any string
    Name = "Box A", //Any string
    Count = 3       //Any int
}

ViewModel looks like this

public ObservableCollection<Product> MyCollection { get; set; }

public ViewModel()
{
    //Filling in the collection
}

And View

<DataGrid ItemsSource="{Binding MyCollection}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Id"/>
        <DataGridTextColumn Header="Name"/>
        <DataGridTextColumn Header="Count"/>
    </DataGrid.Columns>
</DataGrid>

I've tried to bind datagrid to CollectionViewSource this way:

public CollectionViewSource MyCollectionViewSource { get; set; }

public ViewModel()
{
    //Filling in the collection
    MyCollectionViewSource = new CollectionViewSource();
    MyCollectionViewSource.Source = MyCollection;
    MyCollectionViewSource.GroupDescriptions.Add(new PropertyGroupDescription(nameof(Product.Name)));
}
<DataGrid ItemsSource="{Binding MyCollectionViewSource.View}" AutoGenerateColumns="False">
    ...
</DataGrid>

But it didn't work


Solution

  • You may find it easier to define the CollectionViewSource in the xaml as a static resource. By way of explanation, <Grid.Resources> is just a dictionary that allows static objects to be referenced using the key that you define, so in this case the key is GroupedProducts.

    <Window x:Class="data_grid_grouping.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:data_grid_grouping"
            mc:Ignorable="d"
            Background="Azure"
            Title="MainWindow" Width="500" Height="500" >
        <Window.DataContext>
            <local:ViewModel />
        </Window.DataContext>
        <Grid>
            <Grid.Resources>
                <CollectionViewSource x:Key="GroupedProducts" Source="{Binding MyCollection}">
                    <CollectionViewSource.GroupDescriptions>
                        <PropertyGroupDescription PropertyName="Name"/>
                    </CollectionViewSource.GroupDescriptions>
                </CollectionViewSource>
            </Grid.Resources>
            <DataGrid ItemsSource="{Binding Source={StaticResource GroupedProducts}}" AutoGenerateColumns="False" Margin="10, 5">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*" />
                    <DataGridTextColumn Header="Count" Binding="{Binding Count}" />
                    <DataGridTextColumn Header="Id" Binding="{Binding Id}" />
                </DataGrid.Columns>
                <DataGrid.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.HeaderTemplate>
                            <DataTemplate>
                                <TextBlock FontWeight="Bold" FontSize="14" Text="{Binding Name}" Margin="5" Foreground="Maroon"/>
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>
                    </GroupStyle>
                </DataGrid.GroupStyle>
            </DataGrid>
        </Grid>
    </Window>
    

    Minimal Reproducible Example using Prism.Mvvm
    public partial class MainWindow : Window
    {
        public MainWindow() => InitializeComponent();
    }
    class ViewModel : BindableBase
    {
        public ViewModel() 
        {
            var tmp = MyCollection.GroupBy(_=>_.Name);
            foreach (var tmpGroup in tmp)
            {
                var count = 1;
                foreach (Product product in tmpGroup)
                {
                    product.Count = count++;
                }
            }
        }
        public ObservableCollection<Product> MyCollection { get; } = new ObservableCollection<Product>
        {
            new Product{ Name = "Hammer" },
            new Product{ Name = "Screwdriver Set" },
            new Product{ Name = "Drill Bit Set" },
            new Product{ Name = "Measuring Tape" },
            new Product{ Name = "Wrench" },
            new Product{ Name = "Drill Bit Set" },
            new Product{ Name = "Hammer" },
            new Product{ Name = "Wrench" },
            new Product{ Name = "Drill Bit Set" },
            new Product{ Name = "Hammer" },
            new Product{ Name = "Screwdriver Set" },
            new Product{ Name = "Measuring Tape" }
        };
    
        public event PropertyChangedEventHandler? PropertyChanged;
    }
    
    public class Product : BindableBase
    {
        private int _count;
        public int Count
        {
            get => _count;
            set => SetProperty(ref _count, value);
        }
    
        private string _name = string.Empty;
        public string Name
        {
            get => _name;
            set => SetProperty(ref _name, value);
        }
    
        private string _id = 
            Guid
            .NewGuid()
            .ToString()
            .Replace("-", string.Empty)
            .ToUpper()
            .Substring(0, 12);
        public string Id
        {
            get => _id;
            set => SetProperty(ref _id, value);
        }
    }
    

    grouped items