Правильный способ отображения ObservableGroupedCollection<string, TElement> с использованием Wpf .NET 6 и пакета CommunityToolkit.Mvvm

ObservableGroupedCollection в Wpf.NET 6

Этот вопрос основан на:

  • Проект Wpf с использованием .NET 6
  • Класс ObservableGroupedCollection<TKey, TElement> из пакета NuGet «CommunityToolkit.Mvvm» от Microsoft.
  • строгое соблюдение шаблона MVVM

Возясь с относительно новым CommunityToolkit.Mvvm, я наткнулся на класс ObservableGroupedCollection<TKey, TElement>, который в Wpf довольно недокументирован. Мои знания Wpf в лучшем случае скудны — я намеревался использовать это как учебный проект — и мне не удалось перенести существующий код UWP xaml в рабочий пример приложения Wpf.
Пример приложения, упомянутый в сообщении блога выше, использует CollectionViewSource, связанный с ObservableGroupedCollection<TKey, TElement>, для отображения сгруппированного списка контактов в прокручиваемом элементе управления. Мои попытки воспроизвести это поведение в приложении Wpf .NET 6 привели к отображению только первых значений каждой коллекции, а не всего диапазона.

Как правильно отобразить все записи сгруппированным образом, соблюдая шаблон MVVM?!

На следующем изображении показан отрывок из примера приложения Microsoft Store слева и желаемый результат справа.

Результаты из приведенного ниже примера кода

Результаты при переборе групп и их коллекций вручную:

А Б Е Ф Вт а_2 б_0 e_0 f_0 w_1 а_1 f_1 w_0 а_0 f_2

Значения, отображаемые в фактическом ListView:

А Б Е Ф Вт а_2 б_0 e_0 f_0 w_1

Это явно те ценности, которые соскоблили с «верха» коллекций.

Что меня озадачивает, так это тот факт, что SemanticZoom, используемый в исходном Sample App (.xaml - UWP) и соответствующем ViewModel.cs, каким-то образом может отображать ВСЕ записи, а не очищать первый элемент коллекции. . При этом все еще используя модель на основе DataTemplate.

Образец кода

Следующий код представляет собой быстрый и грязный пример приложения, иллюстрирующий мою проблему и обеспечивающий основу для возможных участников.

Требования:

  • Проект Wpf -> .NET 6
  • Пакет NuGet: CommunityToolkit.Mvvm от Microsoft
  • 2 новые папки: Models и ViewModels
  • Замените все экземпляры «yourRootNamespace» вашим фактическим корневым пространством имен.

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>
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
69
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я не уверен в намерениях разработчика 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>

Это сработало для отображения ключей в виде заголовков (спасибо за это!), но основная проблема осталась: половина элементов была стерта пустотой. В UWP SampleApp xaml csSemanticZoom и связанный с ним ListView каким-то образом могут отображать все значения каждой группы, а не только первое.

ge.go 14.11.2022 15:16
Ответ принят как подходящий

Я думаю, что между 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>

Также можно добавлять предопределенные пустые группы, чтобы они всегда отображались, и делать другие трюки.

Я не могу попробовать это в данный момент. Как только я смогу изучить это, я отвечу и приму ваш ответ! Заранее спасибо, если получится.

ge.go 18.12.2022 19:14

У меня есть приложение WPF в разработке, которое использует этот подход. Вы можете посмотреть здесь: github.com/Rast1234/SyncFaction/blob/… если вы запустите его и нажмете «Обновить», список будет правильно заполнен данными

Rast 18.12.2022 19:22

Другие вопросы по теме