rksoftware

Visual Studio とか C# とかが好きです

みんな大好き Windows フォームアプリでデータバインディングを改めて試してみた (2)

みんな大好き Windows フォームアプリでデータバインディングを改めて試してみた記事を以前に書きました。 rksoftware.hatenablog.com
今回のコードも足したコードはこちら

github.com

■ System.Windows.Forms.BindingSource

以前に試した時には Form の DataContext に ViewModel のインスタンスを設定していましたが、本来は Designer.cs を見ると System.Windows.Forms.BindingSource を使う様子です。ということで試してみます。

Form のコード

namespace WinForms;

public partial class Form2 : Form
{
    System.Windows.Forms.BindingSource viewModelBindingSource;

    public Form2()
    {
        InitializeComponent();

        ((System.ComponentModel.ISupportInitialize)(viewModelBindingSource)).BeginInit();
        viewModelBindingSource.DataSource = new Mvvm.ViewModel();
        ((System.ComponentModel.ISupportInitialize)(viewModelBindingSource)).EndInit();
        this.DataContext = viewModelBindingSource;

        AddBinding(textBox1, "Text", "Num1");
        AddBinding(textBox2, "Text", "Num2");
        AddBinding(textBox3, "Text", "Num3");
        AddBinding(textBox4, "Text", "Num3");
        AddBinding(textBox5, "Text", "Num3");
        AddBinding(button1, "Command", "Command");

        void AddBinding(Control control, string property, string dataMember) => control.DataBindings.Add(property, control.DataContext, dataMember, true);
    }

    private void button2_Click(object sender, EventArgs e)
    {
        var viewModelBindingSource = ((System.Windows.Forms.BindingSource)this.DataContext!);
        viewModelBindingSource.DataSource = viewModelBindingSource.DataSource is Mvvm.ViewModel ? new Mvvm.ViewModel2() : new Mvvm.ViewModel();

    }
}

ボタンを一つ追加してボタンを押すたびに ViewModel を入れ替えてみました。あとはこの辺りの System.Windows.Forms.BindingSource の設定を行っているコードなどを追加しています。

        ((System.ComponentModel.ISupportInitialize)(viewModelBindingSource)).BeginInit();
        viewModelBindingSource.DataSource = new Mvvm.ViewModel();
        ((System.ComponentModel.ISupportInitialize)(viewModelBindingSource)).EndInit();
        this.DataContext = viewModelBindingSource;

ただこの System.Windows.Forms.BindingSource、厄介にもここでインスタンス作ることができず、Designer.cs に書く必要があるようです。

    /// <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.label1 = new System.Windows.Forms.Label();
        this.label2 = new System.Windows.Forms.Label();
        this.textBox1 = new System.Windows.Forms.TextBox();
        this.textBox2 = new System.Windows.Forms.TextBox();
        this.textBox3 = new System.Windows.Forms.TextBox();
        this.textBox4 = new System.Windows.Forms.TextBox();
        this.textBox5 = new System.Windows.Forms.TextBox();
        this.button1 = new System.Windows.Forms.Button();
        this.button2 = new System.Windows.Forms.Button();
        viewModelBindingSource = new BindingSource(this.components);
        this.SuspendLayout();

どうも this.components = new System.ComponentModel.Container();this.SuspendLayout(); の間でないと this.components が null でエラーになってしまいます。そうなのかー。

ViewModel

二つになった ViewModel は Command での処理時に設定する値を変えています。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace Mvvm
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;
        bool SetProperty<T>(ref T prop, T value, [CallerMemberName] string? name = null)
        {
            if (object.Equals(prop, value)) return false;
            prop = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            ((Command)Command).InvokeCanExecuteChanged(this);
            return true;
        }

        public int? Num1 { get => _num1; set { if (SetProperty(ref _num1, value)) Add(); } } int? _num1;
        public int? Num2 { get => _num2; set { if (SetProperty(ref _num2, value)) Add(); } } int? _num2;
        public int? Num3 { get => _num3; set => SetProperty(ref _num3, value); } int? _num3;
        public ICommand Command { get; init; }

        void Add() => Num3 = Num1 + Num2;

        public ViewModel() => (Command) = (new Command { ExecuteAction = () => (Num1, Num2) = (0, 0), CanExecuteFunc = () => (Num1, Num2) != (0, 0) });
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace Mvvm
{
    public class ViewModel2 : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;
        bool SetProperty<T>(ref T prop, T value, [CallerMemberName] string? name = null)
        {
            if (object.Equals(prop, value)) return false;
            prop = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            ((Command)Command).InvokeCanExecuteChanged(this);
            return true;
        }

        public int? Num1 { get => _num1; set { if (SetProperty(ref _num1, value)) Add(); } } int? _num1;
        public int? Num2 { get => _num2; set { if (SetProperty(ref _num2, value)) Add(); } } int? _num2;
        public int? Num3 { get => _num3; set => SetProperty(ref _num3, value); } int? _num3;
        public ICommand Command { get; init; }

        void Add() => Num3 = Num1 + Num2;

        public ViewModel2() => (Command) = (new Command { ExecuteAction = () => (Num1, Num2) = (1, 1), CanExecuteFunc = () => (Num1, Num2) != (1, 1) });
    }
}

■ 実行


証拠は省きますが動きました。こういうものなんですね。

■ DataContext に直接 ViewModel をセットするとどうなるのか?

namespace WinForms;

public partial class Form2 : Form
{
    System.Windows.Forms.BindingSource viewModelBindingSource;

    public Form2()
    {
        InitializeComponent();

        this.DataContext = new Mvvm.ViewModel();

        AddBinding(textBox1, "Text", "Num1");
        AddBinding(textBox2, "Text", "Num2");
        AddBinding(textBox3, "Text", "Num3");
        AddBinding(textBox4, "Text", "Num3");
        AddBinding(textBox5, "Text", "Num3");
        AddBinding(button1, "Command", "Command");

        void AddBinding(Control control, string property, string dataMember) => control.DataBindings.Add(property, control.DataContext, dataMember, true);
    }

    private void button2_Click(object sender, EventArgs e)
    {
        this.DataContext = this.DataContext is Mvvm.ViewModel ? new Mvvm.ViewModel2() : new Mvvm.ViewModel();
    }
}

こんな感じに。

        void AddBinding(Control control, string property, string dataMember) => control.DataBindings.Add(property, control.DataContext, dataMember, true);

で control.DataContext を引数に渡しているので、コントロールのにバインドされている ViewModel がおそらく変わらなので、何も起きないです。System.Windows.Forms.BindingSource を使うのが本来の姿のようですね。

■ いかがでしたか?

今回は System.Windows.Forms.BindingSource を使ってデータバインディングしてみました。
いかがでしたか?