在我的WPF窗口中,我放置了一个普通的文本框,当按下Ctrl + F时,我希望将其聚焦。
由于我希望尽可能保持MVVM类似,我在窗口上使用InputBindings
将该输入事件绑定到ViewModel中提供的Command(已经破坏了MVVM模式,因为整个action只是视图的一部分?我猜不是,因为Command是一个绑定的对象。)
ViewModel如何与视图通信以聚焦文本框?我读到这已经破坏了MVVM模式,但有时只是必要,否则就不可能。但是,在ViewModel本身中设置焦点将完全打破MVVM模式。
我打算将窗口中当前的焦点控件绑定到ViewModel的属性,但是甚至很难确定WPF中当前关注的元素(总是让我怀疑它是否真的是正确的方法)左右)。
答案 0 :(得分:3)
在这种情况下,没有办法不“打破”纯粹的MVVM。再说一遍,我几乎没有把它打破。我不认为任何大小合适的MVVM应用程序都有“纯粹”。因此,请停止过多关注破坏您使用的模式并改为实施解决方案。
这里至少有两种方式:
答案 1 :(得分:1)
通常,当我们想要在遵守MVVM方法时使用任何UI事件时,我们会创建一个附加属性。正如我昨天刚刚回答了同样的问题,我建议你看一下StackOverflow上的how to set focus to a wpf control using mvvm帖子,找到一个完整的代码示例。
这个问题与你的问题的唯一区别在于你想把元素集中在按键上...我会假设你知道如何做这部分,但如果你不能,那就让我知道,我也会给你一个例子。
答案 2 :(得分:1)
使用mvvm时,在使用:
定义视图模型时更进一步viewmodel不应该知道/引用视图
然后你无法通过viewmodel设置焦点。
但我在mvvm中做的是viewmodel中的以下内容:
将焦点设置为绑定到viewmodel属性的元素
为此我创建了一个行为,它只是遍历可视树中的所有控件并查找绑定表达式路径。如果我找到路径表达式,那么只需关注uielement。
编辑:
xaml用法
<UserControl>
<i:Interaction.Behaviors>
<Behaviors:OnLoadedSetFocusToBindingBehavior BindingName="MyFirstPropertyIWantToFocus" SetFocusToBindingPath="{Binding Path=FocusToBindingPath, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</UserControl>
任何方法中的viemodel
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;
}
}
答案 3 :(得分:0)
(已经打破了MVVM模式,因为整个行动是 只是意味着成为观点的一部分?我猜不是,因为命令是一个 要绑定的对象)
WPF中的Command系统实际上并不是围绕数据绑定设计的,但UI - 使用RoutedCommands,单个命令将根据调用命令的元素的UI结构中的物理位置进行不同的实现。 / p>
您的流程将是:
如果当前元素位于想要以不同方式处理命令的容器内,它将在到达窗口之前停止。
这可能更接近你想要的。如果在blindmeis's answer中存在“活动属性”的某个概念,则涉及视图模型可能是有意义的,但我认为您最终会得到冗余/循环信息流,例如:按键 - &gt; view通知keym的viewmodel - &gt; viewmodel通过告知按键视图来响应。
答案 4 :(得分:0)
在更好地掌握所有这些,考虑并评估所有选项之后,我终于找到了解决问题的方法。我在窗口标记中添加了一个命令绑定:
<Window.InputBindings>
<KeyBinding Command="{Binding Focus}" CommandParameter="{Binding ElementName=SearchBox}" Gesture="CTRL+F" />
</Window.InputBindings>
我的ViewModel中的命令(我将课程简化为本例中的重要事项):
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的答案有一个好点:
所以,不要过分关注破坏你使用的任何模式 并改为实施解决方案。
通常情况下,我会保留按惯例组织的内容,特别是在我还不熟悉的情况下,但我没有看到任何错误。