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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21067774/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 10:29:17  来源:igfitidea点击:

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

c#wpfmvvmmouseevent

提问by DerPrzem

One thing I am really not sure about is how to properly pass mouse events to the ViewModel. There is the way of binding triggers using the interactivity extension like for instance in: WPF event binding from View to ViewModel?

我真的不确定的一件事是如何正确地将鼠标事件传递给 ViewModel。有使用交互扩展绑定触发器的方法,例如:WPF event binding from View to ViewModel?

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, e.g.:

那么正确的解决方案是什么?一种方法是注册一个事件并在后面的代码中处理它,例如:

    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. So again the question: is it the proper way of doing it? Or is there a better one?

这里我假设列表框的 ItemsSource 绑定到 vm.Nodes 属性。那么问题又来了:这是正确的做法吗?或者有更好的吗?

采纳答案by Anatoliy Nikolaev

I think your approach is good. Those events, that work with View, can be in your code-behind if you handlers work via ViewModel. However, there is an alternative use GalaSoft.MvvmLight(link to download), in which have EventToCommand, supports parameter PassEventArgsToCommand.

我认为你的方法很好。这些事件,与工作View,可以在你的后台代码,如果你通过处理工作ViewModel。但是,还有一种替代用途GalaSoft.MvvmLight下载链接),其中 have 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 ViewModelevent handlers, use the command or store these handlers on Viewside.

我认为您可以同时使用这两种方法。您的解决方案很简单,不需要使用任何框架,而是使用代码隐藏,在这种情况下并不重要。有一件事是肯定的,建议不要保留ViewModel事件处理程序、使用命令或将这些处理程序放在View一边。

Some new notes

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 ViewModeland have a dependency via an interface, but not directly with the UI.

我认为,您的方式不违反 的原则MVVM,所有与 一起使用的事件处理程序View都应该站在 的一边View,主要的是 - 它的事件处理程序需要使用 aViewModel并通过接口具有依赖关系,但不能直接与用户界面。

The only principle MVVMthat you break - is the mantra "no code" and this is not the main principle of MVVM. The main principles:

MVVM你打破的唯一原则- 是口头禅“无代码”,这不是MVVM. 主要原则:

  • Split data Modelof View

  • Application logic should not be tied to UI

  • Support testability code

  • 拆分数据ModelView

  • 应用程序逻辑不应绑定到 UI

  • 支持可测试性代码

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 代码背后

回答by Mark Feldman

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. It will also work for key strokes (e.g. KeyDown) but if your child control isn't focus-able then you'll have to add the trigger to the parent instead. In any case all that remains is to add the relevant handlers to your view model:

在我的特定情况下,我将一组项目渲染到画布上,因此我使用了 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
}

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 e.g. 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. 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.

我要提到的最后一件事只是有点偏离主题,通常您需要与代码隐藏进行通信,例如,当用户按下鼠标左键时,您可能需要捕获鼠标,但这可以很容易通过附加的行为来完成。鼠标捕获行为非常简单,您只需将“MouseCaptured”布尔属性添加到您的视图模型,将您的附加行为绑定到它并使其更改的处理程序做出相应的响应。对于任何更复杂的事情,您可能希望在您的视图模型中创建一个事件,然后您的附加行为可以订阅该事件。无论哪种方式,您的 UI 现在都可以完全进行单元测试,并且您的代码隐藏已转移到通用行为中,以便在其他类中重用。