Home > Sample chapters > Programming > Visual Studio and .NET

Designing and Developing Windows Applications Using Microsoft .NET Framework 4: Designing the Presentation Layer

Objective 2.3: Design Application Workflow

Some applications require no directed workflow or have very simplistic workflow requirements. Other applications can be very directional or even branching in the workflow design. This section describes how to implement user navigation in Windows Forms and WPF applications.

Implementing User Navigation

Simple client interfaces can require no more than a single form, but for more complex interfaces a navigable user experience is frequently required. Both Windows Forms and WPF allow you to incorporate user navigation into your applications.

MDI Forms in Windows Forms

MDI applications follow a parent form/child form model. An MDI application generally has a single parent form that contains and organizes multiple child forms (although it is possible for an application to have multiple parent forms). Microsoft Excel is an example of an MDI application—you can open multiple documents and work with them separately within the parent form. The parent form organizes and arranges all the child documents that are currently open.

Creating an MDI Parent Form

The parent form is the main form of any MDI application. This form contains all child forms that the user interacts with and handles the layout and organization of the child forms as well. It is a simple task to create an MDI parent form in Visual Studio.

To create an MDI parent form
  1. Create a new Windows Forms application.

  2. In the Properties window for the startup form, set the IsMDIContainer property to True. This designates the form as an MDI parent form.

Creating MDI Child Forms

MDI child forms are at the center of user interaction in MDI applications. They present the data to the user and generally contain individual documents. Child forms are contained within, and are managed by, a parent form. You can create an MDI child form by setting the MdiParent property of the form.

To create an MDI child form
  1. Create an MDI parent form, as described previously.

  2. In Visual Studio, add a second form to the project and add controls to implement the UI. This is the child form.

  3. In a method in the parent form, such as a menu item Click event handler, create a new instance of the child form and set its MdiParent property, as shown in the following example:

Sample of Visual Basic.NET code

' This example takes place in a method in the parent form, and
' assumes a Form called ChildForm
Dim aChildForm As New ChildForm
' Sets the MdiParent property to the parent form
aChildForm.MdiParent = Me

Sample of C# code

// This example takes place in a method in the parent form, and
// assumes a Form called ChildForm
ChildForm aChildForm = new ChildForm();
// Sets the MdiParent property to the parent form
aChildForm.MdiParent = this;

Identifying the Active Child Form

At times, you will want to identify the active child form in an MDI application. For example, a common feature of MDI applications is a central menu on the parent form that contains commands that act upon the child form that has the focus. You can use the ActiveMDIChild property of the parent form to obtain a reference to the form that was last accessed. The following code example demonstrates how to obtain a reference to the active child form:

Sample of Visual Basic.NET Code

' This example demonstrates how to obtain a reference to the active child
' form from a method inside the parent form
Dim aForm As Form
aForm = Me.ActiveMDIChild

Sample of C# Code

// This example demonstrates how to obtain a reference to the active child
// form from a method inside the parent form
Form aForm;
aForm = this.ActiveMDIChild;

Sending Data to the Active Child Form from the Clipboard

Once you have identified the active MDI form, you can use the properties of the form to send data from the Clipboard to an active control on the form. You might use this functionality to implement a Paste menu item to paste data from the Clipboard into a control. The following code example demonstrates how to determine if the active control is a text box and paste text from the Clipboard into the text box:

Sample of Visual Basic.NET Code

Dim activeForm As Form = Me.ActiveMDIChild
' Checks to see if an active form exists
If Not activeForm Is Nothing Then
   If activeForm.ActiveControl.GetType Is GetType(TextBox) Then
      Dim aTextBox As TextBox = CType(activeForm.ActiveControl, TextBox)
      ' Creates a new instance of the DataObject interface.
      Dim data As IDataObject = Clipboard.GetDataObject()
      ' Checks to see of the data in the data object is text. If it is,
      ' the text of the active Textbox is set to the text in the clipboard.
      If data.GetDataPresent(DataFormats.Text) Then
         aTextBox.Text = data.GetData(DataFormats.Text).ToString()
      End If
   End If
