WPF Series -2- WPF Application Design

Beside the vision and the requirements you also have to have an idea how the WPF application should look like in the end. For that purpose we created a few mock ups during first exchange with the customer in the early stages of the project. In the same stage we also created a process diagram (BPMN) that shows all the processes the application has to support. Both of them served as a good basis for discussion and helped to create a shared understanding of the application. Based on that we evaluated the requirements together with the customer.
After requirements engineering we started implementing the first screen. As it has been a while since our last WPF project a few questions popped up concerning WPF Application Design. In this post I’d like to address some of the questions that had to be clarified and the decisions we made.

Single Window vs. Multiple Windows

First we had to decide, if we implement a single window with multiple views (pages) or multiple windows. Based on the requirements of just a few views and for having a smooth application flow we have decided to implement a single window with multiple pages. We thereby saved dealing with multiple windows and we can make use of a navigation based structure by using the navigation service (see next section). This makes the desktop application take on the basic behaviour of a web browser application.
By default a navigation based WPF application provides a forward and back button. As the navigation service just gets used in backend to navigate from one to another page we disabled the navigation bar.

We named our window MainWindow. It acts like a container for all the pages and the code looks as follows. The UI is built using MahApps.Metro, a framework for creating WPF applications in metro style (more on this topic later in this series).

MainWindow.xaml

<controls:MetroWindow x:Class="biz.dfch.CS.ArbitraryWpfApplication.UI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
        xmlns:p="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.Properties"
        mc:Ignorable="d"

        Title="{x:Static p:Resources.Window_MainWindow_Title}"
        WindowState="Maximized" >

    <controls:MetroWindow.RightWindowCommands>
        <controls:WindowCommands>
            <Button Name="ButtonAbout" Content="{x:Static p:Resources.Window_MainWindow_Button__About}" Click="OnButtonAboutClick" />
        </controls:WindowCommands>
    </controls:MetroWindow.RightWindowCommands>

    <DockPanel>
        <Frame x:Name="PageFrame" Source="Pages/Home.xaml" NavigationUIVisibility="Hidden" HorizontalAlignment="Stretch" />
    </DockPanel>

</controls:MetroWindow>

The navigation bar gets disabled by setting NavigationUIVisibility to hidden. As you can see by default the Home page gets loaded inside the PageFrame.

MainWindow.xaml.cs
The code behind of MainWindow.xaml looks as follows.

/**
 * Copyright 2017 d-fens GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System.Windows;
using MahApps.Metro.Controls;
using MahApps.Metro.Controls.Dialogs;

namespace biz.dfch.CS.ArbitraryWpfApplication.UI
{
    public partial class MainWindow : MetroWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnButtonAboutClick(object sender, RoutedEventArgs e)
        {
            var aboutText = string.Format(Properties.Resources.Dialog_About__Message, typeof(MainWindow).Assembly.GetName().Version);
            this.ShowMessageAsync(Properties.Resources.Dialog_About__Title, aboutText);
        }
    }
}

App.xaml
In the App.xaml file the StartupUri is set to MainWindow.xaml so that the MainWindow gets shown on startup.

<Application x:Class="biz.dfch.CS.ArbitraryWpfApplication.UI"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             StartupUri="MainWindow.xaml">
</Application>

App.xaml.cs
The code behind of App.xaml looks as follows.

/**
 * Copyright 2017 d-fens GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using biz.dfch.CS.Commons.Diagnostics;
using TraceSource = biz.dfch.CS.Commons.Diagnostics.TraceSource;

namespace biz.dfch.CS.ArbitraryWpfApplication.UI
{
    public partial class App
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            traceSource.TraceEvent(TraceEventType.Information, (int)Logging.EventId.Start, Message.App_OnStartup__Initialise_START);

            traceSource.TraceEvent(TraceEventType.Information, (int)Logging.EventId.Stop, Message.App_OnStartup__Initialise_SUCCEEDED);
        }

        protected override void OnExit(ExitEventArgs e)
        {
            traceSource.TraceEvent(TraceEventType.Information, (int)Logging.EventId.Start, Message.App_OnExit__START);

            base.OnExit(e);

            traceSource.TraceEvent(TraceEventType.Information, (int)Logging.EventId.Stop, Message.App_OnExit__SUCCEEDED);
        }
    }
}

Navigation

Next thing we had to clarify is where navigation should take place. As mentioned before we make use of the NavigationService from System.Windows.Navigation namespace for navigation purposes. Furthermore we use custom Commands (inherit from System.Windows.Input.ICommand) that are bound to the buttons in the UI to handle button click events directly in ViewModel. From our point of view the navigation in such an application that follows the MVVM pattern (more on this topic later in this series) should take place in the corresponding ViewModel. Especially if for example data has to be saved or if some code has to be executed, that needs access to the ViewModel properties (i.e. command handlers), before navigating to another page. However according the MVVM pattern a ViewModel cannot reference to its View and by default the NavigationService cannot not be used in ViewModels. To make NavigationService available in ViewModels we did the following.

  1. Define an internal property of type NavigationService in App.xaml.cs
    internal NavigationService Navigator { get; set; }
    
  2. Add a protected static readonly field of Type App to ViewModelBase (base class for all view models) that holds a reference to the App
    protected static readonly App CurrentApp = Application.Current as App;
    

Now the NavigationService can be used in ViewModels (i.e. in command handlers) as follows.

CurrentApp.Navigator.Navigate(new ArbitraryPage());

When using NavigationService the application maintains a navigation history. As there is no backward navigation in our application and to avoid huge memory consumption we always remove the back entry from navigation history in OnNavigated event handler. The OnNavigated event handler in App.xaml.cs looks as follows.

protected override void OnNavigated(NavigationEventArgs e)
{
    var page = e.Content as Page;

    if (null != page)
    {
        Navigator = page.NavigationService;
    }

    Navigator?.RemoveBackEntry();
}

Separation of Duties

Last but not least we had to determine the guidelines concerning separation of duties between XAML, code behind and ViewModel. We agreed on the following list of guidelines. The list is not conclusive, however it’s a summary of the most important points.

XAML

  • Definition of UI controls
  • Structure of page/window
  • Definition of bindings
  • Definition of InkCanvas ans scrolling attributes
  • Appearance
    • Title
    • Alignment
    • Size

Code behind (.xaml.cs)

  • Initialisation of page/view
  • Assignment of DataContext
  • InkCanvas event handlers (i.e. InkCanvas_TouchEnter, …)
  • Webcam handling (needs access to the control by design)
  • App.xaml.cs
    • OnStartup event handler
    • OnExit event handler
    • OnNavigated event handler

ViewModel

  • Initialisation of Model
  • ViewModel properties
    • Validation attributes
    • Invocation of property changed event
    • Management of Model (value assignment)
  • Command handler
    • Navigation
« WPF Series -1- Project Setup WPF Series -3- MVVM Pattern »

Trackbacks

  1. […] WPF Series -2- WPF Application Design » […]

  2. […] « WPF Series -2- WPF Application Design […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: