【入門編】【中級者・実務】VB.NETにおけるRelayCommand(ICommand実装)によるクリーンな画面遷移とイベント処理 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク
スポンサーリンク

コードビハインドの呪縛を解き放て:VB.NETで「RelayCommand」を実装し、エレガントなMVVMへと昇華させる技術

こんにちは。現場の設計(アーキテクチャ)を司るチーフアーキテクトです。

あなたがもし今、「マクロの記録や、ボタンをクリックした時のイベント処理(`Button_Click`)を書き続けていたら、いつの間にかコードが数千行になっていて、どこを直せばいいか分からなくなった……」という状況に陥っているとしたら。

おめでとうございます。あなたは今、「プロフェッショナルなソフトウェアエンジニア」への入り口に立っています。

多くの初学者が、WPF(Windows Presentation Foundation)を学び始めると、つい「コードビハインド(`MainWindow.xaml.vb`)」にすべてのロジックを詰め込んでしまいがちです。しかし、それでは画面のデザインとロジックが密結合になり、テストもできず、メンテナンスも困難な「スパゲッティコード」を生んでしまいます。

今日は、その呪縛を解き放つための鍵、「RelayCommand(ICommand実装)」について、その本質と実装方法を伝授しましょう。ここをクリアすれば、あなたのVB.NETスキルは「ツール作成者」から「アプリケーション設計者」へと進化します。

1. なぜ「Button_Click」ではいけないのか?

まず、なぜコードビハインドを肥大化させてはいけないのか、その理由を整理しましょう。

従来のやり方(コードビハインド方式)

ボタンを押した時、`MainWindow.xaml.vb` にこう書きますよね。

' ❌ 避けるべき書き方:コードビハインドにロジックが集中している
Private Sub SubmitButton_Click(sender As Object, e As RoutedEventArgs) Handles SubmitButton.Click
    ' 入力チェック
    ' データベース保存
    ' 次の画面へ遷移
    ' メッセージ表示
    ' ...(ここに大量のコードが書かれる)
End Sub

これの何が問題かというと、「UI(見た目)」と「ロジック(中身)」が完全に癒着していることです。
もし将来、「ボタンではなく、キーボードのEnterキーでも同じ処理をさせたい」となった時、あるいは「この処理をユニットテスト(自動テスト)したい」となった時、このコードは非常に扱いづらくなります。

目指すべき姿(MVVMパターン)

モダンな開発では、MVVM(Model-View-ViewModel)というパターンを用います。

  • View (XAML): 見た目。ボタンは「何をするか」を知らず、「コマンド(命令)」を投げるだけ。
  • ViewModel: 司令塔。ボタンから届いた「コマンド」を受け取り、実際の処理を行う。
  • Model: データとビジネスロジック。

この「View」と「ViewModel」を繋ぐ架け橋となるのが、`ICommand` インターフェースなのです。

2. 伝令役の設計図:ICommand と RelayCommand

WPFには `ICommand` というインターフェースが用意されています。これは「実行可能であること」を保証する契約のようなものです。しかし、これをそのまま使うのは非常に面倒です。

そこで、実務では「RelayCommand(リレーコマンド)」という、命令をViewModelのメソッドへ「リレー(中継)」するための汎用的なクラスを自作するのが定石です。

それでは、魂を込めた実装を見ていきましょう。

【実装例】RelayCommand クラス

このクラスは、一度作ってしまえば、プロジェクト内のあらゆる場所で使い回せる「最強の部品」になります。

Imports System.Windows.Input