End If

Sample of C# Code

Form activeForm = this.ActiveMDIChild;
// Checks to see if an active form exists
if (activeForm != null)
    if (activeForm.ActiveControl.GetType() is TextBox)
       TextBox aTextBox = (TextBox)activeForm.ActiveControl;
       // Creates a new instance of the DataObject interface.
       IDataObject data = Clipboard.GetDataObject();
       // Checks to see of the data in the data object is text. If it is,
       // the text of the active Textbox is set to the text in the clipboard.
       if (data.GetDataPresent(DataFormats.Text))
          aTextBox.Text = data.GetData(DataFormats.Text).ToString();

Arranging MDI Child Forms

You will commonly want to organize the forms in an MDI application so that they are ordered. The MDI parent form can arrange the child forms that it contains by calling the LayoutMdi method. The LayoutMdi method takes a parameter that is a member of the MdiLayout enumeration. This method causes the forms contained by the parent form to be arranged in the manner specified by the parameter. The members of the MdiLayout enumeration are described in Table 2-7.

Table 2-7 MdiLayout Enumeration Members




All MDI child icons are arranged within the client region of the MDI parent form.


All MDI child windows are cascaded within the client region of the MDI parent form.


All MDI child windows are tiled horizontally within the client region of the MDI parent form.


All MDI child windows are tiled vertically within the client region of the MDI parent form.

The following example demonstrates the LayoutMdi method by causing the contained forms to cascade in the parent form:

Sample of Visual Basic.NET Code

' Causes the contained forms to cascade in the parent form

Sample of C# Code

// Causes the contained forms to cascade in the parent form

Navigation Applications in WPF

Unlike Windows applications, which are based on the WPF Window object and are toolbar-driven and menu-driven, page-based applications are based on the WPF Page object and are navigation-driven, meaning that the flow of the program is driven by navigating through multiple pages rather than interacting with existing windows by using menu and toolbar commands. Although the page-based model is limited in some ways, it lends itself very well to lightweight applications that are focused around a single task, such as a wizard or a shopping cart application. This section will focus primarily on the navigation of page-based applications.

Using Hyperlinks

The most familiar method of page-based navigation is by using hyperlinks. Hyperlinks are displayed as a section of text, usually underlined and in a different color than the surrounding text, which the user can click. When the user clicks a hyperlink, the application navigates to the page indicated by the hyperlink.

Hyperlinks expose a property called NavigateUri that indicates the target of the hyperlink. You set the NavigateUri property in XAML to indicate the navigation target when the hyperlink is clicked, as shown here:

<TextBlock>This is a <Hyperlink NavigateUri="Page2.xaml">hyperlink</Hyperlink>

Hyperlinks are not controls themselves—rather, they are inline flow elements. That means they must be placed within another element that supports inline flow elements, such as a TextBlock element. When a hyperlink pointing to another XAML page is clicked, a new instance of that page is created and the application navigates to that page. A hyperlink also can point to a PageFunction, but it is not possible to return a value from a PageFunction using a hyperlink. PageFunction objects are discussed in greater detail later in this section.

In addition to linking to other WPF pages, hyperlinks can link your application to webpages. You can link an application to a webpage by supplying the Hypertext Transfer Protocol (HTTP) address in the NavigateUri property, as shown here:

<TextBlock>This is a <Hyperlink NavigateUri="http://www.microsoft.com">hyperlink</Hyperlink> </TextBlock>

You can set the NavigateUri property of a hyperlink dynamically in code. This allows you to change the navigation target of a hyperlink in response to program conditions. To change the NavigateURI property dynamically, you must set the Name property of the hyperlink in XAML, as shown here in bold:

<TextBlock>This is a <Hyperlink Name="myLink">hyperlink</Hyperlink></TextBlock>

Then you can set the NavigateUri property in code, as shown here:

Sample of Visual Basic.NET Code

myLink.NavigateUri = New System.Uri("Page2.xaml", System.UriKind.Relative)

Sample of C# Code

myLink.NavigateUri = new System.Uri("Page2.xaml", System.UriKind.Relative);

