Peregrine's View

Yet another C# / WPF / MVVM blog …

MVVM

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. Remember that 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, there will be a different implementation in each one – with the IoC container setup determining which to use. 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;
        view.ShowDialog();
    }
}

The second segment of this post covers how does a ViewModel close its corresponding View, without actually knowing anything about that window control.

A good example of this requirement would be an editing dialog – with the standard window close button removed. 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 a standard WPF Window to bind to. That’s no problem though – we can create one of our own using an attached property, just as we did previously for TreeViewItems …

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 …

<Window x:Class= ... 
        vhelp:perWindowHelper.CloseWindow="{Binding ViewClosed}">

The demo application shows all this functionality in a joined up solution. perDialogBase is an extended WPF 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.

If you find this article useful, or you have any other feedback, please leave a comment below.

One thought on “MVVM – Navigation

  • Excellent. This post is a treausure. Many other publications, tutorials, etc. They only addressed navigation using ContentPresenter/Frame as root element and pages, similar to web/mobile navigation, but had not found someone to address window navigation. Thank you.

    Reply

Leave a Reply to Johan Cancel reply

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