みんな大好き WinForms で、データバインディングが話題なので改めて試してみました。 試した動作はこんな感じです。
きちんと動作しています。
TextBox への入力の変化で別の TextBox の値を変え、ボタンにバインドした Command も CanExecute も含めてきちんと動作しています。 すばらしいですね。
これで、いつでも新規 WinForms 案件に安心して挑めますね!
■ コード概要
WPF アプリと WinForms アプリで VM を共有して試してみました。
WinForms のコード
namespace WinForms; public partial class Form1 : Form { public Form1() { InitializeComponent(); Mvvm.ViewModel dataSource = new(); this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", dataSource, "Num1", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged)); this.textBox2.DataBindings.Add(new System.Windows.Forms.Binding("Text", dataSource, "Num2", true)); this.textBox3.DataBindings.Add(new System.Windows.Forms.Binding("Text", dataSource, "Num3", true)); this.textBox4.DataBindings.Add(new System.Windows.Forms.Binding("Text", dataSource, "Num3", true)); this.textBox5.DataBindings.Add(new System.Windows.Forms.Binding("Text", dataSource, "Num3", true)); this.button1.DataBindings.Add(new System.Windows.Forms.Binding("Command", dataSource, "Command", true)); } }
WPF のコード
<Window x:Class="Wpf.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Wpf" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:vm="clr-namespace:Mvvm;assembly=Mvvm"> <Window.DataContext> <vm:ViewModel/> </Window.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="1" Text="+"/> <TextBlock Grid.Column="3" Text="="/> <TextBox Grid.Column="0" Text="{Binding Num1, UpdateSourceTrigger=PropertyChanged}"/> <TextBox Grid.Column="2" Text="{Binding Num2, UpdateSourceTrigger=LostFocus}"/> <TextBox Grid.Row="0" Grid.Column="4" Text="{Binding Num3, Mode=OneWay}"/> <TextBox Grid.Row="1" Grid.Column="4" Text="{Binding Num3, Mode=OneWayToSource}"/> <TextBox Grid.Row="2" Grid.Column="4" Text="{Binding Num3, Mode=TwoWay}"/> <Button Grid.Row="2" Content="ボタン" Command="{Binding Command}"/> </Grid> </Window>
ViewModel のコード
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.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; namespace Mvvm { internal class Command : ICommand { public event EventHandler? CanExecuteChanged; internal required Action ExecuteAction { get; init; } internal required Func<bool> CanExecuteFunc { get; init; } public bool CanExecute(object? parameter) => CanExecuteFunc.Invoke(); public void Execute(object? parameter) => ExecuteAction.Invoke(); internal void InvokeCanExecuteChanged(object sender) => CanExecuteChanged?.Invoke(sender, new EventArgs()); } }
■ デザイナで
WinForms のデザイナでも設定ができるようですが、設定してみても動きませんでした。
取り敢えずコードで書いて動いたからいいかなと。実際デザイナで設定するのは効率悪いし、デザイナから作られるソースを読むのも効率悪いし、コードで書くのが良い気がします。
Form に DataContext を設定
ソリューションエクスプローラーでプロパティに ViewModel のクラスが生えましたが、Form の DataContext は空です。なぜ。
TextBox の DataContext
プロパティまで指定することしかできません。
詳細 から設定すると、Text プロパティに ViewModel のプロパティを設定できますね。
これが正しそう。値変更のタイミングも選択できます。
Button の Command
どうもデザイナで Command 設定できないです。なぜ。
こうデザイナで設定自体はあるのですが、扱い方をまだ把握できていません。。。
■ WPF での Mode は?
TwoWay とか OneWayToSource とかのアレですが、
public Binding(string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString, IFormatProvider formatInfo)
引数を見てもそれらしいプロパティないですね。
■ とにかく
私が扱い方を把握しきれていませんがとにかく、WinForms でも MVVM できたきがします。
■ 追記
DataContext プロパティ、WPF 等と同じように、親コントロールのものを子コントロールも持つようですね。こんなことができました。これはいいですね。
namespace WinForms; public partial class Form1 : Form { public Form1() { 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"); static void AddBinding(Control control, string property, string dataMember) => control.DataBindings.Add(property, control.DataContext, dataMember, true); } }