imani-cの日記

WPFスタイルとテンプレートの本をAmazonで出しました。ちょっととっつきにくいこれらについて知りたいお方は、覗いてみてください。「WPF スタイル」で検索すると、トップに出ます。gRPCの本もあります。「gRPC入門」で検索するとすぐ見つかります。

WPF - BindableBase

これまでのサンプルコードでは、予備知識なしで読めるようにサンプルコード内にINotifyPropertyChangedを実装してきましたが、面倒になりましたので、サンプルコードで使うViewMode用の基本クラスを作りました。

機能は、以下の通りです。 - INotifyPropertyChangedを実装する。 - プロパティの値が変わるときにPropertyChangedイベントを発行する。

機能とAPIは、PrismのBindableBaseに似ています。

実装はPrismとは異なり、プロパティへの値設定やPropertyChangedの呼び出しを別のお便利クラスに分けてあります。いずれ、もう少しプロパティシステムをおいしくいただくための仕掛けを作るつもりなのですが、このお便利クラスを利用しようと思って分けました。

本格的にWPFを使う場合にはPrismなどを利用すべきだと思いますが、ビジネスモデル担当者がテスト用や保守用のUIを作る場合などには、このソースコードが手軽でよいと思います。そのようなものでもPrismなどのライブラリを使っていると、ライブラリが更新されたときにバージョン不一致などのトラブルを起こしたり、ソリューション内に複数バージョンのライブラリが存在して混乱したりします。

DelegateCommandクラスの代替物もいずれ掲載する予定です。

利用方法1

利用方法は、簡単です。

まず、ViewModelをBindableBaseから派生させてください。

そして、プロパティを以下のように定義してください。

public int AProperty { get => _aProperty; set => SetProperty(ref _aProperty, value); }
private int _aProperty = 0;     // 初期値は任意の値でよい。

これだけです。

なお、SetProperty()の戻り値は、プロパティの値が変更されるとき、つまり、_aPropertyの値が変わるときにtrueを返す論理型です。ですから、以下のようなコードによりプロパティの値が変わるときにだけ実行される処理を書くことができます。

public int AProperty
{
    get => _aProperty;
    set {
         if(SetProperty(ref _aProperty, value)){
            ...     // プロパティの値が変わるときにだけ実行される処理。
         }
    }
}
private int _aProperty = 0;     // 初期値は任意の値でよい。

RaisePropertyChanged()を用いて、任意のプロパティの値が変更されたことを通知することもできます。

利用法2

ViewModelを上記BindableBaseクラス以外から派生させたい場合には、お便利クラスであるChangeNotifierを直接使ってください。使い方は、BindableBaseクラスを見ればわかると思いますので、説明を省略します。

コード

BindableBaseクラスとお便利クラスの全ソースコードを掲げます。

/*  Copyright 2022 Imani-C 
    This source code is provided under MIT license.
        https://opensource.org/licenses/MIT
        https://licenses.opensource.jp/MIT/MIT.html
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace NotifyChangeBase
{
    public class ChangeNotifier
    {
        #region //! @name コンストラクタ
        //! コンストラクタ。
        /*! @param[in]  propertyChangedCaller   INotifyPropertyChanged.PropertyChangedを呼び出すためのデリゲート。
        */
        public ChangeNotifier(Action<PropertyChangedEventArgs> propertyChangedCaller)
            => PropertyChangedCaller = propertyChangedCaller;
        #endregion

        #region //! @name 変数
        //! INotifyPropertyChanged.PropertyChangedを呼び出すためのデリゲート。コンストラクタで与えられる。
        private Action<PropertyChangedEventArgs> PropertyChangedCaller = null!;
        #endregion

        #region //! @name プロパティ値の変更を通知するためのAPI
        //! プロパティの値を実体に設定する。
        /*! @param[in]  value       プロパティに設定する値。
            @param[in]  storage     プロパティ値の実態を格納する変数。
            @param[in]  propName    この引数を設定しないこと。この関数を呼び出したプロパティの名前。
            @return true    プロパティ値が変更された。
            @return false   設定する値とプロパティ値が同じだったので、プロパティ値が変更されなかった。
        */
        public bool SetProperty<T>(T value, ref T storage, [CallerMemberName] string? propName = null)
        {
            bool result;
            if (EqualityComparer<T>.Default.Equals(storage, value))
            {
                result = false;
            }
            else
            {
                storage = value;
                RaisePropertyChanged(propName);

                result = true;
            }
            return result;
        }

        //! PropertyChangedイベントを発行する。
        /*! @param[in]  propName    変更されたプロパティの名前。
        */
        public void RaisePropertyChanged(string? propName)
            => PropertyChangedCaller(new PropertyChangedEventArgs(propName));
        #endregion
    }

    //! INotifyPropertyChangedを実装した基本クラス。
    public class BindableBase : INotifyPropertyChanged
    {
        #region //! コンストラクタと共通変数
        //! コンストラクタ。通知管理オブジェクトを用意する。
        public BindableBase()
            => Np = new((a) => PropertyChanged?.Invoke(this, a));

        private readonly ChangeNotifier Np = null!;     //!< プロパティ値変更通知管理オブジェクト。
        #endregion

        #region //! @name INotifyPropertyChangedの実装
        public event PropertyChangedEventHandler? PropertyChanged;  //!< INotifyPropertyChangedが定義しているプロパティ値変更通知イベント。
        #endregion

        #region //! @name 派生クラス用API
        //! @copydoc ChangeNotifier::SetProperty
        protected bool SetProperty<T>(T value, ref T storage, [CallerMemberName] string? propName = null)
            => Np.SetProperty(value, ref storage, propName);

        //! @copydoc ChangeNotifier::RaisePropertyChanged
        protected void RaisePropertyChanged(string propName)
            => Np.RaisePropertyChanged(propName);
        #endregion
    }
}