Using NavigationService

Hyperlinks provide a fairly easy way to navigate between pages, but for more complicated navigational models, the NavigationService class provides finer control.

You can obtain a reference to the NavigationService class by calling the static GetNavigationService method, as shown here:

Sample of Visual Basic.NET Code

Dim myNav As NavigationService
myNav = NavigationService.GetNavigationService(Me)

Sample of C# Code

NavigationService myNav; myNav = NavigationService.GetNavigationService(this);

The NavigationService exposes a method called Navigate, which causes the application to navigate to the specified page. The most common way to use the Navigate method is to provide an instance of a Uniform Resource Identifier (URI), as shown here:

Sample of Visual Basic.NET Code

myNav.Navigate(New System.Uri("Page2.xaml", UriKind.Relative))

Sample of C# Code

myNav.Navigate(new System.Uri("Page2.xaml", UriKind.Relative));

You also can create an instance of a new page in memory and navigate to it with the Navigate method, as shown here:

Sample of Visual Basic.NET Code

Dim aPage As New Page2()

Sample of C# Code

Page2 aPage = new Page2();

There are advantages and disadvantages to each method. When passing a URI to the Navigate method, the application’s journal can maintain the page data without having to maintain the entire page object in memory. Thus, memory overhead is lower using this method. However, it is not possible to pass information between pages using a URI. You can pass information between pages by creating a custom constructor for your page and using it to pass information or by setting properties on the page prior to navigating to it.

You can use the NavigationService to refresh your page by calling NavigationService.Refresh. NavigationService also allows you to navigate forward and backward in the journal by calling NavigationService.GoForward and NavigationService.GoBack, respectively. These methods are demonstrated here:

Sample of Visual Basic.NET Code


Sample of C# Code


The NavigationService also exposes two Boolean properties called CanGoBack and CanGoForward, which you can query to determine if the application can navigate backward or forward. An example is shown here:

Sample of Visual Basic.NET Code

If myNav.CanGoBack = True Then
End If

Sample of C# Code

if (myNav.CanGoBack)

Navigation is asynchronous. Thus, you can cancel navigation before it is completed by calling NavigationService.StopLoading, as shown here:

Sample of Visual Basic.NET Code


Sample of C# Code


Hosting Pages in Frames

In addition to hosting a stand-alone Navigation application in a NavigationWindow, you also can host a page inside a control called a frame. A frame is simply a host for a XAML page or a webpage and is itself hosted inside a page or window. This allows you to incorporate navigation-based sections into a Windows application.

The Source property of the Frame control indicates the page to be loaded into the frame. The following code demonstrates how to set the source for a frame in XAML:

<Frame Margin="66,98,12,64" Name="frame1" Source="Page2.xaml"/>

Using the Journal

The journal is a bit of built-in technology in XBAPs and Navigation applications that keeps a list of the pages that have been visited and allows you to navigate this list. This will be familiar to anyone who uses Internet Explorer—the Back button navigates backward in the history to previously visited pages. The NavigationService allows you to manipulate the contents of the journal.

Removing Items from the Journal

You might want to remove items from the journal. For example, suppose that your application has a complex configuration step that runs through several pages initially but is required only once. After configuration, you might want to remove these journal entries so that the user could navigate the regular pages without reloading the configuration pages. Removing items from the journal is fairly straightforward. NavigationService provides a method called RemoveBackEntry, which removes the last entry in the journal and returns an instance of JournalEntry that describes the instance that was removed. The following example demonstrates how to remove the last item from the journal:

Sample of Visual Basic.NET Code


Sample of C# Code


You can use the CanGoBack property to remove all the items in the journal, as shown here:

Sample of Visual Basic.NET Code

While myNav.CanGoback
End While

Sample of C# Code

while (myNav.CanGoBack)
Adding Items to the Journal

Adding items to the journal is considerably less straightforward than removing them. In general, you want to add items to the journal only when you want to take a “snapshot” of the state of a single page and allow the user to navigate back to previous states. For example, if you were performing a complex configuration task with multiple steps on a single page, you could provide custom journal entries to allow the user to roll back changes before they were committed.

