4
votes

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?

But this does not forward the MouseEventArgs to my knowledge, and this solution does not appear very elegant to me.

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?

2
How are you going to solve similar task with data templates?Dennis
This works by using EventSetters like in stackoverflow.com/questions/1800595/… I just tried it, and it seems to work.DerPrzem

2 Answers

2
votes

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

4
votes

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.