INotifyPropertyChangedを継承したクラスのオブジェクトをViewModelとしてWinFormのコントロールとバインドすることが出来ました。今回はジェネリッククラスの勉強がてら、ジェネリッククラスを使うコードに書き換えてみます。
ソースコード
ファイル名:Form1.Designer.cs
前回から変更なし
namespace VMsample01;
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "Form1";
}
TextBox textBox1 = new()
{
Location = new Point(10, 10),
Size = new Size(500, 60),
Text = "textBox1", // 初期値
};
Label label1 = new()
{
Location = new Point(10, 70),
Size = new Size(500, 60),
Text = "label1", // 初期値
};
Button button1 = new()
{
Location = new Point(10, 140),
Size = new Size(500, 60),
Text = "button1", // 初期値
};
#endregion
}
ファイル名:BindingObject.cs
新規作成
using System.ComponentModel;
using System.Diagnostics;
namespace VMsample01;
class BindingObject<T> : INotifyPropertyChanged
{
private T? _value = default(T);
public BindingObject()
{
PropertyChanged += (o, e) => {}; // 何もしない
}
public BindingObject(T v) : this()
{
_value = v;
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
if (PropertyChanged is not null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public T? Value
{
get
{
return _value;
}
set
{
if (_value is not null && _value.Equals(value)) return;
_value = value;
OnPropertyChanged("Value");
}
}
}
ファイル名:Form1.cs
using System.Diagnostics;
namespace VMsample01;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
/*
var viewModel = new Form1ViewModel();
*/
var MyName = new BindingObject<string>("Default");
var DispName = new BindingObject<string>("Default");
/*
textBox1.DataBindings.Add(new Binding("Text", viewModel, "MyName"));
label1.DataBindings.Add(new Binding("Text", viewModel, "DispName"));
*/
textBox1.DataBindings.Add(new Binding("Text", MyName, "Value"));
label1.DataBindings.Add(new Binding("Text", DispName, "Value"));
this.Load += (sender, e) =>
{
Controls.AddRange([textBox1, label1, button1]);
};
/*
viewModel.PropertyChanged += (sender, e) =>
{
// MyName意外は処理しない
if (e.PropertyName != "MyName") return;
if (sender is null) return;
Debug.Print("1");
var vm = (Form1ViewModel)sender;
// ここで連動
vm.DispName = vm.MyName;
};
*/
MyName.PropertyChanged += (sender, e) =>
{
DispName.Value = MyName.Value;
};
}
}
説明
プログラムの振る舞いは前回と同じです。
前回まであったForm1ViewModel.csの代わりにBindingObject.csを作成しました。
こちらはBindingObject<T>()というクラスを定義していて、INotifyPropertyChangedを継承している点はForm1ViewModelと同じです。
プロパティ名はValueで固定していてValueにセットするデータの型をTで指定するジェネリッククラスにしてあります。
前回はForm1ViewModelのMyNameプロパティとDispNameプロパティとコントロールをバインドしましたが、今回はBindingObjectのMyNameオブジェクトとDispNameオブジェクトのValueプロパティとバインドするようにしてみました。
そうすることでPropertyChangedで処理すべきプロパティか確認する処理を省くことが出来ます。
BindingObjectをジェネリッククラスにしたことで、使いまわすことができ、set,getの部分を何度も書く必要が無いようにしたつもりです。
学んだこと(初めて使った構文)
default(T)でT型の初期値?をセット
private T? _value = default(T);
コンストラクタのオーバーロードで先にデフォルトのコンストラクタを呼ぶ
public BindingObject(T v) : this()
Tだと==が使えないと叱られたので.Equals()に置き換えてみました。厳密には異なるので支障があればまた考えます。
if (_value is not null && _value.Equals(value)) return;
コメント