MVVM – Navigation

The ‘Open/Closed Principle’ in action … ?

In the next few posts I’ll look at some aspects of WPF development that are often cited as excuses for not adopting the MVVM design pattern. Using the techniques I’ve introduced in previous posts, I’ll create some library functions that offer a clean-code way to solve these ‘hard’ MVVM problems.

The first issue to look at is how to open a new window and then close it again, all from within the ViewModel layer. The key tenet of MVVM is the clear separation between the user interface and business logic, so we shouldn’t write code like this in the ViewModel …

    private void OnOpenSettingsView()
    {
        var view = new SettingsView();
        view.DataContext = new SettingsViewModel();
        view.ShowDialog();
    }

I’ve already covered services in a previous post – data provision in the StaffManager demo application was extracted from the main ViewModel into a distinct module, that was accessed via an interface. We can do the same for navigation. Any navigation task the ViewModel wants to perform can be added to the application specific INavigationService interface, the implementation of which is defined elsewhere and set at runtime in the IoC container.

In the demo application for this post, the MainViewModel wants to display the selected DataItem in a separate window. The interface required for that is just …

    public interface INavigationService
    {
        void ShowDataItem(DataItem dataItem);
    }

The ViewModel doesn’t need to know how this functionality is implemented – all it cares about is that it can pass in a DataItem instance which will then be displayed to the user. If the ViewModel class is shared between multiple application variants, e.g. WPF / UWP / Xamarin / Windows Phone, there will be a different implementation in each one. In our demo WPF application, the INavigationService interface is implemented as …

    public class NavigationService : INavigationService
    {
        public void ShowDataItem(DataItem dataItem)
        {
            var view = new DataItemView();
            var viewModel = new DataItemViewModel(dataItem);
            view.DataContext = viewModel;
            view.Owner = Application.Current.MainWindow;
            view.WindowStartupLocation = WindowStartupLocation.CenterOwner;
            return view.ShowDialog();
        }
    }

The second segment of this issue covers how does the ViewModel close the window again. A good example of this requirement would be an editing dialog – with the standard window close button hidden. The save command can only execute once the entered data is valid, and that in turn will close the dialog. We already have a method for linking the ViewModel to the View – data binding. We can create a suitable property on the ViewModel – it is a nullable<bool> to match the return type of Window.ShowDialog()

    private bool? _viewClosed;

    public bool? ViewClosed
    {
        get { return _viewClosed; }
        set { Set(nameof(ViewClosed), ref _viewClosed, value); }
    }

However, there’s no suitable property on the View to bind to. No problem – we can create one of our own using an attached property, just as we did previously for TreeViews …

    public static class perWindowHelper
    {
        public static readonly DependencyProperty CloseWindowProperty = DependencyProperty.RegisterAttached(
            "CloseWindow",
            typeof(bool?),
            typeof(perWindowHelper),
            new PropertyMetadata(null, OnCloseWindowChanged));

        private static void OnCloseWindowChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
        {
            var view = target as Window;

            if (view == null)
                return;

            if (view.IsModal())
                view.DialogResult = args.NewValue as bool?;
            else
                view.Close();
        }

        public static void SetCloseWindow(Window target, bool? value)
        {
            target.SetValue(CloseWindowProperty, value);
        }

        public static bool IsModal(this Window window)
        {
            var fieldInfo = typeof(Window).GetField("_showingAsDialog", BindingFlags.Instance | BindingFlags.NonPublic);
            return fieldInfo != null && (bool)fieldInfo.GetValue(window);
        }
    }

When this CloseWindow property value is changed, it will close the View using the appropriate method – depending on whether it is modal or not. To use this new attached property, just bind to it …

    <view x:Class= ...
          vhelp:perWindowHelper.WindowClosed="{Binding ViewClosed}">

The demo application shows all this functionality in a joined up solution. perDialogBase is an extended Window class that hides the close button and disables the standard Alt-F4 (= close the window) functionality, using a couple of behavior classes. These behaviors are defined using the perBehaviorForStyle wrapper, so that they can be included in a style definition.

In my next post, I’ll continue with this theme by looking at another ‘hard’ MVVM problem – message dialogs.

Don’t forget that all of the code samples for this blog are available on Github, along with my own personal C# / WPF library.

Leave a Reply

Your email address will not be published. Required fields are marked *