Slauma
Slauma

Reputation: 177163

Pragmatic use of code-behind in MVVM pattern

I'm trying to follow the MVVM pattern in a WPF application as good as I can, mainly to be able to create unit tests for my ViewModel logic.

In most cases data binding between ViewModel properties and properties of visual elements works fine and is easy. But sometimes I encounter situations where I cannot see an obvious and straightforward way while a solution to access and manipulate controls from code-behind is very easy.

Here is an example of what I mean: Inserting a text fragment into a TextBox at the current caret position

Since CaretIndex isn't a dependency property it can't be bound directly to a ViewModel's property. Here is a solution to work around this limitation by creating a dependency property. And here is the solution to do this in code-behind. I would prefer the code-behind way in this situation. Another problem I recently had was binding a dynamic collection of columns to a WPF datagrid. It was clear and simple to program in code-behind. But for a MVVM-friendly databinding approach I could only find work arounds in several blogs which all looked quite complex to me and had various limitations in one or the other aspect.

I don't want to keep the MVVM architecture clean of code-behind logic at all costs. If the amount of work arounds is too big, a MVVM-friendly solution requires a lot of code which I don't fully understand (I'm still a WPF beginner) and is too time consuming I prefer a code-behind solution and sacrifice automatic testability of a few parts of my application.

For the mentioned pragmatic reasons I am looking now for "patterns" to make controlled use of code-behind in an application without breaking the MVVM architecture or without breaking it too much.

Up to now I've found and tested two solutions. I will draw rough sketches with the Caret Position example:

Solution 1) Give the ViewModel a reference to the View through an abstract interface

To create a View-ViewModel pair I would use dependency injection to instantiate the concrete View and inject it into the ViewModel.

Solution 2) Execute code-behind methods through events

I am not sure if the Loaded and Unloaded events of the UserControl are good places to subscribe and unsubscribe to the events but I couldn't find problems during test.

I have tested both approaches in two simple examples and they both seem to work. Now my questions are:

  1. Which approach do you think is preferable? Are there any benefits or downsides of one of the solutions which I possibly don't see?

  2. Do you see (and perhaps practice) other solutions?

Thank you for feedback in advance!

Upvotes: 20

Views: 6009

Answers (7)

Emperor Eto
Emperor Eto

Reputation: 3526

I agree with many of the other answers but I thought it would be useful to show a full MVVM-friendly implementation using an attached DependencyProperty that avoids code-behind or dependency injection:

public static class AttachedHelpers
{
    public static readonly DependencyProperty InsertTextProperty =
        DependencyProperty.RegisterAttached(
            "InsertText",
            typeof(string),
            typeof(AttachedHelpers),
            new FrameworkPropertyMetadata(
                (string)null,
                // Likewise the default should be two-way so we can 
                // update the bound property to null after its use
                // to ensure no accidental re-use.
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                new PropertyChangedCallback((d, e) =>
                {
                    if (!(d is TextBox tb) || !(e.NewValue is string s))
                        // no-op
                        return;
                    tb.Text.Insert(tb.CaretIndex, s);
                    // This ensures the value is used only once and
                    // text doesn't get re-inserted if the TextBox
                    // loses and regains its DataContext for some reason.
                    tb.SetCurrentValue(InsertTextProperty, null);
                })));
    public static string GetInsertText(DependencyObject obj)
    {
        return (string)obj.GetValue(InsertTextProperty);
    }
    public static void SetInsertText(DependencyObject obj, string value) 
    { 
        obj.SetValue(InsertTextProperty, value);
    }
}

public class ViewModel : ViewModelBase
{
    #region string TextToInsert property
    private string _textToInsert;
    public string TextToInsert
    {
        get
        {
            return _textToInsert;
        }
        set
        {
            _textToInsert = value;
            OnPropertyChanged();
        }
    }
    #endregion

    // Command handler
    private void InsertText(string text)
    {
        this.TextToInsert = text;
    }
}

And the very simple XAML:

 <TextBox so:AttachedHelpers.InsertText="{Binding InsertTextProperty}" />

