実際にコマンド パターンを調べてみると、非常に興味深いことがわかりました。MVVM Architectural Patternに従って WPF Windows アプリを作成しています。
基本を説明するこれらの投稿から始めました。
ユーザー アクションをコマンドに分割できるようになったので、必要なコマンドを挿入するにはこれが最適だと考えました。最初に参照した記事でコマンドがViewModelに含まれていることに気付きました。そのため、Ninjectと一緒にコマンドを使用し、次のようなバインディングを使用してビュー モデルにコマンドを実際に挿入できれば素晴らしいと思いました。
kernel
.Bind<ICommand>()
.To<RelayCommand>()
.WithConstructorArgument("execute", new Action<object>(???));
では、ここに何を入れるか???. 期待される答えはメソッドです。すごい!そこに入れるメソッドが必要なだけです。
最初の記事では ViewModel コンストラクター内でコマンドを初期化するだけなので、コマンド実行呼び出しでどのメソッドを実行する必要があるかは簡単にわかります。
しかし、CompositionRootの中から? これは、使用している DI コンテナーを介して型をバインドする以外のことを行うメソッドを配置する場所ではありません。
さて、Ninject 拡張機能を使用した Interceptor パターンに出会いました。これは私の要件に合っているように見えますが、私が言うには、少し混乱しています。記事が紛らわしいというわけではありませんが、そうではありません。よくわかりません!
また、常に素晴らしい回答を設定している BatteryBackupUnit からのこの回答もあります。
しかし、今では、すべてを接着する方法がわかりません。謙虚に、私は迷っています。
これまでのコードは次のとおりです。
Relayコマンド
public class RelayCommand : ICommand {
public RelayCommand(Action<object> methodToExecute, Predicate<object> canExecute) {
if(methodToExecute == null)
throw new ArgumentNullException("methodToExecute");
if(canExecute == null)
throw new ArgumentNullException("canExecute");
this.canExecute = canExecute;
this.methodToExecute = methodToExecute;
}
public bool CanExecute(object parameter) {
return canExecute != null && canExecute(parameter);
}
public event EventHandler CanExecuteChanged {
add {
CommandManager.RequerySuggested += value;
canExecuteChanged += value;
}
remove {
CommandManager.RequerySuggested -= value;
canExecuteChanged -= value;
}
}
public static bool DefaultCanExecute(object parameter) { return true; }
public void Execute(object parameter) { methodToExecute(parameter); }
public void OnCanExecuteChanged() {
var handler = canExecuteChanged;
if(handler != null) handler(this, EventArgs.Empty);
}
public void Destroy() {
canExecute = _ => false;
methodToExecute = _ => { return; };
}
private Predicate<object> canExecute;
private Action<object> methodToExecute;
private event EventHandler canExecuteChanged;
}
カテゴリー管理ビューモデル
public class CategoriesManagementViewModel : ViewModel<IList<Category>> {
public CategoriesManagementViewModel(IList<Category> categories
, ICommand changeCommand
, ICommand createCommand
, ICommand deleteCommand) : base(categories) {
if(changeCommand == null)
throw new ArgumentNullException("changeCommand");
if(createCommand == null)
throw new ArgumentNullException("createCommand");
if(deleteCommand == null)
throw new ArgumentNullException("deleteCommand");
this.changeCommand = changeCommand;
this.createCommand = createCommand;
this.deleteCommand = deleteCommand;
}
public ICommand ChangeCommand { get { return changeCommand; } }
public ICommand CreateCommand { get { return createCommand; } }
public ICommand DeleteCommand { get { return deleteCommand; } }
private readonly ICommand changeCommand;
private readonly ICommand createCommand;
private readonly ICommand deleteCommand;
}
すべてを使用するわけではありませんが、 Property Injectionを使用した方がよいでしょうか?
CategoriesManagementView別のウィンドウを呼び出すとしましょう。たとえばCreateCategoryView.Show()、ユーザーが管理ウィンドウに戻るまで、CreateCategoryView が引き継ぎます。
次に、Create Command は CreateCategoryView.Show() を呼び出す必要があります。これが、CompositionRoot内から試したものです。
コンポジションルート
public class CompositionRoot {
public CompositionRoot(IKernel kernel) {
if(kernel == null) throw new ArgumentNullException("kernel");
this.kernel = kernel;
}
//
// Unrelated code suppressed for simplicity sake.
//
public IKernel ComposeObjectGraph() {
BindCommandsByConvention();
return kernel;
}
private void BindCommandsByConvention() {
//
// This is where I'm lost. I can't see any way to tell Ninject
// what I want it to inject into my RelayCommand class constructor.
//
kernel
.Bind<ICommand>()
.To<RelayCommand>()
.WithConstructorArgument("methodToExecute", new Action<object>());
//
// I have also tried:
//
kernel
.Bind<ICommand>()
.ToConstructor(ctx =>
new RelayCommand(new Action<object>(
ctx.Context.Kernel
.Get<ICreateCategoryView>().ShowSelf()), true);
//
// And this would complain that there is no implicit conversion
// between void and Action and so forth.
//
}
private readonly IKernel kernel;
}
おそらく、私は物事を複雑にしすぎているのかもしれません。=)
Ninject Interception Extension がこの仕事に適したツールであるかどうか、またそれを効果的に使用するにはどうすればよいでしょうか?