自由テーマ

GUI

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

PowerShellでXAML GUIを構築する際、MVVMパターンを適用し保守性を高める手法を解説する。

MVVMパターン概要

Model-View-ViewModel (MVVM) パターンは、UI (View) とビジネスロジック (Model) を分離し、テスト容易性、保守性、再利用性を向上させるアーキテクチャパターンである。WPFやUWPといったデータバインディングを多用するフレームワークで特に効果を発揮する。

  • Model: アプリケーションのデータとビジネスロジックを管理する。UIに依存しない。
  • View: ユーザーインターフェース。XAMLで定義され、ViewModelのデータを表示し、ユーザー入力をViewModelに伝える。
  • ViewModel: ViewとModelの間の仲介役。ModelのデータをViewに表示可能な形式で提供し、ViewからのコマンドをModelに渡す。INotifyPropertyChangedインターフェースを実装し、データ変更をViewに通知する。
graph TD
  subgraph UI[UI Layer]
    View[View]
  end

  subgraph Logic[Logic Layer]
    VM[ViewModel]
    M[Model]
  end

  View -->|データバインディング(表示)| VM
  View -->|コマンドバインディング(操作)| VM
  VM   -->|更新通知(INotifyPropertyChanged)| View

  VM   -->|データ要求/更新| M
  M    -->|データ提供/結果通知| VM

  style View fill:#f9f,stroke:#333,stroke-width:2px
  style VM   fill:#ccf,stroke:#333,stroke-width:2px
  style M    fill:#cfc,stroke:#333,stroke-width:2px

PowerShellにおけるMVVMの実装アプローチ

PowerShellでXAML GUIを構築する場合、WPFのデータバインディング機構を最大限に活用するためにMVVMパターンを適用する。

INotifyPropertyChangedの実装

WPFのデータバインディングは、ViewModelのプロパティ変更をViewに通知するためSystem.ComponentModel.INotifyPropertyChangedインターフェースを必要とする。PowerShellクラスでこのインターフェースを実装するには、add_PropertyChangedおよびremove_PropertyChangedメソッドと、RaisePropertyChangedメソッドを定義する。

ICommandの実装

ViewからViewModelの操作を呼び出すため、WPFはSystem.Windows.Input.ICommandインターフェースを使用する。PowerShellでは、このインターフェースを実装するカスタムクラス(一般的にRelayCommandまたはDelegateCommandと呼ばれる)をAdd-Typeで定義し、ViewModelから利用する。

XAMLとのデータバインディング

XAMLで定義されたViewは、DataContextプロパティを通じてViewModelと接続される。View内のコントロールは、{Binding Path=PropertyName}構文でViewModelのプロパティにバインドされ、Command="{Binding CommandName}"構文でViewModelのコマンドにバインドされる。

実装例

以下に、簡単なカウンターアプリケーションをMVVMパターンで実装するPowerShellスクリプトを示す。

1. XAML定義 (View)

<Window x:Class="MvvmExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MVVM Example" Height="200" Width="300">
    <StackPanel Margin="10">
        <TextBlock Text="{Binding Value}" FontSize="36" HorizontalAlignment="Center" Margin="0,0,0,10"/>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Content="Increment" Command="{Binding IncrementCommand}" Width="80" Height="30" Margin="5"/>
            <Button Content="Decrement" Command="{Binding DecrementCommand}" Width="80" Height="30" Margin="5"/>
        </StackPanel>
    </StackPanel>
</Window>

2. PowerShellスクリプト (ViewModelおよびView/ViewModelの結合)

# XAML定義
$xaml = @"
<Window x:Class="MvvmExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MVVM Example" Height="200" Width="300">
    <StackPanel Margin="10">
        <TextBlock Text="{Binding Value}" FontSize="36" HorizontalAlignment="Center" Margin="0,0,0,10"/>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Content="Increment" Command="{Binding IncrementCommand}" Width="80" Height="30" Margin="5"/>
            <Button Content="Decrement" Command="{Binding DecrementCommand}" Width="80" Height="30" Margin="5"/>
        </StackPanel>
    </StackPanel>
</Window>
"@

# RelayCommandクラスの定義 (C#コードをAdd-Typeで追加)
# ICommandインターフェースを実装し、Actionデリゲートでコマンド実行ロジックをラップする
Add-Type -TypeDefinition @'
using System;
using System.Windows.Input;
using System.ComponentModel; // For INotifyPropertyChanged

namespace PowerShell.Commands {
    public class RelayCommand : ICommand {
        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;

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

        public RelayCommand(Action<object> execute, Predicate<object> canExecute = null) {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter) {
            return _canExecute == null || _canExecute(parameter);
        }

        public void Execute(object parameter) {
            _execute(parameter);
        }
    }
}
'@ -ErrorAction Stop

# ViewModelの定義
# System.ComponentModel.INotifyPropertyChangedを実装
class CounterViewModel : System.ComponentModel.INotifyPropertyChanged {
    [int]$_value
    hidden [System.ComponentModel.PropertyChangedEventHandler]$_propertyChangedEventHandler

    CounterViewModel() {
        $this._value = 0
        $this.IncrementCommand = [PowerShell.Commands.RelayCommand]::new([Action[object]]{ $this.Value++ })
        $this.DecrementCommand = [PowerShell.Commands.RelayCommand]::new([Action[object]]{ $this.Value-- })
    }

    # INotifyPropertyChanged インターフェースのイベントハンドラ実装
    [event]add_PropertyChanged([System.ComponentModel.PropertyChangedEventHandler]$handler) {
        $this._propertyChangedEventHandler += $handler
    }
    [event]remove_PropertyChanged([System.ComponentModel.PropertyChangedEventHandler]$handler) {
        $this._propertyChangedEventHandler -= $handler
    }

    # プロパティ変更通知を発行するメソッド
    [void]RaisePropertyChanged([string]$propertyName) {
        if ($this._propertyChangedEventHandler) {
            $this._propertyChangedEventHandler.Invoke($this, [System.ComponentModel.PropertyChangedEventArgs]::new($propertyName))
        }
    }

    # カウンターの値プロパティ
    [int]$Value {
        get { return $this._value }
        set {
            if ($this._value -ne $args[0]) {
                $this._value = $args[0]
                $this.RaisePropertyChanged('Value') # プロパティ変更を通知
            }
        }
    }

    # コマンドプロパティ
    [System.Windows.Input.ICommand]$IncrementCommand
    [System.Windows.Input.ICommand]$DecrementCommand
}

# ViewとViewModelの結合およびアプリケーション実行
try {
    # XAMLのロード
    [xml]$parsedXaml = $xaml
    $reader = [System.Xml.XmlNodeReader]::new($parsedXaml)
    $window = [System.Windows.Markup.XamlReader]::Load($reader)

    # ViewModelのインスタンス作成
    $viewModel = [CounterViewModel]::new()

    # ViewのDataContextにViewModelを設定
    $window.DataContext = $viewModel

    # ウィンドウ表示
    $window.ShowDialog()
} catch {
    Write-Error "Error loading or displaying the window: $($_.Exception.Message)"
}
ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

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