for WPF developers
Home Profile Tips 全記事一覧

NotificationObject クラス

(2017/03/07 15:34:40 created.)

プロパティ変更を View 側に伝達するために、ViewModel または Model は INotifyPropertyChanged インターフェースを実装しなければなりません。しかし、このインターフェースを実装するために毎回同じコードを書くべきではないので、あらかじめこのインターフェースを実装したクラスとして NotificationObject クラスを次のように定義します。

NotificationObject.cs
  1. using System.ComponentModel;
  2. using System.Runtime.CompilerServices;
  3.  
  4. /// <summary>
  5. /// INotifyPropertyChanged インターフェースを実装した抽象クラスを表します。
  6. /// </summary>
  7. public abstract class NotificationObject : INotifyPropertyChanged
  8. {
  9.     #region INotifyPropertyChanged のメンバ
  10.     /// <summary>
  11.     /// プロパティ変更時に発生します。
  12.     /// </summary>
  13.     public event PropertyChangedEventHandler PropertyChanged;
  14.     #endregion INotifyPropertyChanged のメンバ
  15.  
  16.     /// <summary>
  17.     /// PropertyChanged イベントを発行します。
  18.     /// </summary>
  19.     /// <param name="propertyName">プロパティ名を指定します。</param>
  20.     protected void RaisePropertyChanged([CallerMemberName]string propertyName = null)
  21.     {
  22.         var h = this.PropertyChanged;
  23.         if (h != null) h(this, new PropertyChangedEventArgs(propertyName));
  24.     }
  25.  
  26.     /// <summary>
  27.     /// プロパティ値変更ヘルパです。
  28.     /// </summary>
  29.     /// <typeparam name="T">プロパティの型を表します。</typeparam>
  30.     /// <param name="target">変更するプロパティの実体を指定します。</param>
  31.     /// <param name="value">変更後の値を指定します。</param>
  32.     /// <param name="propertyName">プロパティ名を指定します。</param>
  33.     /// <returns>プロパティ値に変更があった場合に true を返します。</returns>
  34.     protected bool SetProperty<T>(ref T target, T value, [CallerMemberName]string propertyName = null)
  35.     {
  36.         if (Equals(target, value)) { return false; }
  37.         target = value;
  38.         RaisePropertyChanged(propertyName);
  39.         return true;
  40.     }
  41. }

INotifyPropertyChanged インターフェースのメンバは PropertyChanged イベントだけですが、このイベントを発生させるためのメソッドを 2 つ用意しています。

RaisePropertyChanged() メソッドは、変更があったプロパティ名を指定してコールすることで、そのプロパティが変更したことを View に伝えます。プロパティ名を省略した場合は、すべてのプロパティが変更されたとして伝えられます。ただし、CallberMemberName 属性をサポートするコンパイラによってコンパイルした場合、プロパティ名を省略してもそのプロパティが変更されたとして通知されます。

SetProperty() メソッドは、プロパティ値を変更するためのヘルパです。上記の RaisePropertyChanged() メソッドは、プロパティ値をセットするときは必ずコールされるメソッドになります。したがって、プロパティ値をセットするコードと一緒にしてしまえば、プロパティをセットするコードと RaisePropertyChanged() メソッドをコールするコードが一つにまとまります。

これらのメソッドの使用例は次のようになります。

SampleViewModel.cs
  1. /// <summary>
  2. /// プロパティ変更の使用例を示すためのクラスを表します。
  3. /// </summary>
  4. public class SampleViewModel : NotificationObject
  5. {
  6.     private int _id;
  7.     /// <summary>
  8.     /// 識別子を取得または設定します。
  9.     /// </summary>
  10.     public int ID
  11.     {
  12.         get { return this._id; }
  13.         set
  14.         {
  15.             // プロパティ値に変更があるかどうかを確認します。
  16.             if (this._id != value)
  17.             {
  18.                 this._id = value;
  19.                 // 変更があった場合に変更通知をおこないます。
  20.                 RaisePropertyChanged("ID");
  21.             }
  22.         }
  23.     }
  24.  
  25.     private int _age;
  26.     /// <summary>
  27.     /// 年齢を取得または設定します。
  28.     /// </summary>
  29.     public int Age
  30.     {
  31.         get { return this._age; }
  32.         set { SetProperty(ref this._age, value); }
  33.     }
  34.  
  35.     private string _firstName;
  36.     /// <summary>
  37.     /// 名を取得または設定します。
  38.     /// </summary>
  39.     public string FirstName
  40.     {
  41.         get { return this._firstName; }
  42.         set
  43.         {
  44.             // プロパティ値に変更があった場合に true が返ります。
  45.             if (SetProperty(ref this._firstName, value))
  46.             {
  47.                 // 同時に FullName プロパティの変更通知をおこないます。
  48.                 RaisePropertyChanged("FullName");
  49.             }
  50.         }
  51.     }
  52.  
  53.     private string _lastName;
  54.     /// <summary>
  55.     /// 姓を取得または設定します。
  56.     /// </summary>
  57.     public string LastName
  58.     {
  59.         get { return this._lastName; }
  60.         set
  61.         {
  62.             // プロパティ値に変更があった場合に true が返ります。
  63.             if (SetProperty(ref this._lastName, value))
  64.             {
  65.                 // 同時に FullName プロパティの変更通知をおこないます。
  66.                 RaisePropertyChanged("FullName");
  67.             }
  68.         }
  69.     }
  70.  
  71.     /// <summary>
  72.     /// 氏名を取得します。
  73.     /// </summary>
  74.     public string FullName
  75.     {
  76.         get { return string.Format("{0} {1}", this.FirstName, this.LastName); }
  77.     }
  78. }

SetProperty() メソッドを使わずにプロパティ値の変更をおこなう場合、ID プロパティの set アクセサのように、現在の値と比較して、異なる場合に新しい値を代入し、その変更通知を RaisePropertyChanged() メソッドでおこなわなければいけません。

これに対し、SetProperty() メソッドは、Age プロパティの set アクセサのように、プロパティ値の変更とその変更通知をたったの一行で書くことができるようになります。通常のプロパティはこの Age プロパティのような書き方をお勧めします。

実際には、FullName プロパティのように、他のプロパティ値に依存して変更されるプロパティもあります。この場合、他のプロパティ値が変更されたときに、FullName プロパティの変更通知をおこなわなければなりません。このような場合でも、SetProperty() メソッドが有用となります。FirstName プロパティや LastName プロパティの set アクセサのように、SetProperty() メソッドの戻り値によって、自分のプロパティ値が変更された場合は、その変更が反映されるように RaisePropertyChanged() メソッドの引数に "FullName" を与え、自分の変更通知の後に FullName プロパティの変更通知もおこなうようにできます。