如何处理纯粹与视图相关的命令
本文关键字:视图 命令 纯粹 何处理 处理 | 更新日期: 2023-09-27 18:37:22
有一个我的WPF窗口,我在其中放置了一个普通的文本框,我希望在按下Ctrl + F时将其聚焦。
由于我想尽可能保持它与 MVVM 相似,因此我在窗口中使用InputBindings
将该输入事件绑定到 ViewModel 中提供的命令(这是否已经破坏了 MVVM 模式,因为整个操作只是视图的一部分?我想不是,因为命令是要绑定的对象)。
模型如何与视图通信以聚焦文本框?我读到这已经打破了 MVVM 模式,但有时只是必要的,否则是不可能的。但是,在 ViewModel 本身中设置焦点将完全破坏 MVVM 模式。
我最初打算将窗口中的当前焦点控件绑定到 ViewModel 的属性,但甚至很难确定 WPF 中的当前焦点元素(这总是让我怀疑它是否真的是正确的方法)。
在这种情况下,没有办法不"破坏"纯 MVVM。话又说回来,我几乎不会说它破坏了任何东西。我不认为任何大小适中的 MVVM 应用程序是"纯粹的"。因此,不要太在意打破您使用的任何模式,而是实现解决方案。
这里至少有两种方法:
- 只需在视图中执行代码后面的所有操作:检查是否按下了该键,如果是,则设置焦点。它不会比这更简单,您可能会争辩说 VM 与真正与视图相关的东西无关
- 否则,显然必须在VM和View之间进行一些通信。这使得一切变得更加复杂:假设您使用 InputBinding,您的命令可以设置布尔属性,然后视图可以依次绑定到它以设置焦点。这种绑定可以像谢里登的回答中附加属性一样完成。
通常,当我们想要在遵循 MVVM 方法的同时使用任何 UI 事件时,我们会创建一个附加属性。由于我昨天刚刚回答了同样的问题,我建议您查看如何在 StackOverflow 上使用 mvvm 帖子将焦点设置为 wpf 控件,以获取完整的工作代码示例。
这个问题与你的问题的唯一区别是你想把元素集中在按键上......我将假设您知道如何完成该部分,但是如果您不能,请告诉我,我也会给您举一个例子。
MVVM 时,以及进一步定义视图模型时:
视图模型不应知道/引用视图
然后,您无法通过视图模型设置焦点。
但是我在 MVVM 中所做的是在视图模型中的以下内容:
将焦点设置为绑定到视图模型属性的元素
为此,我创建了一个行为,该行为只需遍历可视化树中的所有控件并查找绑定表达式路径。 如果我找到一个路径表达式,那么只需聚焦 UI元素。
编辑:
XAML 用法
<UserControl>
<i:Interaction.Behaviors>
<Behaviors:OnLoadedSetFocusToBindingBehavior BindingName="MyFirstPropertyIWantToFocus" SetFocusToBindingPath="{Binding Path=FocusToBindingPath, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</UserControl>
任何方法的模型
this.FocusToBindingPath = "MyPropertyIWantToFocus";
行为
public class SetFocusToBindingBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty SetFocusToBindingPathProperty =
DependencyProperty.Register("SetFocusToBindingPath", typeof(string), typeof(SetFocusToBindingBehavior ), new FrameworkPropertyMetadata(SetFocusToBindingPathPropertyChanged));
public string SetFocusToBindingPath
{
get { return (string)GetValue(SetFocusToBindingPathProperty); }
set { SetValue(SetFocusToBindingPathProperty, value); }
}
private static void SetFocusToBindingPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as SetFocusToBindingBehavior;
var bindingpath = (e.NewValue as string) ?? string.Empty;
if (behavior == null || string.IsNullOrWhiteSpace(bindingpath))
return;
behavior.SetFocusTo(behavior.AssociatedObject, bindingpath);
//wenn alles vorbei ist dann binding path zurücksetzen auf string.empty,
//ansonsten springt PropertyChangedCallback nicht mehr an wenn wieder zum gleichen Propertyname der Focus gesetzt werden soll
behavior.SetFocusToBindingPath = string.Empty;
}
private void SetFocusTo(DependencyObject obj, string bindingpath)
{
if (string.IsNullOrWhiteSpace(bindingpath))
return;
var ctrl = CheckForBinding(obj, bindingpath);
if (ctrl == null || !(ctrl is IInputElement))
return;
var iie = (IInputElement) ctrl;
ctrl.Dispatcher.BeginInvoke((Action)(() =>
{
if (!iie.Focus())
{
//zb. bei IsEditable=true Comboboxen funzt .Focus() nicht, daher Keyboard.Focus probieren
Keyboard.Focus(iie);
if (!iie.IsKeyboardFocusWithin)
{
Debug.WriteLine("Focus konnte nicht auf Bindingpath: " + bindingpath + " gesetzt werden.");
var tNext = new TraversalRequest(FocusNavigationDirection.Next);
var uie = iie as UIElement;
if (uie != null)
{
uie.MoveFocus(tNext);
}
}
}
}), DispatcherPriority.Background);
}
public string BindingName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectLoaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObjectLoaded;
}
private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
SetFocusTo(AssociatedObject, this.BindingName);
}
private DependencyObject CheckForBinding(DependencyObject obj, string bindingpath)
{
var properties = TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) });
if (obj is IInputElement && ((IInputElement) obj).Focusable)
{
foreach (PropertyDescriptor property in properties)
{
var prop = DependencyPropertyDescriptor.FromProperty(property);
if (prop == null) continue;
var ex = BindingOperations.GetBindingExpression(obj, prop.DependencyProperty);
if (ex == null) continue;
if (ex.ParentBinding.Path.Path == bindingpath)
return obj;
}
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var result = CheckForBinding(VisualTreeHelper.GetChild(obj, i),bindingpath);
if (result != null)
return result;
}
return null;
}
}
(这已经破坏了MVVM模式,因为整个操作是 只是为了成为视图的一部分?我想不是,因为命令是一个 要绑定到的对象)
WPF 中的命令系统实际上不是围绕数据绑定设计的,而是围绕 UI 设计的 - 使用 RoutedCommand,单个命令将根据调用命令的元素的 UI 结构中的物理位置具有不同的实现。
命令概述
您的流程将是:
- 按 Ctrl+F
- 命令事件引发并冒泡
- 事件到达窗口,该窗口具有命令绑定到命令
- 窗口上的事件处理程序聚焦文本框
如果当前元素位于想要以不同方式处理命令的容器内,则它将在到达窗口之前停止在那里。
这可能更接近您想要的。如果像blindmeis的答案中那样存在一些"活动属性"的概念,那么涉及视图模型可能是有意义的,但否则我认为您最终只会得到冗余/循环的信息流,例如.key按下->视图通知按键视图模型->视图模型通过通知按键视图来响应。
经过几天的更好地掌握了所有这些,考虑和评估了所有选项,我终于找到了一种方法来解决它。我在窗口标记中添加了一个命令绑定:
<Window.InputBindings>
<KeyBinding Command="{Binding Focus}" CommandParameter="{Binding ElementName=SearchBox}" Gesture="CTRL+F" />
</Window.InputBindings>
我的视图模型中的命令(在这种情况下,我将类缩减为重要内容):
class Overview : Base
{
public Command.FocusUIElement Focus
{
get;
private set;
}
public Overview( )
{
this.Focus = new Command.FocusUIElement();
}
}
最后是命令本身:
class FocusUIElement : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute ( object parameter )
{
return true;
}
public void Execute ( object parameter )
{
System.Windows.UIElement UIElement = ( System.Windows.UIElement ) parameter;
UIElement.Focus();
}
}
这可能不是 MVVM - 但 stijn 的回答有一个很好的观点:
所以,不要太在意打破你使用的任何模式 并改为实施解决方案。
通常我会按照惯例来组织东西,特别是当我对某件事还很陌生的时候,但我看不出这有什么问题。