WPFプログラミング備忘録

WPF コマンドとコマンドパラメータについて

WPF

はじめに

この記事ではコマンドとコマンドパラメータの使い方について確認しています。

開発環境は以下のとおりです。

オペレーティングシステムWindows10 x64
Visual StudioMicrosoft Visual Studio Community 2019 Version 16.11.2
.NET Framework4.7.2

アプリケーションの仕様

まず起動時の画面は以下のとおりです。コピーボタンと貼り付けボタンは無効化されています。

起動時の画面

コピーボタンの隣のテキストボックスに文字を入力するとコピーボタンが有効化されるようにします。

テキストボックスに入力

コピーボタンを押すと貼り付けボタンが有効化されるようにします。

コピーボタンを押す

貼り付けボタンを押すと右側のテキストブロックにコピーした文字列を表示します。貼り付けが終わると貼り付けボタンは再度無効化されるようにします。

貼り付けボタンを押す

リセットボタンを押すと起動時の画面に戻るようにします。

リセットボタンを押す

プロジェクトのフォルダ構成とファイル

WPFアプリケーションのプロジェクト作成したら、プロジェクト内にViewsフォルダ、ViewModelsフォルダ、およびCommandsフォルダを作成します。

フォルダの作成手順 「プロジェクトを右クリック -> 追加 -> 新しいフォルダー」

自動的に生成されるMainWindow.xamlファイルは削除します。

そして下図のように各フォルダに必要なファイルを作成します。

①Commandsフォルダ

  • Commands.cs

作成手順 「Commandsフォルダを右クリック -> 追加 -> クラス」

②ViewModelsフォルダ

  • MainWindowViewModel.cs

作成手順 「ViewModelsフォルダを右クリック -> 追加 -> クラス」

③Viewsフォルダ

  • MainWindow.xaml

作成手順 「Viewsフォルダを右クリック -> 追加 -> ウィンドウ(WPF)」

Viewを起動するためにApp.xamlファイルのStartupUri属性の値を以下のとおり変更してください。

<Application x:Class="WpfMVVMSample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfMVVMSample"
             StartupUri="Views\MainWindow.xaml">
    <Application.Resources>
         
    </Application.Resources>
</Application>

コマンドの定義

コピー、貼り付け、リセットそれぞれの処理をパラメータで振り分けます。

ICommandインターフェースのメンバーを実装しています。

public interface ICommand
{
    bool CanExecute(object parameter);
    void Execute(object parameter);
    event EventHandler CanExecuteChanged;
}

Commands.csは以下のとおりです。

using System;
using System.Windows;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using WpfMVVMSample.ViewModels;

namespace WpfMVVMSample.Commands
{
    enum ButtonType
    {
        Copy,
        Paste,
        Reset
    }
    class Commands : ICommand
    {
        MainWindowViewModel _vm;

        public Commands(MainWindowViewModel vm)
        {
            _vm = vm;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            var buttonType = (ButtonType)Enum.Parse(typeof(ButtonType), (string)parameter, true);

            switch (buttonType)
            {
                case ButtonType.Copy:
                    return _vm.CopyText == null ? false : !string.IsNullOrWhiteSpace(_vm.CopyText);

                case ButtonType.Paste:
                    return !string.IsNullOrWhiteSpace(Clipboard.GetText());

                case ButtonType.Reset:
                    return true; // リセットボタンは常に有効にするため無条件にtrueを返します

                default:
                    throw new NotImplementedException();
            }
        }

        public void Execute(object parameter)
        {
            var buttonType = (ButtonType)Enum.Parse(typeof(ButtonType), (string)parameter, true);

            switch (buttonType)
            {
                case ButtonType.Copy:
                    if (!string.IsNullOrWhiteSpace(_vm.CopyText))
                    {
                        // クリップボードにテキストボックスの文字列を保存する
                        Clipboard.SetData(DataFormats.Text, _vm.CopyText);
                    }
                    break;

                case ButtonType.Paste:
                    _vm.PasteText = Clipboard.GetText();

                    // テキストブロックに貼り付けた後はクリップボードをクリアする
                    // クリップボードが空なので貼り付けボタンは無効化される
                    Clipboard.Clear();
                    break;

                case ButtonType.Reset:
                    // リセット処理をする
                    Clipboard.Clear();
                    _vm.CopyText = string.Empty;
                    _vm.PasteText = string.Empty;
                    break;

                default:
                    throw new NotImplementedException();
            }
        }
    }
}