''' <summary>
''' Viewからのコマンドを受け取り、ViewModelのメソッドへ中継するクラス
''' </summary>
Public Class RelayCommand
    Implements ICommand

    ' 実行したい処理(Action)を保持
    Private ReadOnly _execute As Action(Of Object)
    ' 実行可能かどうかを判定する条件(Predicate)を保持
    Private ReadOnly _canExecute As Predicate(Of Object)

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <param name="execute">実行する処理</param>
    ''' <param name="canExecute">実行可能かどうかの判定条件(任意)</param>
    Public Sub New(execute As Action(Of Object), Optional canExecute As Predicate(Of Object) = Nothing)
        _execute = execute
        _canExecute = canExecute
    End Sub

    ''' <summary>
    ''' コマンドが現在実行可能かどうかを判定します
    ''' </summary>
    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        ' 条件が設定されていればその結果を、なければ常にTrueを返す
        Return If(_canExecute IsNot Nothing, _canExecute(parameter), True)
    End Function

    ''' <summary>
    ''' コマンドを実行します
    ''' </summary>
    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        ' 実行処理を呼び出す
        _execute(parameter)
    End Sub

    ''' <summary>
    ''' コマンドの状態(実行可能かどうか)が変わったことをViewに通知します
    ''' </summary>
    Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
End Class

アーキテクトの視点:ここがポイント!

  • `Action(Of Object)`: これにより、メソッドそのものを「引数」として渡せるようになります。これが「リレー」の正体です。
  • `CanExecute`: これを実装しておくことで、例えば「入力欄が空の時はボタンを自動的にグレーアウト(無効化)させる」といった制御が、ViewModel側から宣言的に行えるようになります。

3. 実践:ViewModelによるエレガントな制御

次に、この `RelayCommand` を使って、実際に画面遷移や処理を行う ViewModel を構築しましょう。

【実装例】MainViewModel クラス

Imports System.ComponentModel

''' <summary>
''' Viewにデータとコマンドを提供する司令塔
''' </summary>
Public Class MainViewModel
    Implements INotifyPropertyChanged

    ' プロパティ変更通知のイベント
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    ' --- プロパティ ---
    Private _statusMessage As String = "準備完了"
    Public Property StatusMessage As String
        Get
            Return _statusMessage
        End Get
        Set(value As String)
            _statusMessage = value
            ' 値が変わったらViewに通知する(これを忘れると画面が更新されません!)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(StatusMessage)))
        End Set
    End Property

    ' --- コマンド ---
    ' Viewからバインドされるボタン用のコマンド
    Public Property ExecuteActionCommand As RelayCommand

    ''' <summary>
    ''' ViewModelのコンストラクタ
    ''' </summary>
    Public Sub New()
        ' 実際の処理メソッドをRelayCommandに登録する
        ' 第1引数に実行したいメソッド、第2引数に実行条件を指定
        ExecuteActionCommand = New RelayCommand(AddressOf DoSomething, AddressOf CanDoSomething)
        
        ' 初期状態の設定
        StatusMessage = "待機中..."
    End Sub

    ' --- ロジック(メソッド) ---

    ''' <summary>
    ''' ボタンが押された時に実行されるロジック
    ''' </summary>
    Private Sub DoSomething(parameter As Object)
        ' ここに本来やりたい処理を書く(画面遷移のトリガーなど)
        StatusMessage = "処理を実行しました!"
        
        ' ※本来はここで別のViewModelへ遷移するロジックなどを呼び出します
    End Sub

    ''' <summary>
    ''' 実行可能条件の判定
    ''' </summary>
    Private Function CanDoSomething(parameter As Object) As Boolean
        ' 例えば「ステータスが待機中である時だけ」ボタンを押せるようにする
        Return StatusMessage = "待機中..."
    End Function

    ''' <summary>
    ''' プロパティ変更通知用の補助メソッド
    ''' </summary>
    Protected Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class

4. View (XAML) との結合

最後に、これらを XAML 上で紐付けます。見てください、コードビハインドには一行もロジックを書いていません。

【実装例】MainWindow.xaml

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Modern UI with RelayCommand" Height="200" Width="400">
    
    <StackPanel Margin="20" VerticalAlignment="Center">
        <!-- ステータス表示 -->
        <TextBlock Text="{Binding StatusMessage}" 
                   FontSize="18" 
                   HorizontalAlignment="Center" 
                   Margin="0,0,0,20"/>

        <!-- 
            CommandプロパティにViewModelのコマンドをバインド!
            これが「Button_Click」の代わりになります。
        -->
        <Button Content="アクション実行" 
                Command="{Binding ExecuteActionCommand}" 
                Padding="10,5"
                HorizontalAlignment="Center"/>
    </StackPanel>
</Window>

そして、`MainWindow.xaml.vb` はこれだけです。

Class MainWindow
    Public Sub New()
        InitializeComponent()
        ' ViewModelをこの画面の「DataContext(データの源泉)」としてセットする
        Me.DataContext = New MainViewModel()
    End Sub
End Class

5. 陥りやすいエラーと、それを防ぐ「プロの知恵」

ここまで辿り着いたあなたに、私が現場で何度も目にしてきた「落とし穴」を共有しておきます。

① DataContext をセットし忘れる

一番多いミスです。XAMLで `{Binding …}` と書いても、`Me.DataContext = New MainViewModel()` を忘れると、Viewは「誰に命令を頼めばいいのか」が分からず、何も起きません。

② PropertyChanged を呼び忘れる

ViewModelで値を書き換えても、`RaiseEvent PropertyChanged(…)` を呼ばないと、画面上の表示(TextBlockなど)は古いままです。「データは変わったが、Viewに伝えていない」状態です。

③ CommandParameter の型違い

`RelayCommand` に引数(`parameter`)を渡す際、XAML側で渡す型と、ViewModel側で受け取る型が一致していないと、実行時にエラーになります。型には常に敏感であってください。

まとめ:ここをクリアすれば、基本はバッチリです

いかがでしたか?
最初は「わざわざ `RelayCommand` なんてクラスを作らなくても、`Button_Click` で書いたほうが早いじゃないか」と感じるかもしれません。

しかし、アプリケーションが成長し、機能が増え、複雑な条件分岐が入り混じるようになった時、この「設計の丁寧さ」があなたを救います。

1. ICommand を使って、命令を抽象化する。
2. RelayCommand で、命令を ViewModel へリレーする。
3. ViewModel で、ロジックと状態を管理する。

このサイクルを回せるようになれば、あなたはもう「マクロの延長線上」にはいません。洗練された、堅牢なアプリケーションを構築できるエンジニアへの階段を、力強く駆け上がっているのです。

自信を持って、次の一歩を踏み出してください。応援していますよ。

タイトルとURLをコピーしました