在后面的代码中访问ViewModel总是违反MVVM模式吗?

时间:2014-01-11 20:56:24

标签: c# wpf mvvm mouseevent

我真正不确定的一件事是如何将鼠标事件正确传递给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属性。所以问题是:这是正确的做法吗?还是有一个更好的?

2 个答案:

答案 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.MvvmLightlink 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

  • 相关联
  • 支持可测试性代码

一旦代码隐藏违反了其中至少一项原则,您必须已经看到了解决问题的替代方法。

此外,您可以在此链接上阅读有关它的意见:

WPF MVVM Code Behind