Regarding the other ideas presented in the question:

  • I would absolutely not try to give the view model an IView interface for direct control. While it's right to not want to ever give the view model access to view classes (or any WPF dependent types), the IView paradigm does the wrong thing for the wrong reasons.

    One of the original motives for MVVM and the XAML binding scheme more specifically was to limit contact points between view and view model to declaratively-defined bindings. Imperative code is perfectly fine when confined to one domain or the other, but if you let the view and view model interact through imperative code (whether that imperative code lives on the view or view model side), the pattern and its benefits break down. An IView paradigm is no less of an affront to the goal of a declaratively-defined UI than excessive code-behind, and probably even more so because it scales so poorly at that.

  • "I don't want to keep the MVVM architecture clean of code-behind logic at all costs." 100%. MVVM isn't a suicide pact. But we have to be careful what we mean by code-behind because not all C# code in the view layer is equally bad (or good). After all, WPF itself is 90+% coded in C#. When we talk about the kind of "code-behind" that MVVM discourages, we mean partial UserControl-derived classes with named elements declared in XAML being exposed to the C# code as private members, and event handlers declared in XAML and defined in the .xaml.cs file. That's code-behind. Code-behind is not subclassing Controls or writing attached properties or behaviors. That's just plain code.

    Moreover, code-behind, even as defined above, is fine when its purpose is solely to interact with and/or manipulate other UI elements, where making a subclassed Control would be overkill. But directly accessing - let alone manipulating - the view model through partial class code-behind is indeed something to try to avoid - not at "all costs", but when it's reasonable to do so.

    Your second idea - to have the view model expose an event and have the view subscribe to it - is not bad, and there are times when there's virtually no other way to accomplish something, but in this case I don't love it. It really has the same problem as IView, except problem moved from view model to view. It's still an imperative, rather than declarative, meeting point to have to maintain/debug/etc.

So why do I prefer my solution (besides the fact that I wrote it? :))?

  • Unlike either the IView or event solutions, an attached property is reusable, since it depends on no application-specific logic. You can put the AttachedHelpers class in a helper library that you maintain for your whole career and reuse (and add to) as needed with any other WPF app. Subclassing TextBox would also work, and is philosophically the same.
  • Leaving aside the somewhat clunky but necessary boilerplate code associated with attached properties, the rest of the solution is super-simple, easy to read and write, and easy to debug. You avoid messy code-behind, and the cost is, well, IMHO the cost is in the negative because I think this solution is even simpler than any other that's been proposed (at least, again, giving allowance for the boilerplate attached property), and brings added value beyond the specific application.

Upvotes: -1

Elad Katz
Elad Katz

Reputation: 7601

Specifically for this problem

The simplest solution to this specific case is adding an Attached Property to do it, or a Behavior. Behaviors can be a silver bullet for most of these rich-gui-not-supported cases in MVVM.

As for the general case

ViewModel should never ever under any circumstance know about the view. Not even about an IView. In MVVM it's "always look up", which means a View can look at the VM, and the VM can look at the Model. Never the other way around. This creates much better maintainability, since this way the ViewModel doesn't do two things (in charge of logic AND the gui), but only one thing. This is where MVVM is superior to any prior MV* pattern.

I would also try to refrain from having the View rely on the ViewModel in a coupled way. This creates ugly code, and a breakable dependency between the two classes, but sometimes this is more pragmatic as you said. A prettier way is to send a Loose Message (e.g. Messenger in MVVMLight, or EventAggregator in Prism) from the ViewModel to the View, and thus there is no strong dependency between the two. Some think this is better although IMO this is still a dependency.

Writing code in the View is OK in some situations, and this could be one of those situation. You could achieve a perfect solution using Attached-Behaviors, but the principle is important, like you asked.

MVVM is problematic when you need GUI that is very rich or the UI doesn't have the right Properties to bind to. In those situations you would resort to one of three things:

  1. Attached Behaviors.
  2. Deriving from existing controls and adding the properties you'd like.
  3. Actually writing code in the View.

