Этот вопрос основан на:
ObservableGroupedCollection<TKey, TElement>
из пакета NuGet «CommunityToolkit.Mvvm» от Microsoft.Возясь с относительно новым CommunityToolkit.Mvvm, я наткнулся на класс ObservableGroupedCollection<TKey, TElement>
, который в Wpf довольно недокументирован. Мои знания Wpf в лучшем случае скудны — я намеревался использовать это как учебный проект — и мне не удалось перенести существующий код UWP xaml в рабочий пример приложения Wpf.
Пример приложения, упомянутый в сообщении блога выше, использует CollectionViewSource
, связанный с ObservableGroupedCollection<TKey, TElement>
, для отображения сгруппированного списка контактов в прокручиваемом элементе управления. Мои попытки воспроизвести это поведение в приложении Wpf .NET 6 привели к отображению только первых значений каждой коллекции, а не всего диапазона.
Как правильно отобразить все записи сгруппированным образом, соблюдая шаблон MVVM?!
На следующем изображении показан отрывок из примера приложения Microsoft Store слева и желаемый результат справа.
Это явно те ценности, которые соскоблили с «верха» коллекций.
Что меня озадачивает, так это тот факт, что SemanticZoom
, используемый в исходном Sample App (.xaml - UWP) и соответствующем ViewModel.cs, каким-то образом может отображать ВСЕ записи, а не очищать первый элемент коллекции. . При этом все еще используя модель на основе DataTemplate
.
Следующий код представляет собой быстрый и грязный пример приложения, иллюстрирующий мою проблему и обеспечивающий основу для возможных участников.
Требования:
namespace "yourRootNamespace".Models;
public class SomeModel
{
public string SomeString { get; set; }
public SomeModel(string _s)
{
SomeString = _s;
}
}
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));
}
}
using "yourRootNamespace".ViewModels;
using System.Windows;
namespace "yourRootNamespace";
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
<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>
Я не уверен в намерениях разработчика CommunityToolkit, он работает, указав CollectionViewSource.GroupDescriptions
на CollectionViewSource
.
<Window.Resources>
<CollectionViewSource x:Key = "SomeListViewSource"
Source = "{Binding SomeObservableGroupedCollection}"
IsLiveGroupingRequested = "True">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName = "Key"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key = "SomeTemplate">
<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>
<TextBlock Foreground = "Red"
Text = "{Binding Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
Я думаю, что между WPF и UWP есть разница, или я не знаю, где, мои знания платформы пользовательского интерфейса плохие: CollectionViewSource
в dotnet 6 WPF не имеет свойства IsSourceGrouped
. Код в примерах Microsoft задает для этого свойства значение true
, а в вашем коде — нет, потому что WPF не предоставляет его.
У меня было такое же поведение, и я пошел сравнивать, что отличается, строка за строкой, и обнаружил отсутствие IsSourceGrouped
. Единственное объяснение, которое я могу придумать, заключается в том, что сгруппированная коллекция представляет собой коллекцию вложенных коллекций, поэтому внутренние компоненты ListView/CollectionViewSource должны правильно перечислять ее в глубину, а в вашем случае этого не происходит.
Таким образом, ответ будет таким: эта функция отсутствует в используемой вами версии фреймворка, поэтому вам придется прибегать к обходным путям, а не к строго чистой MVVM.
UPD: мне удалось сделать чистую группировку только для xaml, используя только ObservableCollection<T>
во ViewModel. ListView получает группы и элементы из ItemsSource = "{Binding Source = {StaticResource CustomViewSource}}
, который контролирует, как группировать, сортировать и т.д.:
<CollectionViewSource x:Key = "CustomViewSource" Source = "{Binding OnlineMods}" IsLiveGroupingRequested = "True">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName = "YourPropertyForGrouping" />
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription PropertyName = "YourPropertyForOrdering" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
Также можно добавлять предопределенные пустые группы, чтобы они всегда отображались, и делать другие трюки.
Я не могу попробовать это в данный момент. Как только я смогу изучить это, я отвечу и приму ваш ответ! Заранее спасибо, если получится.
У меня есть приложение WPF в разработке, которое использует этот подход. Вы можете посмотреть здесь: github.com/Rast1234/SyncFaction/blob/… если вы запустите его и нажмете «Обновить», список будет правильно заполнен данными
Это сработало для отображения ключей в виде заголовков (спасибо за это!), но основная проблема осталась: половина элементов была стерта пустотой. В UWP SampleApp xaml cs
SemanticZoom
и связанный с нимListView
каким-то образом могут отображать все значения каждой группы, а не только первое.