NavigationService provides a method called AddBackEntry, but it is more difficult to use than it appears and is considerably more complicated than RemoveBackEntry. It takes a single parameter, which is an instance of a class that derives from CustomContentState. This class, which you must implement, stores the state information for the page and reconstitutes the page state when the custom entry is navigated to. You also must implement the IProvideCustomContentState interface in the page for which you want to provide custom journal entries. Finally, you must add the custom journal entry manually at the point that you want to take the snapshot. The following is a high-level procedure that describes the general protocol for adding custom journal entries.

To add custom journal entries
  1. Create a class that inherits CustomContentState. (You need a separate class for each page for which you want to add custom entries.) This class also must be marked with the Serializable attribute.

  2. Add member variables and public properties to this class that hold the state of each control on the page that you want to constitute.

  3. Add code to override the JournalEntryName property, which indicates the name that will be displayed in the journal. Often you might want to set this value in the constructor for this class, or use a method to determine an automatic name.

  4. Override the Replay method. This method is called when the application navigates backward or forward in the journal and is used to reconstitute the page. Although there are a few different approaches here, the best method is to create a callback method that executes a method in the page that receives the class instance as a parameter, thereby allowing the page access to the stored data.

  5. Create a constructor for this class. The constructor should set the value of all data about the state of the page that needs to be stored. It also should indicate the address of the callback method for the Replay method and any other parameters that you need for this instance (such as the JournalEntryName).

  6. In the page for which a custom journal entry will be created, create a method that handles the callback from the Replay method. This method should use the information in the passed parameter to restore the state of the page.

  7. Implement the IProvideCustomContentState in the page. This involves implementing the method GetContentState. GetContentState must return a CustomContentState object—you return an instance of your class in this method.

  8. Add code that calls the NavigationService.AddBackEntry method at each point for which you want to create a custom journal entry. Each time you call this method, you must provide a new instance of your class to save the custom state.

Handling Navigation Events

Navigation in WPF applications occurs asynchronously. Thus, the NavigationService.Navigate method will return before navigation is complete. NavigationService exposes several events that allow your application to react at different points in the navigation process. You can handle these events to provide custom validation, to update navigation progress, or to add any other custom navigation functionality that is required. Table 2-8 summarizes the navigation events exposed by NavigationService. The events are listed in the order in which they occur.

Table 2-8 Navigation Events Exposed by NavigationService




The Navigating event occurs just as navigation begins.


The Navigated event occurs after navigation has been initiated but before the target page has been retrieved.


The NavigationProgess event is raised after every 1 kilobyte (KB) of data has been received from the new page.


The LoadCompleted event is raised after the page has finished loading but before any of the page events fire.


The FragmentNavigation event occurs as the page is about to be scrolled to the target element. This event does not fire if you do not use a URI with a target element.


The NavigationStopped event fires when the StopLoading method is called. Note that this event is not fired if navigation is canceled in the Navigating event handler.


The NavigationFailed event is raised if the requested page cannot be located or downloaded.

Note that the NavigationService events fire whether navigation occurs through the NavigationService or through hyperlink clicks.

