WPFを使う-ApplicationCommandsとCompositeCommandを連携させる

アプリケーションを閉じる際に、表示されているドキュメントの保存とか、バックグラウンドで実行途中の処理とかを止めるなどの処理をしなければいけなかった。

その対応をするため、前回はクローズボタンのイベント処理の実装をした。ただこれとは別にファイルメニュー内に閉じるも用意したのだが、これらを別々に処理していたら、なんかこれじゃない感があったので、別の方法が使えないかを検討した。

使用できるであろうクラス

CompositeCommand

prismでは、コマンドをまとめて管理するCompositeCommandというのがあった。各ViewModelで自分の中のコマンドを登録することで、CompositeCommandから登録済みコマンドも呼び出される。

prismのヘルプには、連携させるコマンドがある場合は、CompositeCommandを使うのがいいのではと記述されている。またヘルプでは保存を例にしていたのだが、たぶんシェルから全ViewModelに出す共通のコマンドは、CompositeCommandを使うのが本筋なのだろう。

RoutedCommand

System.Windows.InputにRoutedCommandがある。PrismのCompositeCommandに近いかもしれない。これには、さらにInputGesturesでキーアサインなどもできる。

RoutedCommandのうち、共通で使えるであろう物に関しては、ApplicationCommandsが用意されており、この中には、New、Open、Closeなどが公開されている。

xaml内のcommandにこの登録済みのコマンド名を入れると、RoutedCommandに登録されているCanExecute,Executeを実行してくれる。

  • 注意
    MenuItemにあるInputGestureTextの内容が、割り付けたコマンドのInputGesturesが表示されたら良いな、と思ったが、どうやらこれはできないみたいだ。

実装方法

RoutedCommandはViewの要素として、CompositeCommandはViewとViewModelのつなぎとして考えてみた。コマンドの例は、前回のものに引き続き「閉じる」で。

CompositeCommandの定義

まず、閉じるのコマンド群を登録するためのCompositeCommandへのインターフェース。ViewModelなどをモジュール化して「閉じる」を利用する場合、このインターフェースをViewModel側で受け取る。

public interface IApplicationCommands {
	/// <summary>
	/// 閉じるコマンド用
	/// canメソッドでは、閉じる際の保存ダイアログの表示などをさせる。
	/// 問題なければtrueを返す
	/// </summary>
	CompositeCommand closeCommand { get; }
}

View

一応、コマンドを処理するShell側。

public partial class Shell : Window {
	IApplicationCommands cmds_;
	public Shell(IApplicationCommands cmd) {
		cmds_ = cmd;
		InitializeComponent();
		Closing += windowClosing;
		CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, closeWindow));
	}

	#region 閉じる処理
	private void windowClosing(object sender, System.ComponentModel.CancelEventArgs e) {
		//新規作成で行う処理が実装されます。
		if (cmds_.closeCommand.CanExecute(null)) {
			cmds_.closeCommand.Execute(null);
			e.Cancel = false;
		}
		else {
			e.Cancel = true;
		}
	}
	private void closeWindow(object sender, ExecutedRoutedEventArgs e) {
		if (cmds_.closeCommand.CanExecute(null)) {
			cmds_.closeCommand.Execute(null);
			Close();
		}
	}
	#endregion
}

本来であればRoutedCommand.CanExecuteをそのままCompositeCommand.CanExecuteに渡すのがいいのだが、「閉じる」では、×ボタンを押したときの動作の都合で、すべてExecuteの中で処理をしている。

メソッドへの割り付けは、xaml内でやるより、コード内に実装したほうがいいと感じたため、上記のようにした。

ViewModel

private IApplicationCommands cmds_;
public FileParameterViewModel(IApplicationCommands cmds) {
	cmds_ = cmds;
	closeCmd = new DelegateCommand&lt;Object&gt;(x =&gt; executeClose(x), x =&gt; canClose(x));
	cmds_.closeCommand.RegisterCommand(closeCmd);
}
private void executeClose(Object target) {
	//閉じる処理
}
private bool canClose(Object target) {
	//閉じれるかどうか、例えばファイルの保存確認ダイアログを出すなど
}

こちらでは「閉じる」処理用のコマンドを登録する。必要であれば、クラスにはIDisposableを継承し、Dispose内でUnregisterCommandで登録解除をしておく必要があるかも。

Main

internal class ShellCommand : IApplicationCommands {
	private CompositeCommand closeCommand_ = new CompositeCommand();
	public CompositeCommand closeCommand {
		get => closeCommand_;
	}
}
protected override void RegisterTypes(IContainerRegistry containerRegistry) {
	containerRegistry.RegisterSingleton<Controler.IApplicationCommands, ShellCommand>();
}

Main内では、IApplicationCommandsの実装を行ったものを用意し、Singletonで登録しておく。

これにより、ViewModelが起動時、インスタンスされたオブジェクトが参照されるようになる。

最後に

当初、イベントをViewModel側で処理する方法を考えていたが、モジュール化を行った場合に、モジュール側のVIewModelにどのようにその情報を伝えなければいけないか、考えた結果、今回のような形になった。

なんとか、CompositeCommandとRoutedCommandのさわりが分かったかもしれない。

コメント

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