2024年9月29日日曜日

ListView Extensions ver.1.3.1

ListView Extensions ver.1.3.1をリリースしました。

Github:

Nuget:
https://www.nuget.org/packages/ListViewExtensions/

今回の変更点

  • ソート対象のプロパティ名を入れ子(プロパティのプロパティの…のプロパティ)にできるようにした。
  • SortableGridViewColumnにSortingMemberPathを追加した。この項目に何も設定していない場合(nullの場合)は今まで通りDisplayMemberBindingsのパスをソートのキーとして扱うが、この項目を設定した場合はこれをキーとしてソートされる。
  • ListViewViewModel (ReadOnlyUIObservableCollectionを継承しているクラス)でIndexerとCountのPropertyChangedイベントが発生しなかった不具合を修正
  • ListViewの子要素にComboBoxなどがあると、その選択が変化したときに例外が発生する不具合を修正

下2つはバグ修正ですので、上2つについて説明していきます。

ソート対象のプロパティ名を入れ子(プロパティのプロパティの…のプロパティ)にできるようにした

今までは、ソートのキーにするプロパティ名は、SortableObservableCollectionの要素のプロパティしかできませんでした。ですので、少々わざとらしいですが、例えば以下のようなクラスがあったとしたら、person.Name.Spellなどのいわゆる「プロパティのプロパティ」はソートキーに指定することができませんでした(こちらのコードはgithubに公開しているサンプルコードの抜粋です)。

public class PersonViewModel : ViewModel
{
    // 中略
    
    public NameViewModel? Name
    {
        get => _Name;
        set => RaisePropertyChangedIfSet(ref _Name, value);
    }
    NameViewModel? _Name;

    public string Age => $"{model.Age}歳";

    public string Birthday => model.Birthday.ToShortDateString();

    public string Height => $"{model.Height_cm}cm";

    // 中略
}

public class NameViewModel : ViewModel
{
    // 中略
    
    public string? Spell
    {
        get => model.Spell;
        set => model.Spell = value;
    }

    public string? Pronunciation
    {
        get => model.Pronunciation;
        set => model.Pronunciation = value;
    }
}

それをできるようにしたという変更です。

これの真価を発揮するのは、ReactivePropertyを使ってViewModelを作ったときです。ReactivePropertyでは、person.Name.Valueなどの形でプロパティにアクセスする必要があるため、必ず2段以上入れ子になります。

実は今までもListVIewViewModelのコンストラクタでプロパティの読み替え用のディクショナリを、SortableObservableCollectionの引数でIComparerのディクショナリを渡すことで無理やり使うことはできなくはなかったのですが、回りくどい方法でソースコードの負荷が高まってしまうためあまりイケている方法ではありませんでした。ですが、今回のアップデートで、DisplayMemberBindingsに表現したとおりのパスを辿るようになったので、ViewModelやModelでは何も書かずに入れ子プロパティを参照できるようになりました。

SortableGridViewColumnにSortingMemberPathを追加した

ListViewのヘッダーはSortableGridViewColumnで作ることができます。ここで、それぞれのヘッダーに対応するソートのキーとするプロパティは、そのままDisplayMemberBindingsのパスを用いていましたが、それを任意のプロパティに設定できるようにしました。

通常は別の名前のプロパティにする必要は無いとは思いますが、例えば、ViewModelとViewでプロパティ名が異なる場合は、実際はソート操作自体はModel(SortableObservableCollection)にて行っているためプロパティ名の読み替えが必要になっていました。前述した通り、ViewModelにはプロパティ名読み替え用にディクショナリを受け取るコンストラクタがありますが、SortingMemberPathで直接設定できるようになりました。

<GridView>
    <lv:SortableGridViewColumn Width="120" SortableSource="{Binding People}" DisplayMemberBinding="{Binding Name.Spell}" Header="Name" />
    <lv:SortableGridViewColumn Width="150" SortableSource="{Binding People}" DisplayMemberBinding="{Binding Name.Pronunciation}" Header="Pronunciation" />
    <lv:SortableGridViewColumn Width="70"  SortableSource="{Binding People}" DisplayMemberBinding="{Binding Age}" Header="Age" />
    <lv:SortableGridViewColumn Width="120" SortableSource="{Binding People}" DisplayMemberBinding="{Binding Birthday}" Header="Birthday" />
    <lv:SortableGridViewColumn Width="120" SortableSource="{Binding People}" DisplayMemberBinding="{Binding Height}" SortingMemberPath="Height_cm" Header="Height" />
    <GridView.ColumnHeaderContainerStyle>
        <Style TargetType="lv:SortableGridViewColumnHeader">
            <Setter Property="SortingArrowLocation" Value="Top" />
        </Style>
    </GridView.ColumnHeaderContainerStyle>
</GridView>

このコードでは、5つ目の「Height」のプロパティ名を「Height_cm」と指定しています。

ちなみにもう一点この機能が必須のところがあって、SortableGridViewColumnにてCellTemplateを指定する場合です。セルの中身をDataTemplateで表現したい場合に使うものですが、DisplayMemberBindingsを設定しているとそちらのほうが優先されてしまいCellTemplateが働きません。その際もこのSortingMemberPathを指定することで、DataTemplateを使いながらソート用のプロパティも指定できるようになりました。

ちなみに、DisplayMemberBindingsはBindingBase型のプロパティですが、SortingMemberPathはString型です。ですので、Visual StudioでIntelliSenseは働きませんのでご注意ください。

--------------

変更点の説明は以上です 。

実際自分でこのライブラリを使い込んでいくといろいろと見つかりますね。まあ、WPFの全貌は10年以上触っていてもよくわからないので、こうやってブラッシュアップしていくしかないですね…。

0 件のコメント:

コメントを投稿