Прежде всего, если вы хотите связать RoutedUICommand, это просто - просто добавьте в коллекцию UIElement.InputBindings:
<TextBox ...>
<TextBox.InputBindings>
<KeyBinding
Key="Q"
Modifiers="Control"
Command="my:ModelAirplaneViewModel.AddGlueCommand" />
Ваша проблема начинается, когда вы пытаетесь установить Command = "{Binding AddGlueCommand}", чтобы получить ICommand из ViewModel. Поскольку Command не является DependencyProperty, вы не можете установить для него привязку.
Ваша следующая попытка, вероятно, будет заключаться в создании присоединенного свойства BindableCommand с PropertyChangedCallback, которое обновляет Command. Это позволяет вам получить доступ к привязке, но нет способа использовать FindAncestor для поиска вашей ViewModel, поскольку коллекция InputBindings не устанавливает InheritanceContext.
Очевидно, что вы можете создать прикрепленное свойство, которое вы можете применить к TextBox, которое будет проходить через все InputBindings, вызывая BindingOperations.GetBinding для каждого, чтобы найти привязки команд и обновить эти привязки с явным источником, что позволит вам сделать это:
<TextBox my:BindingHelper.SetDataContextOnInputBindings="true">
<TextBox.InputBindings>
<KeyBinding
Key="Q"
Modifiers="Control"
my:BindingHelper.BindableCommand="{Binding ModelGlueCommand}" />
Это присоединенное свойство было бы легко реализовать: на PropertyChangedCallback было бы запланировано «обновление» в DispatcherPriority.Input и настроено событие, чтобы «обновление» было перепланировано при каждом изменении DataContext. Затем в «обновленном» коде просто установите DataContext для каждой InputBinding:
...
public static readonly SetDataContextOnInputBindingsProperty = DependencyProperty.Register(... , new UIPropetyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var element = obj as FrameworkElement;
ScheduleUpdate(element);
element.DataContextChanged += (obj2, e2) =>
{
ScheduleUpdate(element);
};
}
});
private void ScheduleUpdate(FrameworkElement element)
{
Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
{
UpdateDataContexts(element);
})
}
private void UpdateDataContexts(FrameworkElement target)
{
var context = target.DataContext;
foreach(var inputBinding in target.InputBindings)
inputBinding.SetValue(FrameworkElement.DataContextProperty, context);
}
Альтернативой двум прикрепленным свойствам может быть создание подкласса CommandBinding, который получает перенаправленную команду и активирует связанную команду:
<Window.CommandBindings>
<my:CommandMapper Command="my:RoutedCommands.AddGlue" MapToCommand="{Binding AddGlue}" />
...
в этом случае InputBindings в каждом объекте будет ссылаться на перенаправленную команду, а не на привязку. Затем эта команда будет направлена вверх по представлению и отображена.
Код для CommandMapper относительно тривиален:
public class CommandMapper : CommandBinding
{
... // declaration of DependencyProperty 'MapToCommand'
public CommandMapper() : base(Executed, CanExecute)
{
}
private void Executed(object sender, ExecutedRoutedEventArgs e)
{
if(MapToCommand!=null)
MapToCommand.Execute(e.Parameter);
}
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute =
MapToCommand==null ? null :
MapToCommand.CanExecute(e.Parameter);
}
}
На мой вкус, я бы предпочел использовать решение с присоединенными свойствами, так как оно не так много кода и не позволяет мне объявлять каждую команду дважды (как RoutedCommand и как свойство моей ViewModel). Вспомогательный код встречается только один раз и может использоваться во всех ваших проектах.
С другой стороны, если вы делаете только одноразовый проект и не ожидаете что-либо повторно использовать, возможно, даже CommandMapper излишний. Как вы упомянули, можно просто обрабатывать события вручную.