Search code examples
.netwpfdesign-patternsmvvmcode-behind

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

  • 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!


Solution

  • 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.