Pragmatic use of code-behind in MVVM pattern
Asked Answered
T

7

20

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

  • I would have an interface with methods which would be implemented by the view:

    public interface IView
    {
        void InsertTextAtCaretPosition(string text);
    }
    
    public partial class View : UserControl, IView
    {
        public View()
        {
            InitializeComponent();
        }
    
        // Interface implementation
        public void InsertTextAtCaretPosition(string text)
        {
            MyTextBox.Text = MyTextBox.Text.Insert(MyTextBox.CaretIndex, text);
        }
    }
    
  • Inject this interface into the ViewModel

    public class ViewModel : ViewModelBase
    {
        private readonly IView _view;
    
        public ViewModel(IView view)
        {
            _view = view;
        }
    }
    
  • Execute code-behind from a ViewModel's command handler through the interface methods

    public ICommand InsertCommand { get; private set; }
    // Bound for instance to a button command
    
    // Command handler
    private void InsertText(string text)
    {
        _view.InsertTextAtCaretPosition(text);
    }
    

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

  • The ViewModel is publisher of special events and command handlers raise those events

    public class ViewModel : ViewModelBase
    {
        public ViewModel()
        {
        }
    
        public event InsertTextEventHandler InsertTextEvent;
    
        // Command handler
        private void InsertText(string text)
        {
            InsertTextEventHandler handler = InsertTextEvent;
            if (handler != null)
                handler(this, new InsertTextEventArgs(text));
        }
    }
    
  • The View subscribes to these events

    public partial class View : UserControl
    {
        public View()
        {
            InitializeComponent();
        }
    
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            ViewModel viewModel = DataContext as ViewModel;
            if (viewModel != null)
                viewModel.InsertTextEvent += OnInsertTextEvent;
        }
    
        private void UserControl_Unloaded(object sender, RoutedEventArgs e)
        {
            ViewModel viewModel = DataContext as ViewModel;
            if (viewModel != null)
                viewModel.InsertTextEvent -= OnInsertTextEvent;
        }
    
        private void OnInsertTextEvent(object sender, InsertTextEventArgs e)
        {
            MyTextBox.Text = MyTextBox.Text.Insert(MyTextBox.CaretIndex, e.Text);
        }
    }
    

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!

Taxicab answered 2/4, 2011 at 16:34 Comment(2)
A behavior might work nice for this and no code behind. What is the requirement that causes the insert?Fixer
@Derek Beattie: There is a view which has a treeview on the left side. The elements in the treeview represent text fragments. In the middle is a button and on the right is the mentioned textbox. The user selects a node in the treeview and clicks on the button. Then the text fragment associated to the selected node must be inserted into the text which is already in the textbox at the position where the caret currently is. (The user can also write the text fragment by hand into the textbox, the described operation is only a help to avoid typos because only the texts in the treeview are "valid".)Taxicab
F
7

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.

Farland answered 3/4, 2011 at 6:44 Comment(5)
Hm, I can only find infos about IViewAware in the context of Caliburn or Prism which both I don't use. I'm not clear if this interface would be useful for me without those frameworks. What's the basic idea and how would it improve the 2 solutions in my question?Taxicab
Of course, this idea can be used outside (or without) these two frameworks. I updated my answer with a simplified example, please check.Farland
Hm, this looks a bit like a "bidirectional" event mechanism between View and ViewModel. The CloseView event in your example would be similar to my solution 2 in the question (View subscribes to events in the ViewModel). ViewActivated and ViewDeactivated are examples for the other direction (ViewModel subscribes to events in the View). Very interesting! Thanks for the illustrating example!Taxicab
IView goes exactly in the opposite way of MVVM. Most of MVVM's strength comes from the ViewModel's complete ignorance of a View.Treacherous
I would not agree that you loose anything by using IView. In the end you have UI separated from your code and ViewModel is fully testable. From other side, by introducing attached behaviors you move part of the code on UI side - you split the logic and decrease testability.Farland
T
24

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 :)

Treacherous answered 3/4, 2011 at 7:40 Comment(1)
Thanks for letting me know about attached behaviors. I've just started to read about it here: codeproject.com/KB/WPF/AttachedBehaviors.aspx Good to know about this option!Taxicab
F
7

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.

Farland answered 3/4, 2011 at 6:44 Comment(5)
Hm, I can only find infos about IViewAware in the context of Caliburn or Prism which both I don't use. I'm not clear if this interface would be useful for me without those frameworks. What's the basic idea and how would it improve the 2 solutions in my question?Taxicab
Of course, this idea can be used outside (or without) these two frameworks. I updated my answer with a simplified example, please check.Farland
Hm, this looks a bit like a "bidirectional" event mechanism between View and ViewModel. The CloseView event in your example would be similar to my solution 2 in the question (View subscribes to events in the ViewModel). ViewActivated and ViewDeactivated are examples for the other direction (ViewModel subscribes to events in the View). Very interesting! Thanks for the illustrating example!Taxicab
IView goes exactly in the opposite way of MVVM. Most of MVVM's strength comes from the ViewModel's complete ignorance of a View.Treacherous
I would not agree that you loose anything by using IView. In the end you have UI separated from your code and ViewModel is fully testable. From other side, by introducing attached behaviors you move part of the code on UI side - you split the logic and decrease testability.Farland
F
3

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.

Fijian answered 3/4, 2011 at 6:56 Comment(0)
S
2

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.

Swansdown answered 3/4, 2011 at 18:38 Comment(0)
H
1

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

Houghton answered 2/4, 2011 at 16:45 Comment(0)
F
1

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

Fixer answered 3/4, 2011 at 15:3 Comment(0)
C
-1

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.
Collegium answered 23/7, 2024 at 21:56 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.