WPF MVVM Code Behind Best Practices
Asked Answered
O

3

5

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();
    }
}
Oakie answered 6/6, 2013 at 22:38 Comment(0)
P
16

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.

Propitious answered 6/6, 2013 at 22:44 Comment(5)
The last paragraph is inaccurate, of course you can set properties on the VM from the view, you do it all the time (usually via data-binding).Daguerreotype
@H.B right. What I wanted to emphasize is that the application logic (probably based on whether some data is dirty/unsaved or not) should not go into code behind. I should probably reword that.Propitious
Thank you your response. I was suspecting it to be ok, just needed the confirmation.Oakie
"the application logic lives in ViewModels" - If application logic means business logic or business domain etc then this statement is wrong. Application logic is the core of the application and resides in the application model. The application view model is the intermediate layer that decouples the application view from the model. Maybe that's a typo.Khadijahkhai
Obviously Fede is right and that's not a typo. You might want to read learn.microsoft.com/en-us/archive/blogs/johngossman/…. The view model is an abstraction of the view; it's not merely a passive intermediary.Nonah
D
4

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.

Daguerreotype answered 6/6, 2013 at 22:45 Comment(3)
H.B. I'm thinking about your last paragraph, and the only reason I externalised is because I want to make it reusable and testable per definition. Would that be ok then, or is it still too much? (not sure about the testability part yet)Oakie
@JvN: Well, if you have multiple windows (rarely happens for me, just have one Shell wit various view models as its Content) you could do that. However you could also have a base window class (not allowed to define XAML for it i think) which implements the logic, there usually are several ways to keep the redundancy down...Daguerreotype
Uhm.. Base window class... That sounds promising, why did I not think of that, even though I just created a base ViewModel?? great tip, Thanks!Oakie
N
0

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.

Nonah answered 26/7, 2024 at 19:41 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.