我真正不确定的一件事是如何将鼠标事件正确传递给ViewModel。有一种使用交互扩展来绑定触发器的方法,例如:WPF event binding from View to ViewModel?
但是这并没有将MouseEventArgs转发给我所知,而且这个解决方案对我来说并不是很优雅。
那么什么是正确的解决方案?一种方法是注册事件并在后面的代码中处理它,例如:
private void ListBox_PreviewMouseDown(object sender, System.Windows.Input.MouseEventArgs e)
{
var listbox = sender as ListBox;
if (listbox == null)
return;
var vm = listbox.DataContext as MainViewModel;
if (vm == null)
return;
// access the source of the listbox in viewmodel
double x = e.GetPosition(listbox).X;
double y = e.GetPosition(listbox).Y;
vm.Nodes.Add(new Node(x, y));
}
这里我假设列表框的ItemsSource绑定到vm.Nodes属性。所以问题是:这是正确的做法吗?还是有一个更好的?
答案 0 :(得分:4)
好时机,我写了一些代码,大概在两个小时前完成。你确实可以传递参数,我个人认为它很优雅,因为它允许你完全测试你的用户界面。 MVVM Lite允许您使用EventToCommand将事件绑定到命令,因此首先将相关的命名空间添加到控件/窗口:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"
现在将事件触发器添加到要拦截其事件的子控件:
<ItemsControl ... etc ... >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseDownCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseUp">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseUpCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseMove">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseMoveCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ItemsControl>
在我的特定情况下,我将一组项目渲染到画布上,因此我使用了ItemsControl,但它将适用于包括父窗口在内的任何内容。它也适用于击键(例如KeyDown),但是如果您的子控件不能聚焦,那么您必须将触发器添加到父级。无论如何,剩下的就是将相关的处理程序添加到视图模型中:
public class MyViewModel : ViewModelBase
{
public ICommand MouseDownCommand { get; set; }
public ICommand MouseUpCommand { get; set; }
public ICommand MouseMoveCommand { get; set; }
public ICommand KeyDownCommand { get; set; }
// I'm using a dependency injection framework which is why I'm
// doing this here, otherwise you could do it in the constructor
[InjectionMethod]
public void Init()
{
this.MouseDownCommand = new RelayCommand<MouseButtonEventArgs>(args => OnMouseDown(args));
this.MouseUpCommand = new RelayCommand<MouseButtonEventArgs>(args => OnMouseUp(args));
this.MouseMoveCommand = new RelayCommand<MouseEventArgs>(args => OnMouseMove(args));
this.KeyDownCommand = new RelayCommand<KeyEventArgs>(args => OnKeyDown(args));
}
private void OnMouseDown(MouseButtonEventArgs args)
{
// handle mouse press here
}
// OnMouseUp, OnMouseMove and OnKeyDown handlers go here
}
我要提到的最后一件事只是有点偏离主题,通常你需要回复代码隐藏,例如当用户按下鼠标左键时,您可能需要捕获鼠标,但这可以通过附加的行为轻松完成。鼠标捕获行为很简单,您只需将“MouseCaptured”布尔属性添加到视图模型中,将附加行为绑定到它并让其更改处理程序相应地响应。对于任何更复杂的事情,您可能希望在视图模型中创建一个事件,然后您的附加行为可以订阅。无论哪种方式,您的UI现在都完全可以进行单元测试,并且您的代码隐藏已经转移到通用行为中,以便在其他类中重复使用。
答案 1 :(得分:2)
我认为你的方法很好。如果处理程序通过View
工作,那些与ViewModel
一起使用的事件可能会出现在代码隐藏中。但是,有一个替代用途GalaSoft.MvvmLight
(link to download),其中EventToCommand
支持参数PassEventArgsToCommand
。
使用示例:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<cmd:EventToCommand Command="{Binding FooCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
我认为你可以使用这两种方法。您的解决方案很简单,不需要使用任何框架但使用代码隐藏,在这种情况下它并不重要。有一点是肯定的,建议不要保留ViewModel
事件处理程序,使用命令或在View
侧存储这些处理程序。
<强> Some new notes
强>
我认为,您的方式不违反MVVM
的原则,所有使用View
的事件处理程序都应该位于View
的一边,主要的事情 - 这是事件处理程序需要使用ViewModel
并通过接口具有依赖关系,但不能直接与UI。
你打破的唯一原则MVVM
- 是“无代码”的口头禅,这不是MVVM
的主要原则。主要原则:
拆分Model
View
应用程序逻辑不应与UI
支持可测试性代码
一旦代码隐藏违反了其中至少一项原则,您必须已经看到了解决问题的替代方法。
此外,您可以在此链接上阅读有关它的意见: