みんな大好き Windows フォームアプリでデータバインディングを改めて試してみた記事を以前に書きました。
rksoftware.hatenablog.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 を使ってデータバインディングしてみました。
いかがでしたか?