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

心已入冬 提交于 2019-12-03 16:39:48
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.

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.

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.

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

  • Split data Model of View

  • Application logic should not be tied to 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

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:

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:

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!