Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVM Where Does Event Handlers Belong

I'm writing a simple MVVM application to study proper code design. It has taken a while to get it done but things are going well.

I have a question in regards to how to handle events, and whether the code should go in the ViewModel or in the code-behind.

First, there are 2 techniques for binding events, one is using Blend Interactivity DLL to bind to a command, the other is to use the MethodBindingExtension class.

Using the Interactivity DLL, it allows using an EventArgs converter to convert event args into a UI-agnostic type containing only the data we need. I don't think MethodBindingExtension does that but it's a lot more flexible. But then, this event args converter won't help when you need to set event args values I suppose? (or maybe it allows converting the value back, haven't checked those classes yet)

I like using MethodBindingExtension, and now I have this code in my ViewModel. What I don't like about it is that I'm using specific UI types, which isn't a big deal for now but for theory sake, perhaps it could be improved.

What should I do with this? Move it into the code-behind? Leave it into the ViewModel and use args converter? Leave it like this?

public void Window_DropFile(DragEventArgs e) {
    if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
        string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
        foreach (string file in files) {
            ReadScriptFile(file);
        }
    }
}

public void Window_PreviewDragOver(DragEventArgs e) {
    e.Effects = DragDropEffects.All;
    e.Handled = true;
}

public void Header_PreviewLeftMouseButtonDown(IScriptViewModel sender, MouseButtonEventArgs e) {
    if (sender == SelectedItem && sender.CanEditHeader && !sender.IsEditingHeader) {
        sender.IsEditingHeader = true;
        e.Handled = true;
    }
}
like image 221
Etienne Charland Avatar asked Dec 23 '22 05:12

Etienne Charland


2 Answers

In general:

Your MVVM viewmodel should only contain data and commands. Of course there are loads of exceptions on that but, keep in mind that basically, it should only contain view- related commands and items.

I see a lot of confusion about this when event handlers are applied to components. The thing is; when you put an eventhandler for a UI component in your viewmodel, the viewmodel is bound to the actual implementation (with the UI component) of the view, not a "view" in general.

As a rule of thumb; you should be able to copy past your viewmodel and use it in another implementation and it should just compile. It should contain no references to the UI elements itself or, indirect through event handlings and so.


So, yes , you should put those event handlers in the code-behind, or find a framework or component which is able to handle it in the view/XAML. Its perfectly fine to call a command from such an event. A more practical approach would say: put them where it works best and where it makes your code the most readable/maintainable. If you understand the concept of MVVM, you will minimize the occurrences of these mixed models, which is often good enough.

like image 184
Stefan Avatar answered Dec 25 '22 19:12

Stefan


Event handlers were only added to facilitate the porting of legacy code, in pure MVVM you shouldn't need use them at all. Your MVVM platform will have something to direct events to command handlers in your view model, in MVVM Lite you can use EventToCommand, here's a snippet from some code I wrote for dragging items around a canvas:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"
.
.
.
<i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseDown">
        <cmd:EventToCommand Command="{Binding MouseDownCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
    <i:EventTrigger EventName="MouseUp">
        <cmd:EventToCommand Command="{Binding MouseUpCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
    <i:EventTrigger EventName="MouseMove">
        <cmd:EventToCommand Command="{Binding MouseMoveCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Note that the event parameters are also being passed in to the command handler, this allows the view model to capture the mouse during the drag. This code was written back before I turned into an MVVM purist, nowadays I also set a converter (via EventArgsConverter/EventArgsConverterParameter) to convert from the event arguments type to a custom type so as to avoid referencing the Windows libraries from the view model code. This means the drag operations can also be unit tested.

like image 39
Mark Feldman Avatar answered Dec 25 '22 18:12

Mark Feldman