rksoftware

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

まだまだ現役! Windows フォームアプリの新機能を確認する (3)

devblogs.microsoft.com

最近あまり面白い話題が少ない感じでしたがついに来ました。みんな大好き Windows フォームアプリのお話です。
↑ のブログ記事。見ていきましょう!

※ 現在 WPF でなく WinForms を愛用している日本の IT 技術者が WinForms アプリを MVVM で作りたいと思っているかは別として。

■ BindableComponent クラス

BindableComponent クラスが追加されたとのことです。
確かに、バージョン 7 からとなっています。
learn.microsoft.com

で、このクラスは何者でしょう?

プロパティとして

  • BindingContext
  • DataBindings

を持っているので、このためのクラスなのでしょう。
そして、この BindableComponent クラス。 learn.microsoft.com そしてさらにそこから派生するのが、

  • System.Windows.Forms.ToolStripButton
  • System.Windows.Forms.ToolStripControlHost
  • System.Windows.Forms.ToolStripDropDownItem
  • System.Windows.Forms.ToolStripLabel
  • System.Windows.Forms.ToolStripSeparator

さらに先まで派生をみると

  • System.Windows.Forms.ToolStripComboBox
  • System.Windows.Forms.ToolStripProgressBar
  • System.Windows.Forms.ToolStripTextBox
  • System.Windows.Forms.ToolStripDropDownButton
  • System.Windows.Forms.ToolStripMenuItem
  • System.Windows.Forms.ToolStripSplitButton
  • System.Windows.Forms.ToolStripStatusLabel

こららの画面コントロールがバインディングに対応したという事なのでしょう。つまりツールバーメニューに Command をバインドできるという事ですね。
ちょっと、コントロールが多いので代表して ToolStripButton を試してみます。

やってみましょう。

■ 検証コード

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 ViewModel()
    {
        ButtonCommand = new Command((() => Text += $"Button clicked\n"), canExecute);

        bool canExecute() => _enabled;
    }
}
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 bitmap = new System.Drawing.Bitmap(24, 24);
            System.Drawing.Graphics.FromImage(bitmap).FillRectangle(System.Drawing.Brushes.Aqua, new System.Drawing.Rectangle(0, 0, 24, 24));
            var toolStripButton1 = new System.Windows.Forms.ToolStripButton
            {
                Image = bitmap,
                Size = new System.Drawing.Size(29, 24),
            };
            var toolStrip1 = new System.Windows.Forms.ToolStrip
            {
                ImageScalingSize = new System.Drawing.Size(20, 20),
                Location = new System.Drawing.Point(0, 0),
                Size = new System.Drawing.Size(800, 27)
            };
            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",
            };

            toolStripButton1.DataBindings.Add(new System.Windows.Forms.Binding("Command", bindingSource, "ButtonCommand", true));
            label.DataBindings.Add(new System.Windows.Forms.Binding("Text", bindingSource, "Text", true));

            toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripButton1 });
            this.Controls.Add(toolStrip1);
            this.Controls.Add(label);
        }

    }
}

実行結果

計算通り! 画面上部のツールバーのボタン (aqua 色の四角) をクリックするとテキストが更新されました。

■ コードの要点

バインディングしているところ実際前回と同じなので今回少し手こずったところ。

var bitmap = new System.Drawing.Bitmap(24, 24);
System.Drawing.Graphics.FromImage(bitmap).FillRectangle(System.Drawing.Brushes.Aqua, new System.Drawing.Rectangle(0, 0, 24, 24));
var toolStripButton1 = new System.Windows.Forms.ToolStripButton
{
    Image = bitmap,
    Size = new System.Drawing.Size(29, 24),
};

ToolStripButton は見た目として画像を設定するコントロールでした。検証コードでプロジェクトに画像を追加して、というのはやりたくなかったので古式ゆかしい GDI+ です。GDI+、太古の技術すぎてコードを忘れていました。しかし大事なことです。GDI+ なしには WinForms は語れません。思い出せてよかったです。

次回

次回は CommandParameter を見ていきましょう。