All of those ways are legitimate, but I've ordered them according to what you should resort to first.

To Summarize

The most important thing you have to keep in MVVM is not to keep the code-behind free, but to keep all logic & data to the ViewModel, as the View must only contain View-related code. The reason architects tell you not to write code-behind at all is only because it's a slippery slope. You start writing something small, and you end up doing logical stuff or maintaining application state in the View, which is the big no-no.

Happy MVVMing :)

Upvotes: 24

Derek Beattie
Derek Beattie

Reputation: 9478

I would try and implement this as a blend behavior of the text box similar to this example of selecting and expanding a tree view without using code behind. I'll try and trow an example together. http://www.codeproject.com/KB/silverlight/ViewModelTree.aspx

Edit: Elad already mentioned using attached behaviors, which, after doing a couple, really make doing things like this simple.

Another example of a behavior for popup windows in an mvvm fashion: http://www.codeproject.com/KB/silverlight/HisowaSimplePopUpBehavior.aspx

Upvotes: 1

Vladimir Dorokhov
Vladimir Dorokhov

Reputation: 3839

Often you need work with controls from code behind when the control is hardly compotible with MVVM. In this case you can use AttachedProperties, EventTriggers, Behaviors from blend SDK to extend functionality of the control. But very often I use inheritance to extend functionality of control and make it more MVVM compatible. You can create own set of controls inherited from base with implemented view functionality. A big advantage of this approach is that you can access ControlTemplate controls, it often neccessary to implement specific view functionality.

Upvotes: 2

Andrei Marukovich
Andrei Marukovich

Reputation: 699

Developing WPF applications I found both ways useful. If you need just one call from ViewModel to View, the second option, with event handler, looks simpler and good enough. But if you are requiring more complex interface between these layers, then it makes sense to introduce interface.

And my personal preference is to revert your option one and have a IViewAware interface implemented by my ViewModel and inject this ViewModel into View. Looks like an option three.

public interface IViewAware
{
    void ViewActivated();
    void ViewDeactivated();

    event Action CloseView;
}

public class TaskViewModel : ViewModelBase, IViewAware
{

    private void FireCloseRequest()
    {
        var handler = CloseView;
        if (handler != null)
            handler();
    }

    #region Implementation of IViewAware        
    public void ViewActivated()
    {
        // Do something 
    }

    public void ViewDeactivated()
    {
        // Do something 
    }

    public event Action CloseView;    
    #endregion
}

And this a simplified code for your View:

    public View(IViewAware viewModel) : this()
    {
        _viewModel = viewModel;

        DataContext = viewModel;
        Loaded += ViewLoaded;

    }

    void ViewLoaded(object sender, RoutedEventArgs e)
    {
        Activated += (o, v) => _viewModel.ViewActivated();
        Deactivated += (o, v) => _viewModel.ViewDeactivated();

        _viewModel.CloseView += Close;
    }

In real application I usually use an external logic to connect V and VM, for example Attached Behaviors.

Upvotes: 7

Emond
Emond

Reputation: 50692

I'd try to keep away from having the ViewModel a reference to the View.

A way of doing that in this case:

Derive from TextBox and add a dependency property that wraps the CaretIndex by subscribing to the OnSelectionChanged event that lets you know the caret has moved.

This way the ViewModel is able to know where the caret is by binding to it.

Upvotes: 3

Chris Ballard
Chris Ballard

Reputation: 3769

To my mind, the first option is preferable. It still maintains the separation between the View and the ViewModel (via the view interface), and keeps things in their logical places. The use of events is less intuitive.

I am in favour of pragmatic use of code behind in situations where it is either impossible to achieve through bindings, or requires you to add hundreds of lines of XAML to achieve what we can achieve with 3 lines of code behind.

My gut feeling is that if you can more or less be sure of correctness by code review of the code behind (which is the same as what we do with XAML anyway) and keep the main complexity where we can unit test it - ie the ViewModel, then we have a happy medium. It is all too easy to create technically pure MVVM which is a maintainability nightmare.

All IMHO :D

Upvotes: 1

Related Questions