最近あまり面白い話題が少ない感じでしたがついに来ました。みんな大好き Windows フォームアプリのお話です。
↑ のブログ記事。見ていきましょう!
※ 現在 WPF でなく WinForms を愛用している日本の IT 技術者が WinForms アプリを MVVM で作りたいと思っているかは別として。
■ .NET 7 での WinForms の Command バインドの新機能
WinForms のボタンとツールバーアイテムに ICommand をバインドするためのアップデートがあるそうです。
アップデートされた要素を一つずつ確認して行きたいのですが、その前に気になったところが。ICommand の実装例も掲載されています。この中に気になるコードがありました。
※コメントや改行は私の好みに書き換えています。
public class RelayCommand : ICommand { public event EventHandler? CanExecuteChanged; private readonly Action _commandAction; private readonly Func<bool>? _canExecuteCommandAction; public RelayCommand(Action commandAction, Func<bool>? canExecuteCommandAction = null) { ArgumentNullException.ThrowIfNull(commandAction, nameof(commandAction)); _commandAction = commandAction; _canExecuteCommandAction = canExecuteCommandAction; } bool ICommand.CanExecute(object? parameter) => _canExecuteCommandAction is null || _canExecuteCommandAction.Invoke(); void ICommand.Execute(object? parameter) => _commandAction.Invoke(); public void NotifyCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
この中のここです。
_canExecuteCommandAction is null || _canExecuteCommandAction.Invoke();
なるほど! そういうのもあるのか! という感じでした。普段何も考えずに
_canExecuteCommandAction?.Invoke() ?? true;
と書いていました。覚えておこう。
■ ButtonBase クラスの Comand プロパティ
ButtonBase クラスに Comand プロパティが追加されました。
ButtonBase は .NET Framework 1.1 からある由緒正しい老舗クラスですね。
learn.microsoft.com
Comand プロパティは .NET 7 から。 learn.microsoft.com
記事にも書かれていますが、ButtonBase の派生クラスである System.Windows.Forms.Button、System.Windows.Forms.CheckBox、System.Windows.Forms.RadioButton で使えるはずです。試してみましょう。
■ プレビュー機能
Command プロパティを使おうと思ったらエラーで怒られが発生しました、
エラー CA2252 'Command' を使用するには、プレビュー機能を選択する必要があります。詳細については、「https://aka.ms/dotnet-warnings/preview-features」を参照してください。
対処として .csproj ファイルへ記述を追加します。
変更前
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net7.0-windows</TargetFramework> <Nullable>enable</Nullable> <UseWindowsForms>true</UseWindowsForms> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> </Project>
追加する内容
<EnablePreviewFeatures>True</EnablePreviewFeatures>
変更後
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net7.0-windows</TargetFramework> <Nullable>enable</Nullable> <UseWindowsForms>true</UseWindowsForms> <ImplicitUsings>enable</ImplicitUsings> <EnablePreviewFeatures>True</EnablePreviewFeatures> </PropertyGroup> </Project>
■ ソースコード
namespace WinFormsApp1 { public partial class Form1 : Form { public Form1() { this.components = new System.ComponentModel.Container(); InitializeComponent(); var viewModel = new ViewModel(); var bindingSource = new System.Windows.Forms.BindingSource(components); bindingSource.DataSource = viewModel; var button = new System.Windows.Forms.Button { AutoSize = true, Location = new System.Drawing.Point(16, 32), Size = new System.Drawing.Size(94, 29), Text = "button1", UseVisualStyleBackColor = true, }; var checkBox = new System.Windows.Forms.CheckBox { AutoSize = true, Location = new System.Drawing.Point(16, 76), Size = new System.Drawing.Size(101, 24), Text = "checkBox1", UseVisualStyleBackColor = true, }; var radioButton = new System.Windows.Forms.RadioButton { AutoSize = true, Location = new System.Drawing.Point(16, 108), Size = new System.Drawing.Size(117, 24), TabStop = true, Text = "radioButton1", UseVisualStyleBackColor = true, }; var enabledChange = new System.Windows.Forms.Button { AutoSize = true, Location = new System.Drawing.Point(16, 140), Size = new System.Drawing.Size(94, 29), Text = "EnabledChange", UseVisualStyleBackColor = true, }; var label = new System.Windows.Forms.Label { AutoSize = true, Location = new System.Drawing.Point(20, 172), Size = new System.Drawing.Size(50, 20), Text = "label1", }; button.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "ButtonCommand", true)); checkBox.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "CheckBoxCommand", true)); radioButton.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "RadioButtonCommand", true)); enabledChange.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "EnabledChangeCommand", true)); label.DataBindings.Add(new System.Windows.Forms.Binding("Text", bindingSource, "Text", true)); this.Controls.Add(button); this.Controls.Add(checkBox); this.Controls.Add(radioButton); this.Controls.Add(enabledChange); this.Controls.Add(label); } } }
public class BindableBase : System.ComponentModel.INotifyPropertyChanged { public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; protected bool SetProperty<T>(ref T property, T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") { if (Object.Equals(property, value)) return false; property = value; PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); return true; } }
class Command : System.Windows.Input.ICommand { public event EventHandler? CanExecuteChanged; private readonly Action _commandAction; private readonly Func<bool>? _canExecuteCommandAction; public Command(Action commandAction, Func<bool>? canExecuteCommandAction = null) { _commandAction = commandAction; _canExecuteCommandAction = canExecuteCommandAction; } public bool CanExecute(object? parameter) => _canExecuteCommandAction?.Invoke() ?? true; public void Execute(object? parameter) => _commandAction.Invoke(); public void NotifyCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
public class ViewModel : BindableBase { bool _enabled = true; private string _text; public string Text { get => _text; private set => SetProperty(ref _text, value); } public System.Windows.Input.ICommand ButtonCommand { get; init; } public System.Windows.Input.ICommand CheckBoxCommand { get; init; } public System.Windows.Input.ICommand RadioButtonCommand { get; init; } public System.Windows.Input.ICommand EnabledChangeCommand { get; init; } public ViewModel() { ButtonCommand = new Command((() => Text += $"Button clicked\n"), canExecute); CheckBoxCommand = new Command((() => Text += $"CheckBox clicked\n"), canExecute); RadioButtonCommand = new Command((() => Text += $"RadioButton clicked\n"), canExecute); EnabledChangeCommand = new Command(enabledChange, null); bool canExecute() => _enabled; void enabledChange() { _enabled = !_enabled; Text += $"EnabledChangeButton clicked\n"; ((Command)ButtonCommand).NotifyCanExecuteChanged(); ((Command)CheckBoxCommand).NotifyCanExecuteChanged(); ((Command)RadioButtonCommand).NotifyCanExecuteChanged(); }; } }
実行結果
上から順にボタンをクリックしていきました。計算通り、バインディングされています。
■ コードの要点
↑ のコードでバインディングしているところは
var viewModel = new ViewModel(); var bindingSource = new System.Windows.Forms.BindingSource(components); bindingSource.DataSource = viewModel;
と
button.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "ButtonCommand", true)); checkBox.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "CheckBoxCommand", true)); radioButton.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "RadioButtonCommand", true)); enabledChange.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "EnabledChangeCommand", true)); label.DataBindings.Add(new System.Windows.Forms.Binding("Text", bindingSource, "Text", true));
です。
次回
次回は BindableComponent クラスを見ていきましょう。