Creating and navigating between pages in a Windows Store business app using C#, XAML, and Prism
[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]
From: Developing a Windows Store business app using C#, XAML, and Prism for the Windows Runtime
Learn how to implement accessible pages that support a fluid layout, are localizable, include design-time data, and that can be easily navigated between, using Prism for the Windows Runtime.
Download
After you download the code, see Getting started using Prism for the Windows Runtime for instructions on how to compile and run the reference implementation, as well as understand the Microsoft Visual Studio solution structure.
You will learn
- How pages were designed in AdventureWorks Shopper.
- How AdventureWorks Shopper creates pages.
- How to create design time data to support designers.
- How AdventureWorks Shopper pages support different layouts.
- How AdventureWorks Shopper pages support localization and accessibility.
- How AdventureWorks Shopper performs navigation between pages.
Applies to
- Windows Runtime for Windows 8.1
- C#
- Extensible Application Markup Language (XAML)
Making key decisions
The app page is the focal point for designing your UI. It holds all of your content and controls for a single point of interaction with the user within your app. Whenever possible, you should integrate your UI elements inline into the app page. Presenting your UI inline lets users fully immerse themselves in your app and stay in context, as opposed to using pop-ups, dialogs, or overlapping windows that were common in previous Windows desktop application platforms. You can create as many app pages as you need to support your user scenarios. The following list summarizes the decisions to make when creating pages in your app:
- What tool should I use to create page content?
- What minimum resolution should I design my pages for?
- Should my page content fill the screen, regardless of resolution?
- Should my pages adapt to different orientations and layouts?
- How should I lay out UI elements on each page?
- What should I display in minimal view?
- How should I test my page layout on different screen sizes?
- Should I add design time data to my pages?
- Should I make my pages easily localizable?
- Should I make my pages accessible?
- Should I cache pages in my app?
- Where should navigation logic reside?
- How should I invoke navigation from a view?
- What commands belong on the navigation bar and the bottom app bar?
- Should common page navigation functionality be implemented on each page, or can it be encapsulated into a single control for reuse on each page?
- Should the page being navigated to reside in the same assembly that the navigation request originates from?
- How should I specify a navigation target?
We recommend that you use Visual Studio to work with the code-focused aspects of your app. Visual Studio is best suited for writing code, running, and debugging your app. We recommend that you use Blend for Microsoft Visual Studio 2013 to work on the visual appearance of your app. You can use Blend to create pages and custom controls, change templates and styles, and create animations. Blend comes with minimal code-behind support. For more info about XAML editing tools, see Design Windows Store apps using Blend and Creating a UI by using the XAML Designer.
There are two primary screen resolutions that your app should support. The minimum resolution at which Windows Store apps will run is 1024x768. However, the minimum optimal resolution required is 1366x768. When designing pages for a minimum resolution of 1024x768 you should ensure that all of your UI fits on the screen without clipping. When designing pages for an optimal resolution of 1366x768 you should ensure that all of your UI fits on the screen without blank regions. Page content should fill the screen to the best of its ability and should appear to be thoughtfully designed for varying screen sizes. Users who buy larger monitors expect that their apps will continue to look good on these large screens and fill the screen with more content, where possible. For more info see Guidelines for window sizes and scaling to screens.
Users can rotate and flip their tablets, slates, and monitors, so you should ensure that you app can handle both landscape and portrait orientations. In addition, because users can work with up to two apps at once, you should provide a minimal layout. The default minimum width of an app is 500 pixels. If you keep this width you do not have to make any special considerations for your app at narrow widths. You simply design your app so that it adapts fluidly when the user resizes it. You can choose to change the minimum width to 320 pixels. If you choose to do this you should make some design changes so that the app is still functional and usable at this narrow width. For more info see Guidelines for layouts, Responsive design 101 for Universal Windows Platform (UWP) apps, and Guidelines for resizing windows to tall and narrow layouts.
The user interface in Microsoft Windows strives to maintain a consistent silhouette across its apps. The signature characteristic of the silhouette is a wide margin on the top, bottom, and left edges. This wide margin helps users understand the horizontal panning direction of the content. You should follow a consistent layout pattern for margins, page headers, gutter widths, and other such elements on your pages. For more info see Laying out an app page.
When you plan for full screen and minimal views, your app's UI should reflow smoothly and gracefully to accommodate screen size, orientation, and user interactions. You should maintain state in minimal view, even if it means showing less content or reducing functionality. In addition, you should have feature parity across states. The user still expects to be able to interact with your app when it is in minimal view. For more info see Guidelines for resizing windows to tall and narrow layouts.
Most people don't have many devices at their disposal for testing page layout on different screen sizes. However, you can use the Windows Simulator to run your app on a variety of screen sizes, orientations, and pixel densities. In addition, Blend offers a platform menu that enables you to design your app on different screen sizes and pixel densities on the fly. The Blend canvas then updates dynamically based upon the chosen screen option.
Sample data should be added to each page if you want to easily view styling results and layout sizes at design time. This has the additional advantage of supporting the designer-developer workflow.
Preparing your pages for localization can help your app reach more users in international markets. It's important to consider localization early on in the development process, as there are some issues that will affect UI elements across various locales. As you design your pages, keep in mind that users have a wide range of abilities, disabilities, and preferences. If you incorporate accessible design principles into your pages you will help to ensure that your app is accessible to the widest possible audience, thus attracting more customers to your app. For more info see Globalizing your app and Design for accessibility.
Deciding whether to cache pages will be dependent upon how well-performing and responsive the app is. Page caching results in memory consumption for views that are not currently displayed, which would increase the chance of termination when the app is suspended. However, without page caching it does mean that XAML parsing and construction of the page and its view model will occur every time you navigate to a new page, which could have a performance impact for a complicated page. For a well-designed page that does not use too many controls, the performance should be sufficient. However, if you encounter slow page load times you should test to see if enabling page caching alleviates the problem. For more info see Quickstart: Navigating between pages.
Navigation within a Windows Store app can result from the user's interaction with the UI or from the app itself as a result of internal logic-driven state changes. Page navigation requests are usually triggered from a view, with the navigation logic either being in the view's code-behind, or in the data bound view model. While placing navigation logic in the view may be the simplest approach, it is not easily testable through automated tests. Placing navigation logic in the view model classes means that the navigation logic can be exercised through automated tests. In addition, the view model can then implement logic to control navigation to ensure that certain business rules are enforced. For instance, an app may not allow the user to navigate away from a page without first ensuring that the entered data is correct.
Users will trigger navigation from a view by selecting a UI control, with the navigation logic residing in the appropriate view model class. For controls derived from ButtonBase, such as Button, you should use commands to implement a navigation action in the view model class. For controls that do not derive from ButtonBase, you should use a Blend behavior to implement a navigation action. For more info see Using the Model-View-ViewModel (MVVM) pattern.
In general, you should use the navigation bar for navigational elements that move the user to a different page and use the bottom app bar for commands that act on the current page. If every page of your app is going to include a navigation bar that allows the user to move to different pages, it does not make sense to implement this functionality individually on each page. Rather, the functionality should be implemented as a user control that can be easily be included on each page. In addition, you should follow placement conventions for commands on the bottom app bar. You should place New/Add/Create buttons on the far right, with view switching buttons being placed on the far left. Also, you should place Accept, Yes, and OK buttons to the left of Reject, No, and Cancel buttons. For more info see Guidelines for app bars.
The view classes that define your pages and the view model classes that implement the business logic for those pages can reside in the same assembly or different assemblies. That is a design decision to be made when architecting your app. A page type resolution strategy should be used to navigate to a page in any assembly, regardless of the assembly from which the navigation request originates.
One approach for specifying a navigation target is to use a navigation service, which would require the type of the view to navigate to. Because a navigation service is usually invoked from view models in order to promote testability, this approach would require view models to reference views (and particularly views that the view model isn't associated with), which is not recommended. The recommended approach is to use a string to specify the navigation target that can be easily passed to a navigation service, and which is easily testable.
[Top]
Creating pages and navigating between them in AdventureWorks Shopper
We used Blend and the Visual Studio XAML Designer to work with XAML because these tools make it straightforward to quickly add and modify page layout. Blend was useful to initially define pages and controls; we used Visual Studio to optimize their appearances. These tools also enabled us to iterate quickly through design choices because they give immediate visual feedback. In many cases, our user experience designer was able to work in parallel with the developers because changing the visual appearance of a page does not affect its behavior. For more info see Creating pages.
Pages were designed for a minimum resolution of 1024x768, and an optimal minimum resolution of 1366x768. In addition, pages were designed to fill the screen for varying screen sizes. Each page is able to adapt to landscape and portrait orientations, and a minimal layout. A consistent silhouette is maintained across all pages, with some pages including design time data. Page layout was tested on a variety of devices, and in the Windows simulator. Pages maintain state when switching between different view states, and possess feature parity across states. For more info see Adding design time data, Supporting multiple view states and Laying out an app page.
Page caching is not used in the app. This prevents views that are not currently displayed from consuming memory, which would increase the chance of termination when the app is suspended. All pages are accessible, and support easy localization. For more info see Enabling page localization and Enabling page accessibility.
In the app, the view classes that define pages are in a different assembly to the view model classes that implement the business logic for those pages. Therefore, a page type resolution strategy implemented as a delegate is used to navigate to the pages in the AdventureWorks.Shopper assembly when the navigation request originates from view model classes in the AdventureWorks.UILogic assembly. In addition, common page navigation functionality is implemented as a user control that is embedded in the navigation bar for each page. Both commands and Blend behaviors are used to implement navigation actions, depending on the control type. Navigation targets are specified by strings that represent the page to navigate to. For more info see Navigating between pages, Handling navigation requests, and Invoking navigation using behaviors.
[Top]
Creating pages
Pages in Windows Store apps are user controls that support navigation and contain other controls. All page classes are subtypes of the Windows.UI.Xaml.Page class, and represent content that can be navigated to by the user.
In apps that use Prism for the Windows Runtime, each page should derive from the VisualStateAwarePage class in the Microsoft.Practices.Prism.StoreApps library. The VisualStateAwarePage class provides view management and navigation support. The following code example shows how the HubPage derives from the VisualStateAwarePage class.
AdventureWorks.Shopper\Views\HubPage.xaml
<prism:VisualStateAwarePage xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:awbehaviors="using:AdventureWorks.Shopper.Behaviors"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:views="using:AdventureWorks.Shopper.Views"
xmlns:awcontrols="using:AdventureWorks.Shopper.Controls"
xmlns:designViewModels="using:AdventureWorks.Shopper.DesignViewModels"
xmlns:prism="using:Microsoft.Practices.Prism.StoreApps"
x:Name="pageRoot"
x:Class="AdventureWorks.Shopper.Views.HubPage"
IsTabStop="false"
x:Uid="Page"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="true"
d:DataContext="{d:DesignInstance designViewModels:HubPageDesignViewModel, IsDesignTimeCreatable=True}">
Note All Flyout classes derive from the SettingsFlyout class.
There are twelve pages in the AdventureWorks Shopper reference implementation, with the pages being the views of the MVVM pattern.
Page | View model |
BillingAddressPage | BillingAddressPageViewModel |
CategoryPage | CategoryPageViewModel |
CheckoutHubPage | CheckoutHubPageViewModel |
CheckoutSummaryPage | CheckoutSummaryPageViewModel |
GroupDetailPage | GroupDetailPageViewModel |
HubPage | HubPageViewModel |
ItemDetailPage | ItemDetailPageViewModel |
OrderConfirmationPage | OrderConfirmationPageViewModel |
PaymentMethodPage | PaymentMethodPageViewModel |
SearchResultsPage | SearchResultsPageViewModel |
ShippingAddressPage | ShippingAddressPageViewModel |
ShoppingCartPage | ShoppingCartPageViewModel |
Data binding links each page to its view model class in the AdventureWorks Shopper reference implementation. The view model class gives the page access to the underlying app logic by using the conventions of the MVVM pattern. For more info see Using the MVVM pattern.
Tip AdventureWorks Shopper uses the MVVM pattern that abstracts the user interface for the app. With MVVM you rarely need to customize the code-behind files. Instead, the controls of the user interface are bound to properties of a view model object. If page-related code is required, it should be limited to conveying data to and from the page's view model object.
If you are interested in AdventureWorks Shopper's interaction model and how we designed the user experience, see Designing the user experience of a Windows Store business app.
[Top]
Adding design time data
When you create a data bound user interface, you can display sample data in the visual designer to view styling results and layout sizes. To display data in the designer you must declare it in XAML. This is necessary because the designer parses the XAML for a page but does not run its code-behind. In the AdventureWorks Shopper reference implementation, we wanted to display design time data in order to support the designer-developer workflow.
Sample data can be displayed at design time by declaring it in XAML by using the various data attributes from the designer namespace. This namespace is typically declared with a d: prefix, as shown in the following code example.
AdventureWorks.Shopper\Views\HubPage.xaml
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
Attributes with d: prefixes are then interpreted only at design time and are ignored at run time.
AdventureWorks.Shopper\Views\HubPage.xaml
d:DataContext="{d:DesignInstance designViewModels:HubPageDesignViewModel, IsDesignTimeCreatable=True}"
The d:DesignInstance attribute indicates that the design time source is a designer created instance based on the HubPageDesignViewModel type. The IsDesignTimeCreateable setting indicates that the designer will instantiate that type directly, which is necessary to display the sample data generated by the type constructor.
For more info see Data binding overview.
[Top]
Supporting multiple view states
The AdventureWorks Shopper reference implementation was designed to be viewed full-screen in landscape orientation. Windows Store apps must adapt to different application view states, including both landscape and portrait orientations. AdventureWorks Shopper supports DefaultLayout (landscape full screen), PortraitLayout, and MinimalLayout view states. AdventureWorks Shopper uses the VisualState class to specify changes to the visual display to support each layout. The VisualStateManager class, used by the VisualStateAwarePage class, manages state and the logic for transitioning between states for controls. For example, here is the XAML specification of the layout changes for the PortraitLayout view state on the hub page.
AdventureWorks.Shopper\Views\HubPage.xaml
<VisualState x:Name="PortraitLayout">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemsGridView"
Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0"
Value="40,0,0,30" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="semanticZoom"
Storyboard.TargetProperty="CanChangeViews">
<DiscreteObjectKeyFrame KeyTime="0"
Value="false" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="semanticZoom"
Storyboard.TargetProperty="IsZoomOutButtonEnabled">
<DiscreteObjectKeyFrame KeyTime="0"
Value="false" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Grid.ColumnDefinitions[0].Width"
Storyboard.TargetName="titleGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="40" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(views:SearchUserControl.IsCompact)"
Storyboard.TargetName="searchUserControl">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<x:Boolean>True</x:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
We directly update individual properties for XAML elements, in order to specify changes to the visual display. For instance, here the Storyboard specifies that the Padding property of the GridView control named itemsGridView will change to a value of "40,0,0,30" when the view state changes to portrait. However, you could update the Style property when you need to update multiple properties or when there is a defined style that does what you want. Although styles enable you to control multiple properties and also provide a consistent appearance throughout your app, providing too many can make your app difficult to maintain. Therefore, only use styles when it makes sense to do so. For more info about styling controls, see Quickstart: styling controls.
Tip When you develop an app in Visual Studio, you can use the Windows Simulator debugger to test layouts. To do this, press F5 and use the debugger tool bar to debug with the Windows Simulator. You can also use Blend to define and test layouts.
For more info see Adapting to different layouts.
[Top]
Creating a custom GridView control that responds to layout changes
Many of the pages in the AdventureWorks Shopper reference implementation use the AutoRotatingGridView custom control, which is a view state detecting GridView control created for the app. When, for example, the view state changes from DefaultLayout to PortraitLayout the items displayed by the control will be automatically rearranged to use an appropriate layout for the view state. The advantage of this approach is that only one control is required to handle all the view states, rather than having to define multiple controls to handle the different view states.
In order to take advantage of the functionality provided by this control you must specify additional properties on your AutoRotatingGridView instance, such as the PortraitItemsPanel and MinimalItemTemplate properties. These additional properties are defined in the AutoRotatingGridView class, and an example of their use is shown in the following code example.
AdventureWorks.Shopper\Views\ShoppingCartPage.xaml
<awcontrols:AutoRotatingGridView x:Name="ShoppingCartItemsGridView"
x:Uid="ShoppingCartItemsGridView"
AutomationProperties.AutomationId="ShoppingCartItemsGridView"
SelectionMode="Single"
Width="Auto"
Grid.Row="2"
Grid.Column="1"
Grid.RowSpan="2"
VerticalAlignment="Top"
ItemsSource="{Binding ShoppingCartItemViewModels}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ItemTemplate="{StaticResource ShoppingCartItemTemplate}"
MinimalItemTemplate="{StaticResource ShoppingCartItemTemplateMinimal}"
Margin="0,0,0,0">
<awcontrols:AutoRotatingGridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical"
ItemWidth="400" />
</ItemsPanelTemplate>
</awcontrols:AutoRotatingGridView.ItemsPanel>
<awcontrols:AutoRotatingGridView.PortraitItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Horizontal"
ItemWidth="400" />
</ItemsPanelTemplate>
</awcontrols:AutoRotatingGridView.PortraitItemsPanel>
<awcontrols:AutoRotatingGridView.MinimalItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</awcontrols:AutoRotatingGridView.MinimalItemsPanel>
<Style TargetType="Control">
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="HorizontalContentAlignment"
Value="Left" />
</Style>
</awcontrols:AutoRotatingGridView>
[Top]
Creating a custom GridView control that displays items at multiple sizes
On the hub page we wanted the first product to be displayed at twice the dimensions of the other products, and the category page to also display the first product in each category at this larger size.
To do this we created a new class named MultipleSizedGridView that derives from the AutoRotatingGridView custom control. We then overrode the PrepareContainerForItemOverride method from the GridView class to enable the first product to span multiple rows and columns of the MultipleSizedGridView, as shown in the following code example.
AdventureWorks.Shopper\Controls\MultipleSizedGridView.cs
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var dataItem = item as ProductViewModel;
if (dataItem != null && dataItem.ItemPosition == 0)
{
_colVal = (int)LayoutSizes.PrimaryItem.Width;
_rowVal = (int)LayoutSizes.PrimaryItem.Height;
}
else
{
_colVal = (int)LayoutSizes.SecondaryItem.Width;
_rowVal = (int)LayoutSizes.SecondaryItem.Height;
}
var uiElement = element as UIElement;
VariableSizedWrapGrid.SetRowSpan(uiElement, _rowVal);
VariableSizedWrapGrid.SetColumnSpan(uiElement, _colVal);
}
The PrepareContainerForItemOverride method gets the first item in the MultipleSizedGridView and sets it to span two rows and two columns, with subsequent items occupying one row and one column. The static LayoutSizes class simply defines two Size objects that specify the number of rows and columns to span for the first item, and subsequent items in the MultipleSizedGridView, respectively.
AdventureWorks.Shopper\Controls\MultipleSizedGridView.cs
public static class LayoutSizes
{
public static Size PrimaryItem
{
get { return new Size(2, 2); }
}
public static Size SecondaryItem
{
get{return new Size(1, 1); }
}
}
[Top]
Styling controls
AdventureWorks Shopper's appearance was customized by styling and templating the controls used in the app. Styles enable you to set control properties and reuse those settings for a consistent appearance across multiple controls. Styles are defined in XAML either inline for a control, or as a reusable resource. Resources can be defined at the page level, app level, or in a separate resource dictionary. A resource dictionary can be shared across apps, and an app can use multiple resource dictionaries. For more info see Quickstart: Styling controls.
The structure and appearance of a control can be customized by defining a new ControlTemplate for the control. Templating a control can be used to avoid having to write a custom control. For more information, see Quickstart: Control templates.
[Top]
Enabling page localization
Preparing for international markets can help you reach more users. Globalizing your app provides guidelines, checklists, and tasks to help you create a user experience that reaches more users by helping you to globalize and localize each page of your app. It's important to consider localization early on in the development process, as there are some issues that will effect user interface elements across various locales. Here's the tasks that we carried out to support page localization in the AdventureWorks Shopper reference implementation.
- Separate resources for each locale.
- Ensure that each piece of text that appears in the UI is defined by a string resource.
- Add contextual comments to the app resource file.
- Define the flow direction for all pages.
- Ensure error messages are read from the resource file.
Separate resources for each locale
We maintain separate solution folders for each locale. For example, Strings -> en-US -> Resources.resw defines the strings for the en-US locale. For more info see Quickstart: Using string resources, and How to name resources using qualifiers.
Ensure that each piece of text that appears in the UI is defined by a string resource
We used the x:Uid directive to provide a unique name for the localization process to associate localized strings with text that appears on screen. The following example shows the XAML that defines the app title that appears on the hub page.
AdventureWorks.Shopper\Views\ShoppingCartPage.xaml
<TextBlock x:Uid="ShoppingCartTitle"
x:Name="pageTitle"
Text="Shopping Cart"
Grid.Column="1"
TextTrimming="WordEllipsis"
Style="{StaticResource PageHeaderTextStyle}" />
For the en-US locale, we define ShoppingCartTitle.Text in the resource file as "Shopping Cart." We specify the .Text part so that the XAML runtime will override the Text property of the TextBlock control with the value from the resource file. We also use this technique to set Button content (ContentControl.Content).
Add contextual comments to the app resource file
Comments in the resource file provide contextual information that helps localizers more accurately translate strings. For more info see How to prepare for localization.
Define the flow direction for all pages
We define the Page.FlowDirection property in the string resources file to set the flow direction for all pages. For languages that use left-to-right reading order, such as English or German, we define "LeftToRight" as its value. For languages that read right-to-left, such as Arabic and Hebrew, you define this value as "RightToLeft". We also defined the flow direction for all app bars by defining TopAppBar.FlowDirection and BottomAppBar.FlowDirection in the resource file.
Ensure error messages are read from the resource file
It's important to localize error messages strings, including exception message strings, because these strings will appear to the user. The AdventureWorks Shopper reference implementation uses an instance of the ResourceLoaderAdapter class to retrieve error messages from the resource file for your locale. This class uses an instance of the ResourceLoader class to load strings from the resource file. When we provide an error message when an exception is thrown, we use the ResourceLoaderAdapter instance to read the message text. The following code example shows how the SubmitOrderTransactionAsync method in the CheckoutSummaryPageViewModel class uses the ResourceLoaderAdapter instance to retrieve error message strings from the resource file.
AdventureWorks.UILogic\ViewModels\CheckoutSummaryPageViewModel.cs
catch (ModelValidationException mvex)
{
errorMessage = string.Format(CultureInfo.CurrentCulture, _resourceLoader.GetString("GeneralServiceErrorMessage"), Environment.NewLine, mvex.Message);
}
if (!string.IsNullOrWhiteSpace(errorMessage))
{
await _alertMessageService.ShowAsync(errorMessage, _resourceLoader.GetString("ErrorProcessingOrder"));
}
This code displays an exception error message to the user, if a ModelValidationException occurs when submitting an order. For the en-US locale, the "GeneralServiceErrorMessage" string is defined as "The following error messages were received from the service: {0} {1}," and the "ErrorProcessingOrder" string is defined as "There was an error processing your order." Other locales would have messages that convey the same error message.
Note When creating an instance of the ResourceLoader class that uses strings that are defined in a class library and not in the executable project, the ResourceLoader class has to be passed a path to the resources in the library. The path must be specified as /project name/Resources/ (for example, /Microsoft.Practices.Prism.StoreApps/Strings/).
You can test your app's localization by configuring the list of preferred languages in Control Panel. For more info about localizing your app and making it accessible, see How to prepare for localization, Guidelines for app resources, and Quickstart: Translating UI resources.
[Top]
Enabling page accessibility
Accessibility is about making your app usable by people who have limitations that impede or prevent the use of conventional user interfaces. This typically means providing support for screen readers, implementing keyboard accessibility, and supporting high-contrast themes.
Accessibility support for Windows Store apps written in C# comes from the integrated support for the Microsoft UI Automation framework that is present in the base classes and the built-in behavior of the class implementation for XAML control types. Each control class uses automation peers and automation patterns that report the control's role and content to UI automation clients. If you use non-standard controls you will be responsible for making the controls accessible.
Here are the tasks that we carried out to support page accessibility in the AdventureWorks Shopper reference implementation:
Set the accessible name for each UI element. An accessible name is a short, descriptive text string that a screen reader uses to announce a UI element. For example, in AdventureWorks Shopper XAML controls specify AutomationProperties.AutomationId and AutomationProperties.Name attached properties to make the control accessible to screen readers.
AdventureWorks.Shopper\Views\ItemDetailPage.xaml
<FlipView x:Name="flipView" AutomationProperties.AutomationId="ItemsFlipView" AutomationProperties.Name="Item Details" TabIndex="1" Grid.Row="1" ItemsSource="{Binding Items}" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" SelectedItem="{Binding SelectedProduct, Mode=TwoWay}">
For more info see Exposing basic information about UI elements.
Overridden the ToString method of the ShippingMethod, ProductViewModel, CheckoutDataViewModel, and ShoppingCartItemViewModel classes in order to support Windows Narrator. When instances of these classes are bound to the view they are styled using data templates, but Windows Narrator uses the result of the ToString overrides.
Implemented keyboard accessibility. Ensure that the tab order of controls corresponds to the visual order of controls, and that UI elements that can be clicked can also be invoked by using the keyboard. For more info see Implementing keyboard accessibility.
Visually verified the UI to ensure that the text contrast is appropriate, and that elements render correctly in high-contrast themes. For more info see Meeting requirements for accessible text and Supporting high contrast themes.
Ran accessibility tools to verify the screen reading experience. For more info see Testing your app for accessibility.
Ensured that the app manifest follows accessibility guidelines. For more info see Meeting requirements for accessible text.
For more info see Accessibility for Windows Store apps using C#/VB/C++ and XAML.
[Top]
Navigating between pages
Navigation within a Windows Store app can result from the user's interaction with the UI or from the app itself as a result of internal logic-driven state changes. Navigation usually involves moving from one page to another page in the app. In some cases, the app may implement complex logic to programmatically control navigation to ensure that certain business requirements are enforced. For example, the app may not allow the user to navigate away from a page without first ensuring that the entered data is correct.
The AdventureWorks Shopper reference implementation typically triggers navigation requests from user interaction in the views. These requests could be to navigate to a particular view or navigate back to the previous view. In some scenarios, for example if the app needs to navigate to a new view when a command completes, the view model will need to send a message to the view. In other scenarios, you might want to trigger the navigation request directly from the view without involving the view model directly. When you're using the MVVM pattern, you want to be able to navigate without using any code-behind in the view, and without introducing any dependency on the view implementation in the view model classes.
The INavigationAware interface, provided by the Microsoft.Practices.Prism.StoreApps library, allows an implementing class to participate in a navigation operation. The interface defines two methods, as shown in the following code example.
Microsoft.Practices.Prism.StoreApps\INavigationAware.cs
public interface INavigationAware
{
void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState);
void OnNavigatedFrom(Dictionary<string, object> viewModelState, bool suspending);
}
The OnNavigatedFrom and OnNavigatedTo methods are called during a navigation operation. In the view model class for the page being navigated from, its OnNavigatedFrom method is called before navigation takes place. The OnNavigatedFrom method allows the page to save any state before it is disposed of. In the view model class for the page being navigated to, its OnNavigatedTo method is called after navigation is complete. The OnNavigatedTo method allows the newly displayed page to initialize itself by loading any page state, and by using any navigation parameters passed to it. For example, the OnNavigatedTo method in the ItemDetailPageViewModel class accepts a product number as a parameter that is used to load the product information for display on the ItemDetailPage.
The ViewModel base class implements the INavigationAware interface, providing virtual OnNavigatedFrom and OnNavigatedTo methods that save and load view model state, respectively. This avoids each view model class having to implement this functionality to support the suspend and resume process. The view model classes for each page derive from the ViewModel class. The OnNavigatedFrom and OnNavigatedTo methods can then be overridden in the view model class for the page if any additional navigation logic is required, such as processing a navigation parameter that has been passed to the page.
Note The OnNavigatedFrom and OnNavigatedTo methods in the ViewModel base class control loading and saving page state during navigation operations. For more info see Handling suspend, resume, and activation.
Handling navigation requests
The XAML UI framework provides a built-in navigation model that uses Frame and Page elements and works much like the navigation in a web browser. The Frame control hosts Pages, and has a navigation history that you can use to go back and forward through pages you've visited.
Prism provides the FrameNavigationService class that allows view models to perform navigation operations without taking a dependency on UI types such as the Frame class. This class, which implements the INavigationService interface, uses the Frame instance created in the InitializeFrameAsync method in the MvvmAppBase class to perform the navigation request for the app. The MvvmAppBase class creates an instance of the FrameNavigationService class by calling the CreateNavigationService method, which is shown in the following code example.
Microsoft.Practices.Prism.StoreApps\MvvmAppBase.cs
private INavigationService CreateNavigationService(IFrameFacade rootFrame, ISessionStateService sessionStateService)
{
var navigationService = new FrameNavigationService(rootFrame, GetPageType, sessionStateService);
return navigationService;
}
The CreateNavigationService method creates an instance of the FrameNavigationService class, which takes the GetPageType delegate to implement a page type resolution strategy. This strategy assumes that the views that define pages are in the AdventureWorks.Shopper assembly and that the view names end with "Page".
After creating the instance of the FrameNavigationService class the MvvmAppBase class calls the OnInitialize override in the App class to register service instances with the Unity dependency injection container. When view model classes are instantiated, the container will inject the dependencies that are required including the FrameNavigationService instance. View models can then invoke the Navigate method on the FrameNavigationService instance to cause the app to navigate to a particular view in the app or the GoBack method to return to the previous view. The following code example shows the Navigate method in the FrameNavigationService class.
Microsoft.Practices.Prism.StoreApps\FrameNavigationService.cs
public bool Navigate(string pageToken, object parameter)
{
Type pageType = _navigationResolver(pageToken);
if (pageType == null)
{
var resourceLoader = ResourceLoader.GetForCurrentView(Constants.StoreAppsInfrastructureResourceMapId);
var error = string.Format(CultureInfo.CurrentCulture, resourceLoader.GetString("FrameNavigationServiceUnableResolveMessage"), pageToken);
throw new ArgumentException(error, "pageToken");
}
// Get the page type and parameter of the last navigation to check if we
// are trying to navigate to the exact same page that we are currently on
var lastNavigationParameter = _sessionStateService.SessionState.ContainsKey(LastNavigationParameterKey) ? _sessionStateService.SessionState[LastNavigationParameterKey] : null;
var lastPageTypeFullName = _sessionStateService.SessionState.ContainsKey(LastNavigationPageKey) ? _sessionStateService.SessionState[LastNavigationPageKey] as string : string.Empty;
if (lastPageTypeFullName != pageType.FullName || !AreEquals(lastNavigationParameter, parameter))
{
return _frame.Navigate(pageType, parameter);
}
return false;
}
The Navigate method accepts a string parameter that represents the page to be navigated to, and a navigation parameter that represents the data to pass to the page being navigated to. Any data being passed to the page being navigated to will be received by the OnNavigatedTo method of the view model class for the page type. A null value is used as the navigation parameter if no data needs to be passed to the page being navigated to.
Note The FrameNavigationService class uses the Frame class to perform the navigation process. This includes managing the navigation history, the parameters passed in each navigation request, and serializing the navigation state in order to save and restore the app state when it resumes following termination. Therefore, any parameter passed during navigation must be supported for serialization by the Frame class, which limits the parameter to basic types such as string, char, numeric and GUID types.
Placing the navigation logic in view model classes means that the navigation logic can be exercised through automated tests. In addition, the view model can then implement logic to control navigation to ensure that certain business rules are enforced. For instance, an app may not allow the user to navigate away from a page without first ensuring that the entered data is correct.
Navigating to the hub page when AdventureWorks Shopper is activated
When the AdventureWorks Shopper reference implementation starts up, and after the bootstrapping process has completed, the OnLaunchApplication method in the App class navigates to the app's hub page, provided that the app hasn't been launched from a secondary tile.
The App class derives from the MvvmAppBase class in the Microsoft.Practices.Prism.StoreApps library that in turn derives from the Windows.UI.Xaml.Application class and overrides the OnLaunched method. The OnLaunched method override calls the OnLaunchApplication method in the App class, which is shown in the following code example.
AdventureWorks.Shopper\App.xaml.cs
protected override Task OnLaunchApplication(LaunchActivatedEventArgs args)
{
if (args != null && !string.IsNullOrEmpty(args.Arguments))
{
// The app was launched from a Secondary Tile
// Navigate to the item's page
NavigationService.Navigate("ItemDetail", args.Arguments);
}
else
{
// Navigate to the initial page
NavigationService.Navigate("Hub", null);
}
Window.Current.Activate();
return Task.FromResult<object>(null);
}
This code example shows how AdventureWorks Shopper calls the Navigate method of the NavigationService object to load content that is specified by the page type.
Note The OnLaunchApplication method returns a Task, allowing it to launch a long running operation. If you don't have a long running operation to launch you should return an empty Task.
Invoking navigation using behaviors
Navigation is usually triggered from a view by a user action. For instance, each page in the app has a navigation bar which contains Button controls that allow the user to navigate to the hub page and the shopping cart page. Rather than implement this functionality separately on each page, it is implemented as a user control named TopAppBarUserControl that is added to each page. The following code example shows the Button controls from the TopAppBarUserControl that allow the user to navigate to the hub page and the shopping cart page.
AdventureWorks.Shopper\Views\TopAppBarUserControl.xaml
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Height="125" Margin="0,15,0,0">
<Button x:Name="HomeAppBarButton" x:Uid="HomeAppBarButton"
AutomationProperties.AutomationId="HomeAppBarButton"
Margin="5,0"
Style="{StaticResource HouseStyle}"
Content="Home"
Height="125">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Click">
<Core:NavigateToPageAction TargetPage="AdventureWorks.Shopper.Views.HubPage"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
<Button x:Uid="ShoppingCartAppBarButton" x:Name="ShoppingCartAppBarButton"
AutomationProperties.AutomationId="ShoppingCartAppBarButton"
Margin="0,0,5,0"
Height="125"
Style="{StaticResource CartStyle}"
Content="Shopping Cart">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Click">
<Core:NavigateToPageAction TargetPage="AdventureWorks.Shopper.Views.ShoppingCartPage"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
</StackPanel>
Note Button controls are used in the TopAppBarUserControl rather than AppBarButton controls, because their default appearance is rectangular. The AppBarButton control's default appearance is circular instead of rectangular.
In this scenario, navigation is triggered from the Button controls by using the EventTriggerBehavior and NavigateToPageAction interactions provided by the Behaviors SDK. The NavigateToPageAction interaction's TargetPage property specifies the page that will be navigated to.
When you want to pass event arguments to a navigation interaction you should use the custom NavigateWithEventArgsToPageAction interaction, which enables the ItemClick event of the MultipleSizedGridView to invoke navigation to a new page, and passes a property value as a parameter that's specified by the action's EventArgsParameterPath property.
AdventureWorks.Shopper\Views\HubPage.xaml
<awcontrols:MultipleSizedGridView x:Name="itemsGridView"
AutomationProperties.AutomationId="HubPageItemGridView"
AutomationProperties.Name="Grouped Items"
Margin="0,0,0,0"
Padding="120,0,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource AWShopperItemTemplate}"
MinimalItemTemplate="{StaticResource ProductTemplateMinimal}"
SelectionMode="None"
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
IsItemClickEnabled="True"
Loaded="itemsGridView_Loaded">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ItemClick">
<awbehaviors:NavigateWithEventArgsToPageAction TargetPage="AdventureWorks.Shopper.Views.ItemDetailPage"
EventArgsParameterPath="ClickedItem.ProductNumber" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
The EventTriggerBehavior binds the ItemClick event of the MultipleSizedGridView to the NavigateWithEventArgsToPageAction. Therefore, when a GridViewItem is selected the NavigateWithEventArgsToPageAction is executed, which navigates from the HubPage to the ItemDetailPage, passing in the ProductNumber of the ClickedItem to the ItemDetailPage.
For more info see Implementing behaviors to supplement the functionality of XAML elements.
[Top]