VB のたまご

作成日: 2017/03/20, 更新日: 2017/03/20



コマンド

    イメージ
  •  ここでは、コマンドについて学習していきます。どこの部分かと言うと ViewModel ←→ View の部分です。

  • スポンサーリンク


  •  コマンドは View から独立するために生まれた振る舞いを担当する機構です。 従来のプログラムでは、プログラムに処理を書く時、コントロールを配置して、コントロールのイベント処理を書いて作ります。 コントロール(View)管理のイベント処理は、コードビハインドでしか扱うことができず、振る舞いは View に依存してしまうポジションでした。 画面とそれ以外(イベント処理)の分離ができないということです。

  •  また、イベント処理がコントロールベースのため、同じ処理を実装するのに、コントロール数分書く必要がありました(共有イベント処理みたいなことができない)。

  • イメージ
  •  この背景から考え出されたのがコマンドという振る舞いです。「コントロールの数だけ」(かぶるかもしれない)振る舞いを用意するのではなく、 「機能の数だけ」振る舞いを用意することで、実装の手間を省きます。

  •  また、「イベント処理の代わりの振る舞い」という担当の他に、「振る舞いを実行できるかどうか」を通知する担当も受け持ちます。 例えば、TextBox にファイルのフルパスをセットしておき、Button をクリックすることでファイルを読み込む処理を想定した場合、 TextBox にセットされた文字列が存在するファイルじゃないと、ファイル読み込み処理をおこなうことができません。 通常、「振る舞いを実行できるかどうか」はコントロールの活性/グレーアウトに反映されます。

  •  コマンドを実装するためには、コマンドを定義するインターフェースを継承して実装します。 「ICommand」というインターフェースです。

  • Imports System.ComponentModel
    Imports System.Runtime.CompilerServices
    Imports System.Windows.Markup
    
    Namespace System.Windows.Input
        '
        ' 概要:
        '     コマンドを定義します。
        <TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")> <TypeForwardedFrom("PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")> <ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")>
        Public Interface ICommand
            '
            ' 概要:
            '     コマンドを実行するかどうかに影響するような変更があった場合に発生します。
            Event CanExecuteChanged As EventHandler
    
            '
            ' 概要:
            '     コマンドの起動時に呼び出されるメソッドを定義します。
            '
            ' パラメーター:
            '   parameter:
            '     コマンドで使用されたデータ。コマンドにデータを渡す必要がない場合は、このオブジェクトを null に設定できます。
            Sub Execute(parameter As Object)
    
            '
            ' 概要:
            '     現在の状態でこのコマンドを実行できるかどうかを判断するメソッドを定義します。
            '
            ' パラメーター:
            '   parameter:
            '     コマンドで使用されたデータ。コマンドにデータを渡す必要がない場合は、このオブジェクトを null に設定できます。
            '
            ' 戻り値:
            '     このコマンドを実行できる場合は true。それ以外の場合は false。
            Function CanExecute(parameter As Object) As Boolean
        End Interface
    End Namespace
    

  •  「ICommand」は、「CanExecuteChanged」イベント、「Execute」メソッド、「CanExecute」メソッドを持つインターフェースです。 とりあえずよく分からなくていいので、どう動くのか見てみましょう。

  • スポンサーリンク


    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices
    
    Public Class NotificationObject
        Implements INotifyPropertyChanged
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        ' System.Runtime.CompilerServices 名前空間の CallerMemberName 属性を付けると、
        ' 呼び出し元のプロパティ名が自動的にセットされる(ので、呼び出し元ではいちいち文字列指定しなくてもセットしてくれるので楽)
        Protected Sub RaisePropertyChanged(<CallerMemberName> Optional propertyName As String = "")
    
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    
        End Sub
    
    End Class
    

    Public Class DelegateCommand
        Implements ICommand
    
        Private executeMethod As Action(Of Object) ' Object 型の引数を受け取る Sub メソッド
        Private canExecuteMethod As Func(Of Object, Boolean) ' Object 型の引数を受け取り、Boolean 型を返却する Function メソッド
    
        ' コンストラクタ
        ' 処理の実行有無を判定するメソッドは、無条件で True を返却(常に実行可能と判定)する
        Public Sub New(executeMethod As Action(Of Object))
    
            Me.New(executeMethod, Function(x) True)
    
        End Sub
    
        ' コンストラクタ
        Public Sub New(executeMethod As Action(Of Object), canExecuteMethod As Func(Of Object, Boolean))
    
            Me.executeMethod = executeMethod
            Me.canExecuteMethod = canExecuteMethod
    
        End Sub
    
    #Region "ICommand"
    
        ' 渡されてきた処理を使って、処理を実行
        Public Sub Execute(parameter As Object) Implements ICommand.Execute
    
            Me.executeMethod(parameter)
    
        End Sub
    
        ' 渡されてきた判定処理を使って、処理の実行有無を判定
        Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    
            Return Me.canExecuteMethod(parameter)
    
        End Function
    
        Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    
    #End Region
    
        ' 最新の処理実行有無判定を View にしてもらうようにイベント通知
        Public Sub RaiseCanExecuteChanged()
    
            RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
    
        End Sub
    
    End Class
    

    Public Class Class1
        Inherits NotificationObject
    
    #Region "これで1つのプロパティ"
    
        Private _MyName As String = "taro"
        Public Property MyName As String
            Get
                Return _MyName
            End Get
            Set(value As String)
    
                ' 変更されたかどうかを判定
                If _MyName = value Then
                    Return
                End If
    
                _MyName = value
                ' イベント発生
                Me.RaisePropertyChanged()
    
                ' _HelloCommand 変数が Nothing じゃない場合は、
                ' RaiseCanExecuteChanged メソッドを呼び出して、ボタンの活性状態を更新
                Me._HelloCommand?.RaiseCanExecuteChanged()
    
            End Set
        End Property
    
    #End Region
    
    #Region "これで1つのコマンド"
    
        Private _HelloCommand As DelegateCommand = Nothing
        Public ReadOnly Property HelloCommand As DelegateCommand
            Get
    
                If Me._HelloCommand Is Nothing Then
                    Me._HelloCommand = New DelegateCommand(AddressOf Me.Hello, AddressOf Me.CanHello)
                End If
    
                Return Me._HelloCommand
    
            End Get
        End Property
    
        Private Sub Hello(parameter As Object)
    
            ' View から値を受け取ってもいいし、直接メンバー値を参照してもいい
            'MessageBox.Show($"Hello, {Me.MyName}!")
            MessageBox.Show($"Hello, {parameter}!")
    
        End Sub
    
        Private Function CanHello(parameter As Object) As Boolean
    
            ' メソッドの動き確認用
            If String.IsNullOrWhiteSpace(Me.MyName) Then
                Console.WriteLine("適切な値ではないので、ボタンをグレーアウトします")
            Else
                Console.WriteLine("適切な値なので、ボタンを活性化します")
            End If
    
            Return Not String.IsNullOrWhiteSpace(Me.MyName)
    
        End Function
    
    #End Region
    
    End Class
    

    <Window x:Class="CommandWindow1"
            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:w08_Commands"
            mc:Ignorable="d"
            Title="CommandWindow1" Height="300" Width="300">
    
        <Window.DataContext>
            <local:Class1 />
        </Window.DataContext>
    
        <StackPanel Margin="10">
    
            <TextBox 
                Text="{Binding MyName, UpdateSourceTrigger=PropertyChanged}" />
            
            <Button 
                Content="Hello" 
                Command="{Binding HelloCommand}"
                CommandParameter="{Binding MyName}" />
    
        </StackPanel>
    
    </Window>
    

  •  できたら実行してみましょう。 ボタンを押すと、入力している文字列がメッセージボックスに表示されます。 次に、入力欄の文字列を全部消してみましょう。消したタイミングでボタンがグレーアウトして押せなくなりました。 再度何か文字を入力すると、ボタンが活性化して押せるようになります。

  •  入力欄への文字の増減するタイミングで、Visual Studio の出力タブに表示されるメッセージを見てください。 ちゃんと、CanExecuteChanged イベントが発生して、処理の実行有無メソッドが走っていますね。

  •  View 側で、CommandParameter にバインドしているデータを渡しているので、 Hello メソッドも CanHello メソッドも、引数経由で受け取ることができます。この値を見てもいいですし、 同じクラス内のメンバーとして定義しているので直接見ても良いです。

  •  ここでは、ICommand を継承した自前のコマンドを学習しました。 コマンドは他に、RoutedCommand, RoutedUICommand などがありますがここでは割愛します。