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.
- Define an internal property of type
NavigationService
inApp.xaml.cs
internal NavigationService Navigator { get; set; }
- Add a protected static readonly field of Type
App
toViewModelBase
(base class for all view models) that holds a reference to theApp
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 » |
3 Comments »