van Nijnatten
van Nijnatten

Reputation: 404

WPF MVVM Code Behind Best Practices

I'm a student learning C# with WPF using the MVVM pattern. Recently I have been working on a [art of my application (a custom splash screen) that should not be closed when I don't want it to. I have been searching the web for a good way of doing this without code-behind. Unfortunately after days I still did not find a satisfying way. Then I came to think of a way to do it myself, with help of just one line of code in the constructor of my view. It still makes my code testable and decouples the code from the View. The question is, is there a better way of doing what I'm trying to do:

My interface for my ViewModel

public interface IPreventCloseViewModel
{
    bool PreventClose { get; set; }
}

The extension for the View

public static class PreventCloseViewModelExtension
{
    /// <summary>
    /// Use this extension method in the constructor of the view.
    /// </summary>
    /// <param name="element"></param>
    public static void PreventCloseViewModel(this Window element)
    {
        var dataContext = element.DataContext as IDisposable;
        if (dataContext is IPreventCloseViewModel)
        {
            element.Closing += delegate(object sender, CancelEventArgs args)
                                   {
                                       if (dataContext is IPreventCloseViewModel)
                                       {
                                           args.Cancel = (dataContext as IPreventCloseViewModel).PreventClose;
                                       }
                                   };
        }
    }
}

The code-behind for the View

public partial class SplashScreen
{
    public SplashScreen()
    {
        InitializeComponent();
        this.PreventCloseViewModel();
    }
}

Upvotes: 5

Views: 8785

Answers (3)

Emperor Eto
Emperor Eto

Reputation: 3520

Fede's answer is absolutely right, but I think there's an even better and more MVVM-friendly way to do this just by extending your Window class a bit:

    #region bool PreventClose dependency property
    public static readonly DependencyProperty PreventCloseProperty = DependencyProperty.Register(
        "PreventClose",
        typeof(bool),
        typeof(MainWindow),
        new PropertyMetadata(false));
    public bool PreventClose
    {
        get
        {
            return (bool)GetValue(PreventCloseProperty);
        }
        set
        {
            SetValue(PreventCloseProperty, value);
        }
    }
    #endregion

    protected override void OnClosing(CancelEventArgs e)
    {
        if (this.PreventClose)
        {
            e.Cancel = true;
            return;
        }
        base.OnClosing(e);
    }

Now you can just bind your new PreventClose property in XAML to some corresponding property on the view model. The actual application logic determining whether the window should be allowed to close still remains in the view model where it belongs, but the separation between view and view model is cleaner now because the GUI code no longer depends on any view model specifics.

This really goes to defining exactly what we mean by "code-behind" vs. "application logic". I hate semantic arguments (most of all on this site), but some erroneously define the "application logic" to mean nothing more than CRUD, then argue that all logic relevant to the user interface goes in the view, and define this to be synonymous with "code-behind". But this is not how those terms were understood when they were first introduced. (See, e.g., Introduction to Model/View/ViewModel pattern for building WPF apps (2005) - the first known article about the pattern).

As that article explains, abstract UI/application logic that isn't specific to the GUI implementation goes in the view model. Data-heavy "business logic", such as CRUD operations, go in the "model" (which I like to call "data model" to limit the potential for confusion). The "view" should by and large do nothing more than lay out the controls and bindings between them and the view model.

"Code-behind" is a mutant hybrid of view and view model that MVVM strives to avoid as it comingles the very parts of the application that we're trying to separate. Note this is not the same as using C# code to customize or extend controls (as in my example), assuming it's done in an application-agnostic way; that kind of code is just GUI implementation, to which MVVM is agnostic.

In the OP's example, the Window (or rather the extension method) needs to know specifics about the view model - or at least an interface it implements - in order to work. It basically suffers from the same issue as generic code-behind (despite being an extension method instead of a partial class) - the clean separation between view and view model MVVM strives for is thus destroyed.

In my example, we extend Window in a completely application-agnostic way. You could take this subclassed Window and plop it in a WPF helper library to use for the rest of your career. And since it's a bindable property, you can hook up the view model and view cleanly through a simple declarative binding.

Ultimately, it's not about whether the view layer contains C# code; it's what that C# code does that matters. If you keep view-layer C# code limited to the GUI implementation and application-agnostic, it's not just ok - but is encouraged - to use that to implement rich interactive UIs. But when it comes time to finally connect your rich interactive UI to your abstract view model, strive to keep those connection points limited to declarative bindings rather than allowing imperative GUI code to directly interact with or become dependent on your application-specific logic. That's how you stay faithful to the pattern and realize its full range of benefits.

Upvotes: 0

Fede
Fede

Reputation: 44028

MVVM does not mean that you cannot use Code-Behind.

MVVM means that your application logic should not be tied to UI elements.

You can perfectly well handle events in code behind (such as Window.Closing), and "send messages" or execute methods in the ViewModel to react to that.

Here, you are not breaking MVVM by placing the event handler in code behind. You would be breaking MVVM if you were placing the logic that determines whether the application can be closed in code behind. That is a responsibility of the application logic, and the application logic lives in ViewModels, not Views.

Upvotes: 16

brunnerh
brunnerh

Reputation: 184271

I usually have a generic Shell class which subclasses Window and does something like:

public Shell()
{
    InitializeComponent();
    this.Closing += (s,e) =>
    {
        var canClose = Content as ICanClose;
        if (canClose != null)
            e.Cancel = !canClose.CanClose;
    }
}

That way it does not matter what kind of view model you put in, if it implements the interface that will be taken into account.

Don't see much point in externalizing the logic, and it's fine in terms of the MVVM pattern.

Upvotes: 4

Related Questions