#c# #wpf #xaml #mvvm #data-binding
#c# #wpf #xaml #mvvm #привязка данных
Вопрос:
Я хочу добавить несколько общих сочетаний клавиш в свое приложение. В настоящее время в каждом представлении XAML я добавляю этот код:
<Window.InputBindings>
<KeyBinding Command="{Binding ZoomInCommand}" Key="Add" Modifiers="Control" />
<KeyBinding Command="{Binding ZoomOutCommand}" Key="Subtract" Modifiers="Control" />
</Window.InputBindings>
Я хочу обобщить это, я хочу создать подкласс класса WPF Window и использовать вместо него вновь созданный подкласс. Теперь мне интересно, как я мог бы связать эти команды клавиатуры в соответствующем коде. В настоящее время это выглядит так:
public class MyWindow : Window
{
public MyWindow()
{
DataContextChanged = OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
InputBindings.Clear();
var dataContext = DataContext as IZoomableViewModel;
if (dataContext != null)
{
InputBindings.Add(new KeyBinding(dataContext.ZoomInCommand, Key.Add, ModifierKeys.Control));
InputBindings.Add(new KeyBinding(dataContext.ZoomOutCommand, Key.Subtract, ModifierKeys.Control));
}
}
}
Но мне это кажется неправильным, так как мне нужно иметь прямой доступ к DataContext и использовать его вместо использования объекта Binding() . Как я могу изменить код, чтобы он выглядел более похожим на MVVM?
Ответ №1:
Я нашел простое решение, которое хорошо работает и, похоже, имитирует поведение синтаксического анализатора XAML. В принципе, для увеличения функциональности я помещаю следующий код в конструктор MyWindow:
var zoomInKeyBinding = new KeyBinding { Key = Key.Add, Modifiers = ModifierKeys.Control };
BindingOperations.SetBinding(
zoomInKeyBinding,
InputBinding.CommandProperty,
new Binding { Path = new PropertyPath("ZoomInCommand") }
);
InputBindings.Add(zoomInKeyBinding);
Конечно, связанная ViewModel должна соответствующим образом реализовать команду ZoomInCommand.
Ответ №2:
Что вам нужно, так это свойства зависимостей.
В MyWindow
, создайте ICommand
свойство зависимости для обеих ваших команд, вам также потребуется реализовать метод обратного вызова при изменении значения свойства зависимости, вот один для ZoomInCommand
:
public ICommand ZoomInCommand
{
get { return (ICommand)GetValue(ZoomInCommandProperty); }
set { SetValue(ZoomInCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for ZoomInCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ZoomInCommandProperty =
DependencyProperty.Register("ZoomInCommand", typeof(ICommand), typeof(MyWindow), new PropertyMetadata(null, new PropertyChangedCallback(OnZoomInCommandChanged)));
...
private static void OnZoomInCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyWindow wnd = (MyWindow)d;
//Remove the old key binding if there is one.
wnd.RemoveInputBinding(e.OldValue as ICommand);
//Add the new input binding.
if (e.NewValue != null)
wnd.InputBindings.Add(new KeyBinding((ICommand)e.NewValue, Key.Add, ModifierKeys.Control));
}
private void RemoveInputBinding(ICommand command)
{
if (command == null)
return;
//Find the old binding if there is one.
InputBinding oldBinding = null;
foreach (InputBinding binding in InputBindings)
{
if (binding.Command == command)
{
oldBinding = binding;
break;
}
}
//Remove the old input binding.
if (oldBinding != null)
InputBindings.Remove(oldBinding);
}
Итак, что именно делает приведенный выше код?
Для свойства dependency необязательно иметь PropertyChangedCallback
метод, который будет срабатывать при изменении значения свойства, это здорово, потому что мы можем использовать это для удаления старого InputBinding
и создания нового InputBinding
, если значение должно измениться. В вашем случае значение будет меняться при DataContext
каждом изменении.
Таким образом, шаги довольно просты, всякий раз, когда изменяется значение свойства:
- Удалите старое
InputCommand
для старогоICommand
. - Добавьте новое
InputCommand
для новогоICommand
.
Я создал удобный RemoveInputBinding
метод, который должен упростить повторное использование кода для вашего другого свойства зависимостей, которое вы можете реализовать.
Чтобы объединить все это вместе, в вашем DataContextChanged
обработчике событий вам просто нужно написать ручную привязку:
private void MyWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
//Bind this.ZoomInCommand to DataContext.ZoomInCommand
Binding zoomInCommandBinding = new Binding("ZoomInCommand");
zoomInCommandBinding.Source = DataContext;
this.SetBinding(MyWindow.ZoomInCommandProperty, zoomInCommandBinding);
...
}
Это гарантирует, что вам больше не нужно беспокоиться о приведении DataContext
к an IZoomableViewModel
, вы просто пытаетесь привязать к ZoomInCommand
. Если в такой команде нет DataContext
, то она просто молча завершится ошибкой. Однако, если это удастся PropertyChangedCallback
, будет запущен и InputBinding
для команды будет создан an.