Because NavigationService events are regular .NET events, not routed events, you can create event handlers by creating methods with the correct signature and then attaching them to the event with the AddHandler operator (in Visual Basic) or the += operator (in C#), as shown here:

Sample of Visual Basic.NET Code

Public Sub HandleNavigated(ByVal sender As Object, _
   ByVal e As System.Windows.Navigation.NavigationEventArgs)
  ' Event Handling Code goes here
End Sub
Public Sub HookupEventHandler()
   ' Hookup the event handler
   AddHandler NavigationService.Navigated, AddressOf HandleNavigated
End Sub

Sample of C# Code

public void HandleNavigated(object sender,
    // Event handling code goes here
public void HookupEventHandler()
    NavigationService.Navigated += HandleNavigated;
Passing Information to Navigation Events

The NavigationService.Navigate method exposes overloads that allow you to pass additional information that becomes available when navigation events are being handled. For example, you might pass time stamp information or an object that could be used to validate the page request. To pass additional information to the event handlers, simply call one of the overloads of NavigationService.Navigate that takes an additional object parameter, as shown here:

Sample of Visual Basic.NET Code

NavigationService.Navigate(New System.Uri("page2.xaml"), "user = Joe")

Sample of C# Code

NavigationService.Navigate(new System.Uri("page2.xaml"), "user = Joe");

The additional information will be available in the Navigated, NavigationStopped, and LoadCompleted events through the e.ExtraData property, as shown here:

Sample of Visual Basic.NET Code

Public Sub Navigate(ByVal sender As Object,
   ByVal e As _System.Windows.Navigation.NavigationEventArgs)
   If e.ExtraData.ToString = "user=Kilroy" Then
      Trace.WriteLine("Kilroy was here")
   End If
End Sub

Sample of C# Code

public void Navigate(object sender,
   System.Windows.Navigation.NavigationEventArgs e)
      if (e.ExtraData.ToString() == "user=Kilroy")
      Trace.WriteLine("Kilroy was here");
Cancelling Navigation

You can cancel navigation in the Navigating event handler by setting the e.Cancel property to True, as shown here:

Sample of Visual Basic.NET Code

Public Sub NavigatingHandler(ByVal sender As Object, _
   ByVal e As System.Windows.Navigation.NavigatingCancelEventArgs)
   e.Cancel = True
End Sub

Sample of C# Code

public void NavigatingHandler(object sender,
   System.Windows.Navigation.NavigatingCancelEventArgs e)
      e.Cancel = true;

Using PageFunction Objects

The PageFunction class is very similar to the Page class. You can design a PageFunction in the designer, you can add controls to a PageFunction, and you can navigate to a PageFunction through hyperlinks or by using NavigationService. The principal difference between Page objects and PageFunction objects is that PageFunction objects can return a value. This allows you to create pages that act in an analogous manner to dialog boxes—they can collect user information and then return that information to the main page.

To add a PageFunction object to a project

  1. From the Project menu, choose Add New Item to open the Add New Item dialog box.

  2. In the Add New Item dialog box, select PageFunction (WPF). Name your PageFunction and click Add.

    A PageFunction can return any type of .NET object. When you add a PageFunction to your project, Visual Studio automatically configures it to return a String instance. Although this is frequently useful, you might want to return some other kind of object from a PageFunction, such as an integer or an object. Changing the return type of your PageFunction is relatively straightforward.

To change the return type of your PageFunction

  1. In XAML view, locate the line in the PageFunction XAML that reads:


    Then change the TypeArguments parameter to the type you want, as follows:


    For Visual Basic, that is all you need to do. For C#, the following additional step is required.

  2. In Code view, locate the class declaration and change the type, as shown here:

    public partial class PageFunction1 : PageFunction<Object>

    When you are ready for your PageFunction to return a value, you should call the OnReturn method. The OnReturn method takes a parameter of the type specified for the PageFunction. You also can return null for this parameter if no return value is required. The page that navigated to the PageFunction should handle the Returned event for that PageFunction. The instance of ReturnEventArgs returned by that event contains the returned value.

To return a value from a PageFunction

  1. In the page that navigates to the PageFunction, create a method that handles the Returned method of that PageFunction. An example is shown here:

    Sample of Visual Basic.NET Code

    Public Sub ReturnHandler(ByVal sender As Object, _
       ByVal e As ReturnEventArgs(Of String))
       myString = e.Result
    End Sub

    Sample of C# Code

    public void ReturnHandler(object sender,
       ReturnEventArgs<string> e)
       myString = e.Result;
  2. In the page that navigates to the PageFunction, instantiate the PageFunction programmatically and add code to hook up the PageFunction.Returned event to the new event handler, as shown here:

    Sample of Visual Basic.NET Code

    Dim myPage As New PageFunction1
    AddHandler myPage.Return, AddressOf ReturnHandler

    Sample of C# Code

    PageFunction1 myPage = new PageFunction1();
    myPage.Return += ReturnHandler;
  3. In the PageFunction, after the task is completed, call the OnReturn method and pass the return value in a new instance of ReturnEventArgs, as shown here:

    Sample of Visual Basic.NET Code

    OnReturn(New ReturnEventArgs(Of String)("Kilroy was here"))

    Sample of C# Code

    OnReturn(new ReturnEventArgs<String>("Kilroy was here"));

Removing PageFunction Entries from the Journal

Because PageFunction objects are used frequently to collect user input, you might not want to allow the user to return to a PageFunction via the journal after the task is completed. The PageFunction class exposes a property called RemoveFromJournal. When RemoveFromJournal is set to True, PageFunction entries are deleted automatically from the journal once the user is finished with the task.

Simple Navigation and Structured Navigation

Simple navigation is a common design model in lightweight page-based applications. An application with simple navigation has a start, an end, and a series of pages though which the user navigates. There is generally little or no branching, and after a page is visited, it generally is not returned to unless the user wants to back up. Although this paradigm is well suited to certain types of applications, such as a configuration wizard, other kinds of applications might find it lacking. Consider a shopping cart application. A user might want to add items to a shopping cart, return to shop for more items, add them to the shopping cart, repeat this a few more times, and then check out. Strictly linear navigation would be insufficient in this case.

PageFunction objects allow you to build more structure into your application. With PageFunction objects, you can allow your users to leave the main line of execution to perform tasks and then return. Using PageFunction objects, you can create execution models with complex flow structures, and by manipulating the journal, you can control how a user is able to navigate back through the application.

Designing for Different Input Types

Different input types require different UI designs. When designing an application for deployment in a public kiosk, keep these factors in mind:

  • Design the application to be full-screen, and remove control buttons that might allow a user to move, resize, or minimize a window.

  • Users will speak different languages, so rely more on icons and images than text.

  • Users will have varying accessibility requirements. Keep buttons and text large, simple, and high-contrast.

  • Users will not spend time learning how to use your application. Defaults should be carefully selected, the UI must be as simple as possible, and you should limit the number of choices presented to the user at a time to less than four.

  • Even with a touch-screen interface, avoid complex interactions such as dragging, pinching, or using multiple fingers. The only user interaction should be touching the screen or pressing a button.

  • Perform extensive usability testing. For more information about testing, refer to Objective 5.2.

When designing a UI for a mobile device, keep these factors in mind:

  • Build a task-based UI focused on the most common tasks. Use buttons instead of menus.

  • Minimize the amount of typing required. For example, instead of prompting the user to type their state, provide them with a list they can select from.

  • Avoid requiring users to scroll. Instead, separate the UI into more pages.

  • Plan for users to be interrupted regularly. Mobile application use tends to be interrupted more often.

  • Design the UI behavior to be as similar as possible to the operating system’s default behavior. For example, support swiping, panning, and pinch-to-zoom (but do not require the user to take advantage of those capabilities).

  • Support both horizontal and vertical screen orientations.

Objective Summary

  • User navigation can be implemented in Windows Forms application through the use of MDI forms. MDI forms allow multiple forms to be arranged and navigated as part of a single application.

  • Navigation applications in WPF provide an easy-to-program navigational experience that allows forward and backward navigation as well as branching. The NavigationService object provides the basic functionality required for user navigation.

  • The Journal object allows you to keep a record of past states of a navigation application in case changes need to be rolled back.

  • PageFunction objects behave like pages, but they return a value and can be useful for receiving user input.

Objective Review

Answer the following questions to test your knowledge of the information in this objective. You can find the answers to these questions and explanations of why each answer choice is correct or incorrect in the “Answers” section at the end of the chapter.

  1. Which of the following is NOT required to implement custom back entries?

    1. A Page or PageFunction that implements IProvideCustomContentState

    2. Code that calls NavigationService.AddBackEntry

    3. An instance of the JournalEntry class

    4. A class that inherits from CustomContentState

  2. Which of the following is the correct firing order for navigation events in a navigation application?

    1. Navigating





    2. Navigating





    3. Navigating





    4. Navigating