ObservableGroupedCollection in Wpf .NET 6
This question is based on:
- A Wpf project using .NET 6
- The
ObservableGroupedCollection<TKey, TElement>
class from the NuGet package "CommunityToolkit.Mvvm" by Microsoft - striktly obeying the MVVM pattern
While tinkering with the relatively new CommunityToolkit.Mvvm I came across the ObservableGroupedCollection<TKey, TElement>
class which in Wpf is rather undocumented. My Wpf knowledge is poor at best - I intended to use this as a learning project - and I was unable to transfer the existing UWP xaml code into a working Wpf sample application.
The sample app referenced in the blog post above uses a CollectionViewSource
bound to an ObservableGroupedCollection<TKey, TElement>
to display a grouped list of contacts within a scrollable control. My attempts to replicate this behavior within a Wpf .NET 6 app resulted in only the first values of each collection being displayed, rather than the entire range.
What is the proper way to display all entries in a grouped fashion, while obeying to the MVVM pattern?!
The following image shows an excerpt from the Microsoft Store sample application on the left and the desired result on the right.
Results from the sample code below
Results when iterating manually through the groups and their collections:
A | B | E | F | W |
---|---|---|---|---|
a_2 | b_0 | e_0 | f_0 | w_1 |
a_1 | f_1 | w_0 | ||
a_0 | f_2 |
Values displayed in the actual ListView:
A | B | E | F | W |
---|---|---|---|---|
a_2 | b_0 | e_0 | f_0 | w_1 |
These are obviously values that got scraped off the "top" of the collections.
What puzzles me is the fact that the SemanticZoom
used in the original Sample App (.xaml - UWP) and the corresponding ViewModel.cs is somehow able to display ALL entries instead of scraping off the first element of the collection. While still using a model based DataTemplate
.
Sample code
The following code is a quick and dirty example application to illustrate my problem and to provide a foundation for possible participants.
Requirements:
- Wpf Project -> .NET 6
- NuGet package: CommunityToolkit.Mvvm by Microsoft
- 2 new folders: Models and ViewModels
- Replace all instances of "yourRootNamespace" with your actual root namespace
SomeModel.cs
namespace "yourRootNamespace".Models;
public class SomeModel
{
public string SomeString { get; set; }
public SomeModel(string _s)
{
SomeString = _s;
}
}
MainWindowViewModel.cs
using CommunityToolkit.Mvvm.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using "yourRootNamespace".Models;
using System.Collections.Generic;
using System.Linq;
namespace "yourRootNamespace".ViewModels;
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private ObservableGroupedCollection<string, SomeModel>? m_someObservableGroupedCollection;
public MainWindowViewModel()
{
List<SomeModel> tempList = new List<SomeModel>()
{
new SomeModel("w_1"),
new SomeModel("b_0"),
new SomeModel("a_2"),
new SomeModel("e_0"),
new SomeModel("f_0"),
new SomeModel("f_1"),
new SomeModel("a_1"),
new SomeModel("a_0"),
new SomeModel("w_0"),
new SomeModel("f_2")
};
m_someObservableGroupedCollection = new ObservableGroupedCollection<string, SomeModel>(tempList
.GroupBy(c => char.ToUpperInvariant(c.SomeString[0]).ToString())
.OrderBy(g => g.Key));
}
}
MainWindow.xaml.cs
using "yourRootNamespace".ViewModels;
using System.Windows;
namespace "yourRootNamespace";
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
MainWindow.xaml
<Window x:Class=""yourRootNamespace".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:"yourRootNamespace""
xmlns:collections="clr-namespace:CommunityToolkit.Mvvm.Collections;assembly=CommunityToolkit.Mvvm"
xmlns:viewmodels="clr-namespace:"yourRootNamespace".ViewModels"
xmlns:models="clr-namespace:"yourRootNamespace".Models"
d:DataContext="{d:DesignInstance Type=viewmodels:MainWindowViewModel}"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<CollectionViewSource
x:Key="SomeListViewSource"
Source="{Binding SomeObservableGroupedCollection}"
IsLiveGroupingRequested="True"/>
<DataTemplate
x:Key="SomeTemplate"
DataType="{x:Type models:SomeModel}">
<TextBlock Text="{Binding SomeString}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListView
ItemTemplate="{StaticResource SomeTemplate}"
ItemsSource="{Binding Source={StaticResource SomeListViewSource}, Mode=OneWay}"
SelectionMode="Single">
<ListView.GroupStyle>
<GroupStyle
HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate
DataType="{x:Type collections:IReadOnlyObservableGroup}">
<TextBlock Text="{Binding Key}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
</Window>