押されたボタンの種類を判別するためにenum ButtonType列挙型を定義しています。

CanExecuteメソッドとExecuteメソッドでは引数parameterを使用して場合分けをしています。 parameterには後ほど説明するViewのCommandParameter属性の文字列が渡されます。

CanExecuteChangedイベントの定義は定石のようです。

ViewModelの定義

ViewModelの定義は以下のとおりです。

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

namespace WpfMVVMSample.ViewModels
{
    class MainWindowViewModel : INotifyPropertyChanged
    {
        ICommand _commands;
        public MainWindowViewModel()
        {
            _commands = new WpfMVVMSample.Commands.Commands(this);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        string _copyText;

        string _pasteText;

        public string CopyText
        {
            get { return _copyText; }
            set
            {
                if(value != _copyText)
                {
                    _copyText = value;
                    NotifyPropertyChanged();
                }
            }
        }
        public string PasteText
        {
            get { return _pasteText; }
            set
            {
                if (value != _pasteText)
                {
                    _pasteText = value;
                    NotifyPropertyChanged();
                }
            }
        }
        public ICommand Commands
        {
            get { return _commands; }
        }
    }
}

コンストラクタではコマンドのインスタンスを生成しています。

またViewの中でコマンドをバインドするためICommand型のプロパティとして公開しています。

Viewの定義

Viewの画面とコードビハインドは以下のとおりです。

<Window x:Class="WpfMVVMSample.Views.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:WpfMVVMSample.Views"
        mc:Ignorable="d"
        Title="Command Sample" Height="160" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Margin="6">
            <Button Command="{Binding Commands}" CommandParameter="Copy" Content="コピー" Width="60" Margin="6"/>
            <TextBox Text="{Binding CopyText, UpdateSourceTrigger=PropertyChanged}" Width="240" Height="30" VerticalContentAlignment="Center"/>
        </StackPanel>
        <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="6">
            <Button Command="{Binding Commands}" CommandParameter="Paste" Content="貼り付け" Width="60" Margin="6"/>
            <TextBlock Text="{Binding PasteText}" Width="240" Height="30" />
        </StackPanel>
        <Button Command="{Binding Commands}" CommandParameter="Reset" Grid.Row="2" Content="リセット" Width="60" Margin="6"/>
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace WpfMVVMSample.Views
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Clipboard.Clear();

            DataContext = new WpfMVVMSample.ViewModels.MainWindowViewModel();
        }
    }
}

コンストラクタの中ではDataContextにViewModelのインスタンスを設定しています。

コピー、貼り付け、リセットの各ボタンではCommandParameter属性を指定しています。

CommandParameter属性の値がICommandインターフェースを実装しているCommandsクラスのCanExecuteメソッドとExecuteメソッドの引数parameterに文字列として渡されます。

以上の実装で仕様どおりの動作をすることが確認できます。

なお、このアプリケーションはMVVMパターンライクな形になっています。Modelに相当するパーツはありません。

MVVMパターンの各パーツの関係性は下図のとおりです。

MVVMパターン

本アプリケーションではコマンドパラメータを使用して処理を振り分けましたが、本来は1つのコマンドでは1つの処理を実装することが好ましいです。そのような実装例については以下の記事をご確認ください。

>> WPF MVVMパターンとコマンドについて

今回はコマンドとコマンドパラメータの使い方について確認しました。

最後に参考書籍について紹介致します。

以下の2冊はWPFの機能が網羅的にまとめられています。基礎から応用までバランス良く解説されており手元に置いておくと何かと重宝します。洋書ですが平易な英語で書かれておりプログラムコードとの比率も適切で、英語が苦手な方でも読めると思いますのでお勧めします。

以上です。

コメント