我刚刚开始使用MVVM并且我在查明如何将文本框中的按键绑定到视图模型中的ICommand时遇到问题。我知道我可以在代码隐藏中做到这一点,但我尽量避免这种情况。
更新:到目前为止,如果您有混合sdk或者您没有遇到与我正在进行的交互dll的问题,那么解决方案一切都很好。除了必须使用混合sdk之外,还有其他更通用的解决方案吗?
答案 0 :(得分:9)
首先,如果要绑定RoutedUICommand,这很容易 - 只需添加到UIElement.InputBindings集合中:
<TextBox ...>
<TextBox.InputBindings>
<KeyBinding
Key="Q"
Modifiers="Control"
Command="my:ModelAirplaneViewModel.AddGlueCommand" />
当您尝试设置Command =“{Binding AddGlueCommand}”以从ViewModel获取ICommand时,您的问题就开始了。由于Command不是DependencyProperty,因此无法在其上设置绑定。
您的下一次尝试可能是创建一个附加属性BindableCommand,它具有更新Command的PropertyChangedCallback。这允许您访问绑定,但由于InputBindings集合未设置InheritanceContext,因此无法使用FindAncestor查找ViewModel。
显然,您可以创建一个可以应用于TextBox的附加属性,该属性将通过调用BindingOperations.GetBinding的所有InputBindings来查找Command绑定并使用显式源更新这些Bindings,从而允许您执行此操作: / p>
<TextBox my:BindingHelper.SetDataContextOnInputBindings="true">
<TextBox.InputBindings>
<KeyBinding
Key="Q"
Modifiers="Control"
my:BindingHelper.BindableCommand="{Binding ModelGlueCommand}" />
这个附加属性很容易实现:在PropertyChangedCallback上,它将在DispatcherPriority.Input上安排“刷新”并设置一个事件,以便在每次DataContext更改时重新安排“刷新”。然后在“刷新”代码中,只需在每个InputBinding上设置DataContext:
...
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也可能过度。如您所述,可以手动处理事件。
答案 1 :(得分:6)
出色的WPF框架Caliburn可以很好地解决这个问题。
<TextBox cm:Message.Attach="[Gesture Key: Enter] = [Action Search]" />
语法[Action Search]绑定到视图模型中的方法。根本不需要ICommands。
答案 2 :(得分:4)
从代码隐藏事件处理到MVVM命令的最简单转换可能是来自Expression Blend Samples的触发器和操作。
以下是一段代码,演示了如何使用以下命令处理文本框内的按键事件:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<si:InvokeDataCommand Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
答案 3 :(得分:1)
最好的选择可能是使用Attached Property来执行此操作。如果您有Blend SDK,Behavior<T>
类可以使这更加简单。
例如,可以很容易地修改此TextBox Behavior以在每次按键时触发ICommand,而不是单击Enter上的按钮。