Извините, Дейв, но мне не очень понравилось ваше решение. Сначала вы должны вручную закодировать сантехнику для каждой команды в коде, затем вам нужно настроить CommandRouter, чтобы он знал о каждой ассоциации представления / представления модели в приложении.
Я выбрал другой подход.
У меня есть сборка утилиты Mvvm (у которой нет зависимостей WPF), которую я использую в своей модели представления. В этой сборке я объявляю пользовательский интерфейс ICommand и класс DelegateCommand, который реализует этот интерфейс.
namespace CommonUtil.Mvvm
{
using System;
public interface ICommand
{
void Execute(object parameter);
bool CanExecute(object parameter);
event EventHandler CanExecuteChanged;
}
public class DelegateCommand : ICommand
{
public DelegateCommand(Action<object> execute) : this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public void Execute(object parameter)
{
_execute(parameter);
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
}
}
У меня также есть сборка библиотеки Wpf (которая ссылается на системные библиотеки WPF), на которую я ссылаюсь из своего проекта пользовательского интерфейса WPF. В этой сборке я объявляю класс CommandWrapper, который имеет стандартный интерфейс System.Windows.Input.ICommand. CommandWrapper создается с использованием экземпляра моей пользовательской ICommand и просто делегирует Execute, CanExecute и CanExecuteChanged непосредственно в мой пользовательский тип ICommand.
namespace WpfUtil
{
using System;
using System.Windows.Input;
public class CommandWrapper : ICommand
{
// Public.
public CommandWrapper(CommonUtil.Mvvm.ICommand source)
{
_source = source;
_source.CanExecuteChanged += OnSource_CanExecuteChanged;
CommandManager.RequerySuggested += OnCommandManager_RequerySuggested;
}
public void Execute(object parameter)
{
_source.Execute(parameter);
}
public bool CanExecute(object parameter)
{
return _source.CanExecute(parameter);
}
public event System.EventHandler CanExecuteChanged = delegate { };
// Implementation.
private void OnSource_CanExecuteChanged(object sender, EventArgs args)
{
CanExecuteChanged(sender, args);
}
private void OnCommandManager_RequerySuggested(object sender, EventArgs args)
{
CanExecuteChanged(sender, args);
}
private readonly CommonUtil.Mvvm.ICommand _source;
}
}
В моей сборке Wpf я также создаю ValueConverter, который при передаче экземпляра моей пользовательской ICommand выделяет экземпляр совместимого с Windows.Input.ICommand CommandWrapper.
namespace WpfUtil
{
using System;
using System.Globalization;
using System.Windows.Data;
public class CommandConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new CommandWrapper((CommonUtil.Mvvm.ICommand)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
}
Теперь мои viewmodels могут выставлять команды как экземпляры моего пользовательского типа команд без необходимости иметь какую-либо зависимость от WPF, и мой пользовательский интерфейс может связывать команды Windows.Input.ICommand с этими viewmodels, используя мой ValueConverter следующим образом. (Спам в пространстве имен XAML опущен).
<Window x:Class="Project1.MainWindow">
<Window.Resources>
<wpf:CommandConverter x:Key="_commandConv"/>
</Window.Resources>
<Grid>
<Button Content="Button1" Command="{Binding CustomCommandOnViewModel,
Converter={StaticResource _commandConv}}"/>
</Grid>
</Window>
Теперь, если я действительно ленив (что я и есть), и меня не беспокоит необходимость вручную применять CommandConverter каждый раз, тогда в моей сборке Wpf я могу создать свой собственный подкласс Binding, например:
namespace WpfUtil
{
using System.Windows.Data;
public class CommandBindingExtension : Binding
{
public CommandBindingExtension(string path) : base(path)
{
Converter = new CommandConverter();
}
}
}
Так что теперь я могу связать свой собственный тип команды еще проще, например:
<Window x:Class="Project1.MainWindow"
xmlns:wpf="clr-namespace:WpfUtil;assembly=WpfUtil">
<Window.Resources>
<wpf:CommandConverter x:Key="_commandConv"/>
</Window.Resources>
<Grid>
<Button Content="Button1" Command="{wpf:CommandBinding CustomCommandOnViewModel}"/>
</Grid>
</Window>