C#のWinFormsでINotifyPropertyChangedを使ってコントロールとデータバインディングしてみる。3

C# コンピュータ
C#

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;

コメント