简体   繁体   English

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

[英]Is accessing the ViewModel in code behind always violating the MVVM pattern?

One thing I am really not sure about is how to properly pass mouse events to the ViewModel. 我真正不确定的一件事是如何将鼠标事件正确传递给ViewModel。 There is the way of binding triggers using the interactivity extension like for instance in: WPF event binding from View to ViewModel? 有一种使用交互扩展来绑定触发器的方式,例如: 从View到ViewModel的WPF事件绑定?

But this does not forward the MouseEventArgs to my knowledge, and this solution does not appear very elegant to me. 但是这并没有将MouseEventArgs转发给我所知,而且这个解决方案对我来说似乎并不优雅。

So what would be the proper solution? 那么什么是正确的解决方案? One way is to register an event and to handle it in the code behind, eg: 一种方法是注册事件并在后面的代码中处理它,例如:

    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));
    }

Here I assume that the listbox's ItemsSource is bound to the vm.Nodes property. 这里我假设列表框的ItemsSource绑定到vm.Nodes属性。 So again the question: is it the proper way of doing it? 所以问题是:这是正确的做法吗? Or is there a better one? 还是有更好的?

Good timing, I wrote some code to do exactly this about two hours ago. 很好的时机,我写了一些代码,大约两个小时前完成这个。 You can indeed pass arguments, and personally I thnk it is elegant because it allows you to fully test your user interface. 你确实可以传递参数,我个人认为它很优雅,因为它允许你完全测试你的用户界面。 MVVM Lite allows you to bind events to commands with EventToCommand, so start by adding the relevant namespaces to your control/window: MVVM Lite允许您使用EventToCommand将事件绑定到命令,因此首先将相关的命名空间添加到控件/窗口:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"

Now add event triggers to the child control whose events you want to intercept: 现在将事件触发器添加到要拦截其事件的子控件:

<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>

In my specific case I'm rendering a collection of items onto a canvas, hence my use of ItemsControl, but it'll work on anything including the parent window. 在我的特定情况下,我将一组项目渲染到画布上,因此我使用了ItemsControl,但它将适用于包括父窗口在内的任何内容。 It will also work for key strokes (eg KeyDown) but if your child control isn't focus-able then you'll have to add the trigger to the parent instead. 它也适用于击键(例如KeyDown),但是如果您的子控件不能聚焦,那么您必须将触发器添加到父级。 In any case all that remains is to add the relevant handlers to your view model: 无论如何,剩下的就是将相关的处理程序添加到视图模型中:

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
}

One last thing I will mention that is only a little bit off-topic is that often you'll need to communicate back to the code-behind eg when the user presses the left mouse button you might need to capture the mouse, but this can easily be accomplished with attached behaviors. 我要提到的最后一件事只是有点偏离主题,通常你需要回传给代码隐藏,例如当用户按下鼠标左键时你可能需要捕获鼠标,但这可以通过附加行为轻松完成。 The mouse capture behavior is simple enough, you just add a "MouseCaptured" boolean property to your view model, bind your attached behavior to it and have it's changed handler respond accordingly. 鼠标捕获行为很简单,您只需将“MouseCaptured”布尔属性添加到视图模型中,将附加行为绑定到它并让其更改处理程序相应地响应。 For anything more complicated you might want to create an event inside your view model which your attached behaviour can then subscribe to. 对于任何更复杂的事情,您可能希望在视图模型中创建一个事件,然后您的附加行为可以订阅。 Either way, your UI is now fully unit-testable and your code-behind has been moved into generic behaviors for re-use in other classes. 无论哪种方式,您的UI现在都完全可以进行单元测试,并且您的代码隐藏已经转移到通用行为中,以便在其他类中重复使用。

I think your approach is good. 我认为你的方法很好。 Those events, that work with View , can be in your code-behind if you handlers work via ViewModel . 如果处理程序通过ViewModel工作,那些与View一起使用的事件可以在您的代码隐藏中。 However, there is an alternative use GalaSoft.MvvmLight ( link to download ), in which have EventToCommand , supports parameter PassEventArgsToCommand . 但是,还有另一种用途GalaSoft.MvvmLight链接下载 ),其中有EventToCommand ,支持参数PassEventArgsToCommand

Example of using: 使用示例:

<Button>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseEnter">
            <cmd:EventToCommand Command="{Binding FooCommand}"
                                PassEventArgsToCommand="True" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

I think you can use both approaches. 我想你可以使用这两种方法。 Your solution is simple, does not require the use of the any frameworks but uses code-behind, in this case it is not critical. 您的解决方案很简单,不需要使用任何框架但使用代码隐藏,在这种情况下它并不重要。 One thing is certain, it is advisable not to keep ViewModel event handlers, use the command or store these handlers on View side. 有一点是肯定的,建议不要保留ViewModel事件处理程序,使用命令或在View端存储这些处理程序。

Some new notes

I think, your way does not violate the principles of MVVM , all event handlers working with View , should be on the side of the View , the main thing - it's event handlers need to work with a ViewModel and have a dependency via an interface, but not directly with the UI. 我认为,你的方式不违反MVVM的原则,所有使用View事件处理程序都应该在View的一边,主要的事情 - 它的事件处理程序需要使用ViewModel并通过接口具有依赖性,但不直接与UI。

The only principle MVVM that you break - is the mantra "no code" and this is not the main principle of MVVM . 你破坏的唯一MVVM原则 - 是“无代码”的口头禅,这不是MVVM的主要原则。 The main principles: 主要原则:

  • Split data Model of View 拆分数据ModelView

  • Application logic should not be tied to UI 应用程序逻辑不应与UI绑定

  • Support testability code 支持可测试性代码

Once the code-behind violate at least one of these principles, you must already see the alternatives to solve their problem. 一旦代码隐藏违反了这些原则中的至少一个,您必须已经看到了解决问题的替代方案。

Also, you can read opinions about it on this link: 此外,您可以在此链接上阅读有关它的意见:

WPF MVVM Code Behind WPF MVVM代码背后

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM