App Fundamentals
Build A Great User Experience With Windows Presentation Foundation
Michael Weinhardt
This article is based on prerelease versions of WPF and the .NET Framework 3.0. All information herein is subject to change
This article discusses:
|
This article uses the following technologies: .NET Framework 3.0, Visual Studio 2005, XAML, and C# |
Code download available at:WPF2006_10.exe(214 KB)
Contents
Application Type
The Window
The Page Class
Hyperlink Class
NavigationWindow
NavigationService
XAML Browser Applications
Frame
Windows Presentation Foundation Resources
PageFunction
Where Are We?
Auser experience is a sum of its content and the way its content is hosted. In Windows® Presentation Foundation, content is created using standard controls, 2D and 3D graphics, animation, data binding, layouts, styling, and templating. However, the content is fairly useless until it is hosted in a way that allows users to both see and interact with it. The content needs to be packaged within an application and displayed through a window. This is where an application model comes in handy.
The Windows Presentation Foundation application model distinguishes between two application types: standalone and browser. A standalone application displays content through its own windows, dialogs, and message boxes, while a browser application consists of its own pages that are hosted in a browser.
Similarly, Windows Presentation Foundation distinguishes between two styles of navigation: menu driven and hyperlink driven. Menu-driven applications allow users to navigate content and functionality using menu bars, tool bars, windows, and dialogs, just as with any traditional desktop Windows-based application. Hyperlink-driven uses hyperlinks to deliver a navigation experience that resembles Web applications.
It goes without saying that standalone applications naturally support menu-driven navigation and browser applications naturally support hyperlink-driven navigation. But the Windows Presentation Foundation application model lets you mix and match elements of both. In most cases, this involves either partially or completely integrating a hyperlink-driven experience into a standalone application. The combination you use should be based upon the type of experience that will most benefit your users. Once you've decided on the experience you want to deliver, you can use the Windows Presentation Foundation application model to build it.
Application Type
Consider the sample Box Application, shown in Figure 1. This is a standalone, menu-driven application that allows people who need boxes to list, order, view, and delete box orders. To provide this user experience, you need to begin with the most fundamental of all application model building blocks: creating an application.
Figure 1** The Box Application **(Click the image for a larger view)
A Windows-based application consists of some standard plumbing, including both an entry point and a message loop, and possibly may also require one or more of the following common application services:
- Processing command-line parameters
- Returning an exit code
- Application-scope state
- Detecting and responding to unhandled exceptions
- Managing application lifetime
Windows Presentation Foundation centralizes both plumbing and services within a single type, System.Windows.Application, which you can use from markup (XAML), code (C# or Visual Basic®), or a combination of both (known as markup and codebehind). Application turns out to be so useful that Visual Studio® 2005 automatically adds an instance of one to every new .NET Framework 3.0 (formerly known as WinFX®) Windows Application project:
<!--App.xaml (markup)--> <Application xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" x:Class="BoxApplicationWindow.App" /> // App.xaml.cs (codebehind) public partial class App : Application { ... }
If you've programmed with a Windows presentation technology before, such as Windows Forms and Win32®, you may be in for a surprise. There isn't a single piece of code that looks remotely like the code used to establish the standard Windows-based application plumbing, including the entry point. This is because the application plumbing is generated for you, which is a result of Visual Studio 2005 configuring your Application markup file as an ApplicationDefinition build action, as shown in Figure 2.
Figure 2** Set an Application XAML File **
Under the covers, the equivalent of this code is generated:
// App.cs using System; public partial class App : Application { [STAThread] public static void Main() { // Initialize and run the application App application = new App(); application.Run(); } }
What exactly is created doesn't really matter, though, since you neither have to write it nor understand its intricacies. Instead, you are shielded by the most complete application abstraction in a Microsoft presentation technology to date, which you can use to create a running application with only a single piece of markup. All you need to do is use Application's services. For standalone applications, this involves showing a window when an application starts running.
The Window
In Windows Presentation Foundation, a window is a Window. A Window has always been the core unit of content hosting in standalone applications. You can add a Window definition to your project in Visual Studio 2005 by choosing Project | Add New Item | WinFX Window, which generates the following:
<!--MainWindow.xaml (markup)--> <Window xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" x:Class="BoxApplication.MainWindow" </Window> // MainWindow.xaml.cs (codebehind) using System.Windows; public partial class MainWindow : Window { ... }
When the window definition is added, Visual Studio 2005 automatically configures the markup file's build type to Page. When built, the markup is turned into a special type of resource that can be identified uniquely by a Uniform Resource Identifier (URI). Essentially, this enables Windows Presentation Foundation to load a Window declaratively from a URI, and you can use this capability to specify a window to be automatically opened when an application starts. You do this by setting the Application.StartupUri attribute in markup, as shown here:
<!--App.xaml (markup)--> <Application ... StartupUri="MainWindow.xaml" />
This creates and shows a window like the one in Figure 3. Like all windows, Windows Presentation Foundation windows contain a client area (this houses Windows Presentation Foundation content and controls) and a non-client area (a border, a title bar, and the various adornments associated with these).
Figure 3** A Window and Its Parts **
The window that results from Application.StartupUri is modeless, which means it doesn't prevent users from using other windows in the application. Since you haven't created any other windows yet, this isn't so exciting. However, if you need to show other modeless windows, as you'll probably need to do in any nontrivial application, simply call Window.Show:
// MainWindow.xaml.cs (codebehind) public partial class MainWindow : Window { void helpContentsMenuItem_Click(object sender, RoutedEventArgs e) { HelpWindow window = new HelpWindow(); window.Owner = this; // Ensure window always appears above us window.Show(); } ... }
Windows Presentation Foundation also supports showing a window modally, which means the window prevents other windows in an application from being used. Modal windows are typically (but not always) used as dialogs, to collect data needed for completing a task such as creating a new order. To show a window modally in Windows Presentation Foundation, call Window.ShowDialog (see Figure 4).
Figure 4 Calling Window.ShowDialog
// MainWindow.xaml.cs (codebehind) public partial class MainWindow : Window { void CreateOrder() { // Place order OrderABoxDialog dlg = new OrderABoxDialog(); dlg.Owner = this; // Ensure dialog box always appears above us bool? dialogResult = dlg.ShowDialog(); // If order details are fine, add order to orders list if (dialogResult == true) { this.orders.Add(dlg.Order); } } ... }
The Window class also supports typical dialog behavior, which allows users to accept or cancel a dialog and have that choice returned to the calling code for appropriate processing.
Message boxes are a special type of dialog for displaying information to users, or asking them questions, and is supported in Windows Presentation Foundation with the MessageBox type:
// MainWindow.xaml.cs (codebehind) public partial class MainWindow : Window { void aboutMenuItem_Click(object sender, RoutedEventArgs e) { MessageBox.Show("Box Application, Version 1.0"); } ... }
Message boxes, dialogs, windows, and applications form the core of the standalone, menu-driven application development model. And these have been supported by previous presentation technologies for a long time now. However, Windows Presentation Foundation extends these with hyperlink-driven navigation support that begins with the fundamental unit of navigation content—the page.
The Page Class
Page is the Windows Presentation Foundation analog of the HTML Web page, which helped popularize the Web. As I mentioned, Windows Presentation Foundation supports hyperlink-driven navigation in both standalone and browser applications. The content cornerstone of the hyperlink-driven navigation experience in Windows Presentation Foundation is the Page.
You add a markup and codebehind Page definition to your project in Visual Studio 2005 by choosing Project | Add New File | WinFX Page. This generates code resembling what's shown in Figure 5.
Figure 5 Adding Markup and Codebehind Page Definition
<!--HomePage.xaml (markup)--> <Page xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" x:Class="BoxApplicationNavigationWindow.HomePage" ... > ... <!--Order Content--> ... </Page> // HomePage.xaml.cs (codebehind) using System.Windows.Controls; // Page public partial class HomePage : Page { ... }
The Page markup file is configured as a Page build item. As with Window, this is done so it can be loaded from a URI, which means you can configure Application.StartupUri to automatically load a page when an application starts:
<!--App.xaml (markup)--> <Application ... StartupUri="HomePage.xaml" />
Because the Page class is not a window, and doesn't derive from Window, it can't host itself. Fortunately, the Application class is smart enough to detect when a particular page is set as the StartupUri. If so, Application creates a window in which to host the page.
Hyperlink Class
Any nontrivial hyperlink-driven application will have more than one XAML page, and you'll need to give your users a way to navigate between these pages. You can probably guess that Windows Presentation Foundation enables hyperlink-driven navigation with hyperlinks. You can add hyperlinks to your pages like so:
<!--HomePage.xaml (markup)--> <Page ... > ... <Hyperlink NavigateUri= "OrderingGuidelinesPage.xaml"> Ordering Guidelines </Hyperlink> ... </Page>
This hyperlink is configured to navigate to a XAML page using the same fundamental programming model as HTML HREFs. You specify a URI to navigate to (in this case, OrderingGuidelinesPage.xaml) and the text that users will see and click on to initiate navigation (in this example, "Ordering Guidelines").
Since so much browsable content is located in HTML-based Web pages, it's nice that Windows Presentation Foundation and Hyperlink let you seamlessly navigate to Web-based content. For example, the ordering guidelines already exist on the Box Application's Web site so, rather than replicating them as XAML pages within the application, you can simply change the value of the NavigateUri property:
<!--HomePage.xaml (markup)--> <Page ... > ... <Hyperlink NavigateUri="OrderingGuidelinesPage.html"> Ordering Guidelines </Hyperlink> ... </Page>
NavigationWindow
At this point, you might be wondering one of several things. Since Page is not a window, where did the window that's hosting it come from? When a hyperlink is clicked, what actually handles the navigation? And how is HTML Web page content displayed from a Windows Presentation Foundation application? All of these things are taken care of by NavigationWindow.
When you set Application.StartupUri to either a XAML or HTML page, Application (knowing that neither of these can provide its own window) creates an instance of NavigationWindow to host them. NavigationWindow derives from Window and extends its visual appearance to look a little like a browser, as you can see in Figure 6.
Figure 6** Created By NavigationWindow **
When a user clicks a hyperlink that appears on a XAML page, Hyperlink asks NavigationWindow to navigate to the specified URI. NavigationWindow proceeds to load the page that is located at the URI, thus hosting it. The URI location from which the page was loaded is stored in the NavigationWindow.Source property, while the loaded page content is available from the NavigationWindow.Content property.
When content changes, navigation is considered to have taken place and the previous content is added to the navigation history. This, too, is managed by NavigationWindow. The navigation UI provides two buttons and dropdown lists for navigation. Note that you are not limited to NavigationWindow's default chrome. Using Windows Presentation Foundation's support for styles, you can easily create your own navigation UI.
So far, I've shown you how to use markup for configuring the URIs that hyperlinks should navigate to. Sometimes, though, you can't determine your navigation declaratively. For example, if you want to view an order, you need to create an instance of Page and pass it the order you want to view. This can't be done declaratively. Instead, you need to use code, as shown in Figure 7.
Figure 7 Using Code for Navigation
HomePage.xaml (markup) <Page ... > ... <Hyperlink Click="viewHyperlink_Click"> View </Hyperlink> ... </Page> HomePage.xaml.cs (codebehind) public partial class HomePage : Page { void viewHyperlink_Click(object sender, RoutedEventArgs e) { // View Order ViewOrderPage page = new ViewOrderPage(GetSelectedOrder()); NavigationWindow window = (NavigationWindow)this.Parent; // Don’t do this! window.location.href=page; } Order GetSelectedOrder() { return (Order)this.ordersListBox.SelectedItem; } ... }
When the hyperlink is clicked, its Click event handler gets the order that is currently selected, passes it to ViewOrderPage during instantiation, and calls its host NavigationWindow's Navigate method, which then navigates to the page as an object, rather than as a URI.
You may find it odd to have to acquire a reference to the host NavigationWindow. This is necessary because Page does not have explicit knowledge about what is actually hosting it. Page can determine the host using its Parent property, but Parent returns a reference to a DependencyObject, rather than a strongly typed reference to a particular host type. Thus, casting Parent to a specific type implies that Page knows what can host it. But, as you'll discover, Page can have different types of hosts. Consequently, if you intend for your pages to be hosted by multiple types of hosts, you need a host-independent way of programmatically performing navigation.
NavigationService
In Windows Presentation Foundation, independence between a page and a page's host is established by NavigationService, which implements the basic machinery of a navigation engine including navigation, navigation history, navigation lifetime, content, and finding a navigation service for a piece of content. Figure 8 shows the essential members of the NavigationService type.NavigationWindow doesn't actually implement its own navigation engine; it uses its own instance of NavigationService. With that in mind, Page can actually get a reference to the NavigationService of the NavigationWindow that hosts it using GetNavigationService:
// HomePage.xaml.cs (codebehind) public partial class HomePage : Page { void viewHyperlink_Click(object sender, RoutedEventArgs e) { // View Order ViewOrderPage page = new ViewOrderPage(GetSelectedOrder()); NavigationService ns = NavigationService.GetNavigationService(this); ns.Navigate(page); } Order GetSelectedOrder() { ... } ... }
This lets Page do navigation without any specific knowledge of its host. This requirement is so common that Page provides a special helper property, NavigationService, which has the same effect:
// HomePage.xaml.cs (code-behind) public partial class HomePage : Page { void viewHyperlink_Click(object sender, RoutedEventArgs e) { // View Order ViewOrderPage page = new ViewOrderPage(GetSelectedOrder()); this.NavigationService.Navigate(page); } Order GetSelectedOrder () { ... } ... }
Figure 8 Essential Members of the NavigationService Type
sealed class NavigationService : IContentContainer { // Navigation public bool Navigate(Uri source); // Navigate to URI public void Refresh(); // Re-navigate to current content public void StopLoading(); // Stop current navigation // Navigation History public bool CanGoBack { get; } // Content in back nav. history? public bool CanGoForward { get; } // Content in forward nav. history? public void GoBack(); // Go to previous content in nav. history public void GoForward(); // Go to next content in nav. history // Navigation Lifetime // Navigation requested public event NavigatingCancelEventHandler Navigating; // Navigated to content public event NavigatedEventHandler Navigated; // Content loaded public event LoadCompletedEventHandler LoadCompleted; // Navigation error public event NavigationFailedEventHandler NavigationFailed; // Bytes downloaded public event NavigationProgressEventHandler NavigationProgress; // Navigation stopped public event NavigationStoppedEventHandler NavigationStopped; // Content public object Content { get; set; } // The currently loaded content public Uri CurrentSource { get; } // The URI for the current content public Uri Source { get; set; } // The URI for the current content // or, if navigating, the URI for the // content being navigated to // Find a navigation service public static NavigationService GetNavigationService( DependencyObject dependencyObject); }
Figure 9 illustrates the relationship between NavigationWindow, NavigationService, and Page. As you can see, NavigationWindow re-implements its NavigationService's Content property. NavigationWindow re-implements the majority of NavigationService's members in this way, and even adds some more. For example, you can enumerate the content in back and forward navigation history via the BackStack and ForwardStack properties, respectively.
Figure 9** The Relationship **
Unfortunately, you can't create your own custom type that aggregates NavigationService (even though it is a public type, it has an internal constructor that consequently prevents instantiation). Instead, you have to rely on one of three Windows Presentation Foundation NavigationService aggregators to host content. Collectively, these are known as navigators and include NavigationWindow, Frame, and browsers (only Internet Explorer® versions 6 and 7 for Windows Presentation Foundation version 1.0). When a page is coded to use its own NavigationService property, it can be hosted in all three without change, as Figure 10 illustrates.
Figure 10** Using Code for Navigation **
Perhaps most exciting is that you can take a page hosted within a single standalone application and suddenly you're able to host it anywhere using Internet Explorer.
XAML Browser Applications
NavigationWindow, Page, and Hyperlink provide a nice way to offer browser-style user experiences in a standalone application. In one sense, NavigationWindow is a browser, though without all the frills built into today's browsers like favorites, tabbed browsing, and the like. Since most users are browser savvy, many will feel more comfortable with an application that provides the same level of capability or, indeed, integrates with browsers. If your application will benefit from a browser-hosted and hyperlink-driven environment, Windows Presentation Foundation XAML Browser Applications (XBAPs) are the way to go.
To create an XBAP version of the sample application, create a new .NET Framework 3.0 Web Browser Application in Visual Studio 2005, copy the files from the NavigationWindow version of the sample (excluding the application definition), and voilà. The resulting application will run hosted by Internet Explorer, as shown in Figure 11.
Figure 11** App Hosted by Internet Explorer **
XBAPs can also be published to and launched from Web servers, on either the intranet or the Internet. This is made possible through ClickOnce Deployment, which is included with the .NET Framework 3.0. To use ClickOnce, MSBuild generates the executable that users ultimately run, as well as two manifest files that ClickOnce requires for downloading and launching the executable. One of these is known as the application manifest; it has the .xbap file extension and is what users actually browse to when they want to launch an XBAP. Note that the launch process is seamless to the user—the experience of browsing to an XBAP application is like browsing to any application running in a browser.
When an application can be launched from the Internet, security is a significant factor. For this reason, XBAPs are not installed. Furthermore, XBAPs leverage the .NET Framework's Code Access Security (CAS) to protect users from malicious code through the use of an enforced security sandbox—XBAPs can only do things that are allowed for apps launched from the Internet zone, a restricted set of operations. Furthermore, if an XBAP attempts to execute functionality that exceeds what is enabled by the Internet zone, an exception is thrown and the application stops executing.
Internet zone permissions prevent a variety of capabilities in Windows Presentation Foundation version 1.0, including showing windows, using the SaveFileDialog, registry access, and accessing the HTML Document Object Model (DOM) via script. While you need to sacrifice features like these to enjoy the benefits of CAS-secured XBAP applications, you still have most of what is really cool about Windows Presentation Foundation at your command.
Frame
Frame embeds browser-style user experiences into content that may be hosted by other navigators (standalone or browser-based, menu- or hyperlink-driven). Given its flexibility, you should be guided by your intended user experience when determining how to use it.
Standalone, menu-driven applications don't provide the best model for navigating through document-style content, such as a help file. A hyperlink-driven approach may be more suitable and links can be easily embedded into a standalone application's window, as shown in Figure 12. This is enabled with the following markup:
<!--HelpDialog.xaml (markup)--> <Window ... > <DockPanel> <TextBlock Padding="20,20,20,20" DockPanel.Dock="Top" TextWrapping="Wrap" FontSize="15" FontWeight="Bold" > Box Application Help </TextBlock> <Frame Padding="20,0,20,0" Source="HelpPageContents.xaml" /> </DockPanel> </Window>
Alternatively, Frame can be used much like the HTML IFRAME element by being hosted within the content of a Windows Presentation Foundation page, as shown in Figure 13. The markup will look like this:
<!--HelpPage.xaml (markup)--> <Page ... > <DockPanel> <TextBlock Padding="20,20,20,20" DockPanel.Dock="Top" TextWrapping="Wrap" FontSize="15" FontWeight="Bold" > Box Application Help </TextBlock> <Frame ... Source="HelpPageContents.xaml" /> </DockPanel> </Page>
Figure 12** Browsable Content in a Standalone Window **(Click the image for a larger view)
By default, when a frame is hosted within content that is itself directly or indirectly hosted by another navigator, the frame uses the navigation service that is managed by the parent navigator. This means that the frame's navigation history is really stored in its parent navigator's navigation history, along with the parent navigator's own navigation history. Thus, the frame's navigation history must be navigated all the way back or forward before the parent navigator's navigation history can be navigated. However, this is not a bad thing if your parent navigator hosts content that can be shared across the one or more pages. This is somewhat analogous to ASP.NET's master/child content.
Figure 13** Frame Hosted within Content **(Click the image for a larger view)
On the other hand, such navigation can be painful for users when the pages in a frame make up a single logical set of content—for instance, a help file rather than one or more help pages. Once a user goes to Help and navigates to the appropriate help page, he is unlikely to want to navigate back through all the help pages he browsed; instead, he'll probably want to go straight back to the previous parent page. In this situation, you can instruct a frame to use its own navigation history by setting its JournalOwnership property like so:
<Page ... > ... <Frame ... JournalOwnership="OwnsJournal" /> ... </Page>
JournalOwnership is a property you set to determine which "journal" a frame will use (the internal Journal type encapsulates navigation history in Windows Presentation Foundation), and can be one of the JournalOwnership enumeration values:
enum JournalOwnership { Automatic, // "UsesParentJournal" if hosted by Frame or // NavigationWindow, "OwnsJournal" otherwise. // (default) OwnsJournal, // Frame manages navigation history UsesParentJournal // Frame's host manages navigation history }
Figure 14** Frame with Its Own Navigation History **(Click the image for a larger view)
Windows Presentation Foundation Resources
So far, I've discussed pages that are embedded into an application assembly. However, content can be loaded from a variety of locations—it can be embedded in the same assembly as the code that uses it, it can be embedded in a referenced assembly, or it can consist of loose content files that are not embedded into any assembly at all. Loose content itself can be located on the local disk, a file share, or even a Web site. And, whether a piece of content is embedded or loose, it doesn't have to be a page; content can include a variety of media, such as images, video, and audio. Finally, content doesn't even have to belong to a particular application. HTML pages that belong to other Web applications are also fair game.
This flexibility allows developers to more easily deal with a variety of real-world scenarios. Sometimes content is specific enough to an application, and an application is so dependent on this content, that it makes the most sense to deploy the content with the application by embedding it right in the assembly. Some applications have content that changes regularly enough that constantly rebuilding assemblies to redeploy new content is impractical, hence the support for loose content (and since loose content can reside in a common location, Internet- and intranet-based XBAP applications can avoid downloading assemblies that aren't needed). Furthermore, sometimes content is shared by more than one application, but there still needs to be a guarantee that it is available.
To enable this flexibility, Windows Presentation Foundation incorporates a special mechanism for uniquely identifying and loading resources. It doesn't matter where content resides or whether the content is embedded or loose. The foundation of this mechanism is the Pack URI scheme, which is an extensible scheme for identifying application resources with unique URIs. Windows Presentation Foundation uses the Pack URI scheme to support several different, but common, scenarios for loading content.
Throughout this article, the sample code has used Pack URIs to identify and load windows and pages, whenever Application.StartupUri and Hyperlink.NavigateUri have been used:
<!--App.xaml (markup)--> <Application ... StartupUri="HomePage.xaml" />
This example uses the relative version of the Pack URI, which is a nice simplification that allows you to type less. The absolute version of this Pack URI is shown here:
pack://application:,,,/HomePage.xaml
The absolute Pack URI is composed of three key parts: the scheme (pack://), the authority (application:), and the path (,/HomePage.xaml). The authority describes the type of container that has a resource, while the path describes the location of that resource relative to the container. An "application:" container is really an assembly, and the path is the location of a resource relative to the root of the assembly.
Whether an absolute or relative Pack URI is used, the content that it refers to can either be embedded within an assembly or exist as a loose XAML file that is stored in a location relative to the application executable. For a loose XAML page that is in the same folder as the application executable, the Pack URI looks like this:
pack://application:,,,/HomePage.xaml
Interestingly, the Pack URI for the loose XAML file is the same as the Pack URI for an embedded file of the same name. To distinguish between the two, Windows Presentation Foundation uses a basic resolution mechanism that first looks in the assembly for an embedded resource before looking for a loose resource.
A Pack URI can also be used to access Windows Presentation Foundation resources that are embedded in referenced assemblies, although with a slightly different syntax:
pack://application:,,,/BoxApplicationLibrary;component/HomePage.xaml
The relative Pack URI equivalent is:
/BoxApplicationLibrary;component/HomePage.xaml
The Pack URI allows you to both locate and load a resource from the application's site of origin, that is, the location from which it is launched. For XBAP applications launched from a Web server, keeping content current is as simple as dropping new content onto the application's publish location. To access loose resources located at the site of origin, you need to use another special type of Pack URI that can only be used absolutely:
pack://siteoforigin:,,,/HomePage.xaml
For any page (embedded or loose), you can enable navigation to a fragment on the page. This is just like Web-style fragment navigation. You define a fragment on a page by giving a XAML element a Name attribute, like so:
<Page ... > <TextBlock Name="Paragraph1" TextWrapping="Wrap"> ... </TextBlock> </Page>
To navigate to a page fragment, you use yet another special Pack URI, by suffixing the page URI with "#XAMLElementName", like this:
HelpPage3.xaml#Paragraph3
PageFunction
With content coming from a variety of locations, and hyperlink-driven applications allowing navigation to any number of other locations, completing a task can be difficult. This is because hyperlink-driven applications can't easily constrain users to navigating to specific pages. No matter how many hyperlinks an application provides, users can still go anywhere they want using the browser's address bar. As a result, a user can leave the page that initiated processing of a task without knowing whether or not the task was completed. In the Web world, there are plenty of tricks to create pseudo-dialog style behaviors for Web pages, relying on session state and the like. Unfortunately, this requires a lot of overhead. Dialogs do solve the problem, but for security reasons Window cannot be instantiated from applications, such as XBAPs, that run within Internet Zone partial trust.
Fortunately, Windows Presentation Foundation supports modal dialog-style mechanics with page functions. These are encapsulated as the generic PageFunction type, which derives indirectly from Page. Consequently, a PageFunction looks much like a Page, and is also built similarly, as you can see in Figure 15.
Figure 15 PageFunction
<!--OrderABoxPageFunction.xaml (markup) --> <PageFunction xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:BoxApplicationXBAP" x:Class="BoxApplicationXBAP.OrderABoxPageFunction" x:TypeArguments="local:Order" WindowTitle="Box Application - Order a Box" > ... <!--Content--> ... <PageFunction> // OrderABoxPageFunction.cs (code-behind) public partial class OrderABoxPageFunction: PageFunction<Order> { ... }
The purpose of this particular page function is to collect new order information, which is encapsulated by Order. Because tasks typically operate over data in this way, PageFunction is generic and is declared to operate over a specific piece of data, hence the special x:TypeArguments attribute for markup. If the value of x:TypeArguments and the generic PageFunction's type argument don't match, you will get a compilation error.
To call a PageFunction, the calling page needs to instantiate a PageFunction and manually navigate to it:
// HomePage.cs (codebehind) public partial class HomePage : Page { void orderHyperlink_Click(object sender, RoutedEventArgs e) { OrderABoxPageFunction pageFunction = new OrderABoxPageFunction(); pageFunction.Return += new ReturnEventHandler<Order>( OrderABoxPageFunction_Returned); this.NavigationService.Navigate(pageFunction); } ... }
Next, the PageFunction needs to allow a user to complete the page before returning the result to the calling page:
// OrderABoxPageFunction.cs (codebehind) public partial class OrderABoxPageFunction: PageFunction<Order> { void orderHyperlink_Click(object sender, RoutedEventArgs e) { // Return order this.OnReturn(new ReturnEventArgs<Order>(this.order)); } void cancelHyperlink_Click(object sender, RoutedEventArgs e) { // Cancel order this.OnReturn(null); } ... }
PageFunction.OnReturn is called to return, passing an instance of the generic ReturnEventArgs. If the task is accepted, this contains an instance of the type over which the PageFunction operates. Otherwise, it's null. To detect PageFunction return, and to acquire the ReturnEventArgs and its data, the calling page needs to handle PageFunction.Returned, as shown in Figure 16. The returned data is available from the Result property of the Returned event handler's ReturnEventArgs.
Figure 16 PageFunction.Returned
// HomePage.cs (code-behind) public partial class HomePage : Page { // Launch the page function void orderHyperlink_Click(object sender, RoutedEventArgs e) { OrderABoxPageFunction pageFunction = new OrderABoxPageFunction(); pageFunction.Return += new ReturnEventHandler<Order>( OrderABoxPageFunction_Returned); this.NavigationService.Navigate(pageFunction); } // Handle page function return void OrderABoxPageFunction_Returned( object sender, ReturnEventArgs<Order> e) { if (e != null) this.orders.Add(e.Result); } ... }
You'll likely want to ensure a PageFunction is removed from navigation history after the task completes. This requires only a simple configuration:
<!--OrderABoxPageFunction.xaml (markup) --> <PageFunction RemoveFromJournal="True" ... > ... <!--Content--> ... <PageFunction>
By removing the page function from navigation history, you prevent users from navigating back to the page function. This is important because users may otherwise be able to edit data that has changed since the last time a page function was accessed, creating the potential for inconsistencies in the data.
Note that a task can be composed from more than one page of content, which is common for wizard-style user experiences. PageFunction is equally capable in this situation, since it is possible to use the same techniques for operating over one page function as for operating over multiple page functions.
Where Are We?
The Windows Presentation Foundation application model is extremely flexible. At its most basic, it enables both standalone and browser-hosted applications—both of which support menu-driven and hyperlink-driven navigation. Furthermore, application content can be packaged in either the application's assembly, in a referenced assembly, or as loose files located in a number of places. All in all, the type of user experience you create within the Windows Presentation Foundation application model is limited only by what you choose to do.
For more information, see the Windows Presentation Foundation SDK.
Michael Weinhardt is a programmer/writer at Microsoft, working on the Windows Client SDK. Michael has coauthored a variety of articles, contributed to the "Wonders of Windows Forms" column at MSDN Online, reviewed several Windows technology books, and coauthored Windows Forms 2.0 Programming (Addison-Wesley Professional, 2006).