다음을 통해 공유


Creating and navigating between pages in Hilo (Windows Store apps using C++ and XAML)

From: Developing an end-to-end Windows Store app using C++ and XAML: Hilo

Previous page | Next page

In Windows Store apps such as Hilo, there is one page for each screen that a user can navigate to. The app creates the first page on startup and then creates subsequent pages in response to navigation requests.

Download

After you download the code, see Getting started with Hilo for instructions.

You Will Learn

  • How pages were designed in the Hilo app.
  • How the Hilo app creates pages and their data sources at run time.
  • How Hilo supports application view states such as the snapped view.

Applies To

  • Windows Runtime for Windows 8
  • Visual C++ component extensions (C++/CX)
  • XAML

Understanding the tools

For Hilo, we used Blend for Visual Studio and the Visual Studio XAML Designer to work with XAML, because these tools made it straightforward to quickly add and modify page layout. Blend was useful to initially define pages and controls; we used Visual Studio to "fine-tune" their appearances. These tools also enabled us to iterate quickly through design choices because they give immediate visual feedback.

In many cases, our UX designer was able to work in parallel with the developers because changing the visual appearance of a page does not affect its behavior.

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.

Tip  

Visual Studio groups XAML design files with its code behind. From Solution Explorer, expand any .xaml file to see its backing .h and .cpp files.

 

We recommend that you use Blend for Visual Studio 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 Blend for Visual Studio and Creating a UI by using XAML Designer.

[Top]

Adding new pages to the project

There are six pages in the Hilo app. These are

  • Main hub page
  • Browser page
  • View image page
  • Crop page
  • Rotate page
  • Cartoon effect page

The page classes are the views of the MVVM pattern:

  • MainHubView
  • ImageBrowserView
  • ImageView
  • CropImageView
  • RotateImageView
  • CartoonizerImageView

When we built the app, we added each page to the project by using the Add New Item dialog box in Visual Studio. For the project template we used the Visual C++ / Windows Store / Grid App (XAML) template. Each page is a separate XAML tree in its own code file.

The Grid App (XAML) template creates pages that are based on Visual Studio's LayoutAwarePage class, which provides navigation, state management, and view management.

In Hilo, we specialized the functionality of Visual Studio's LayoutAwarePage template class with the HiloPage class. This class customizes navigation behavior and state management for Hilo's view models. Hilo pages derive from HiloPage.

See Tutorial: Create your first Windows Store app using C++ for an introduction that walks you through creating pages in a Windows Store app. See Part 1: Create a "Hello, world" app (Windows Store apps using C#/VB and XAML) for more about the LayoutAwarePage class.

[Top]

Creating pages in the designer view

Pages in Windows Store apps are user controls that support navigation. Each page object represents content that can be navigated to by the user. Pages contain other controls. Page classes are laid out visually in the designer. All page classes are subtypes of the Windows::UI::Xaml::Page class. In Hilo, page classes also derive from the HiloPage class that in turn derives from the LayoutAwarePage class.

For example, here is what the image browser page looks like in the Visual Studio designer.

Each page has an associated XAML file that is created by Visual Studio, and a .h and .cpp file for page-related code written by the app developer.

Visual Studio uses the partial keyword, which is part of the C++/CX language extensions, to split the declaration of a page class into several files. For example, for the hub page, the MainHubView class is split into four files.

  • MainHubView.xaml. This file contains the XAML markup that describe the page's controls and their visual layout.
  • MainHubView.g.h. This header file contains declarations that Visual Studio creates from the XAML source. You should not modify the contents of this file. (The "g" suffix in the file name stands for generated.)
  • MainHubView.xaml.h. This header contains declarations of member functions whose implementations you can customize. You can also add your own declarations of member functions and member variables to this file.
  • MainHubView.xaml.cpp. This is the code-behind file. Due to the use of the Model-View-ViewModel (MVVM) pattern, this file only contains event handling code that delegates operations to the page's view model class, MainHubViewModel.

Tip  Hilo uses the MVVM pattern that abstracts the user interface for the application. With MVVM you rarely need to customize the code-behind .cpp 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 Hilo's interaction model and more info about how we designed the Hilo UX, see Designing the UX for Windows Store apps.

[Top]

Establishing the data binding

Data binding links each page to a view model class that is part of the Hilo 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 in this guide.

The data context of each page class in the Hilo app is set to the Hilo app’s ViewModelLocator class. The data binding specifies the view model locator as a static resource, which means the resource has previously been defined. In this case, the resource is a singleton instance of the ViewModelLocator class.

Here is how the data context of the Hilo hub page is set with a data binding, taken from the MainHubView.xaml file:

DataContext="{Binding Source={StaticResource ViewModelLocator}, Path=MainHubVM}"

When the Windows Runtime creates a Hilo page, it binds to the property specified in the Path field of the data context. The property's get method is a method of Hilo’s ViewModelLocator class, with the method creating a new view model object. For example, the ViewModelLocator class’s MainHubVM method creates an instance of the MainHubViewModel class.

ViewModelLocator.cpp

MainHubViewModel^ ViewModelLocator::MainHubVM::get()
{
    auto vector = ref new Vector<HubPhotoGroup^>();
    // Pictures Group
    auto loader = ref new ResourceLoader();
    auto title = loader->GetString("PicturesTitle");
    auto emptyTitle = loader->GetString("EmptyPicturesTitle");
    auto picturesGroup = ref new HubPhotoGroup(title, emptyTitle, m_repository, m_exceptionPolicy);
    vector->Append(picturesGroup);
    return ref new MainHubViewModel(vector, m_exceptionPolicy);
}

The MainHubViewModel object also exposes other properties that the Hub page class can use.

For reference, here is a summary of the pages, binding paths, and view model classes found in the Hilo app:

App page Page class Data binding path View model class
Hub MainHubView MainHubVM MainHubViewModel
Browse ImageBrowserView ImageBrowserVM ImageBrowserViewModel
Image ImageView ImageVM ImageViewModel
Crop CropImageView CropImageVM CropImageViewModel
Rotate RotateImageView RotateImageVM RotateImageViewModel
Cartoon Effect CartoonizerImageView CartoonizerImageVM CartoonizerImageViewModel

 

[Top]

Adding design time data

When you use a visual designer to create a data bound UI, you can display sample data 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 Hilo, 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 XML namespace. This XML namespace is typically declared with a d: prefix.

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. For example, in a CollectionViewSource the d:Source attribute is used for design time sample data, and the Source attribute is used for run time data.

ImageBrowserView.xaml

<CollectionViewSource
    x:Name="MonthGroupedItemsViewSource"
    d:Source="{Binding MonthGroups, Source={d:DesignInstance Type=local:DesignTimeData, IsDesignTimeCreatable=True}}"
    Source="{Binding MonthGroups}"
    IsSourceGrouped="true"
    ItemsPath="Items"/>

The d:DesignInstance attribute indicates that the design time source is a designer created instance based on the DesignTimeData 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 information, see Data binding overview.

Creating the main hub page

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. You can also pass data between pages as you navigate. In the Visual Studio project templates, a Frame named rootFrame is set as the content of the app window.

When the Hilo app starts up, the OnLaunched method of the Hilo::App class navigates to the app’s hub page.

The App class derives from the Windows::UI:Xaml::Application class and overrides theOnLaunched method. Here is the relevant code from the OnLaunched method.

App.xaml.cpp

if (rootFrame->Content == nullptr)
{
    // When the navigation stack isn't restored navigate to the first page,
    // configuring the new page by passing required information as a navigation
    // parameter.  See https://go.microsoft.com/fwlink/?LinkId=267278 for a walkthrough of how 
    // Hilo creates pages and navigates to pages.
    if (!rootFrame->Navigate(TypeName(MainHubView::typeid)))
    {
        throw ref new FailureException((ref new LocalResourceLoader())->GetString("ErrorFailedToCreateInitialPage"));
    }
}

This code shows how Hilo calls the Navigate method of a Windows::UI::Xaml::Controls::Frame object to load content that is specified by the data type, in this case the MainHubView class. The Frame control helps maintain application context. The code tests whether the frame object's Content property is null as a way of determining whether the app is resuming from a previous state or starting in its default navigation state. (For more info on resuming from previous states, see Handling suspend, resume, and activation in this guide.)

See Tutorial: Create your first Windows Store app using C++ for additional info and code examples of creating pages.

[Top]

The LayoutAwarePage class provides methods that use frames to help the app navigate between pages. These are the GoHome, GoForward, and GoBack methods. Hilo calls these methods to initiate page navigation.

For example, the filmstrip control's Back button on the image view page invokes the GoBack method.

ImageView.xaml

<Button x:Name="FilmStripBackButton" 
        AutomationProperties.AutomationId="FilmStripBackButton" 
        Click="GoBack" 
        IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}"
        Margin="26,53,36,36"
        Style="{StaticResource BackButtonStyle}"
        VerticalAlignment="Top"/>

Direct navigation by the user is not the only possibility. For example, when the rotate image page loads, it locates the image in the file system. If the image is no longer there due to file system changes outside of the Hilo app, the rotate operation navigates back to the main hub page programmatically. Here's the code.

RotateImageViewModel.cpp

void RotateImageViewModel::Initialize(String^ photoPath)
{
    assert(IsMainThread());
    m_photo = nullptr;
    m_photoPath = photoPath;

    GetImagePhotoAsync().then([this](IPhotoImage^ photo)
    {
        assert(IsMainThread());
        // Return to the hub page if the photo is no longer present
        if (photo == nullptr)
        {
            GoHome();
        }
    });
}

See Tutorial: Create your first Windows Store app using C++ for additional info about navigating between pages.

[Top]

Supporting portrait, snap, and fill layouts

We designed Hilo to be viewed full-screen in the landscape orientation. Windows Store apps such as Hilo must adapt to different application view states, including both landscape and portrait orientations. Hilo supports FullScreenLandscape, Filled, FullScreenPortrait, and Snapped layouts. Hilo uses the VisualState class to specify changes to the visual display to support each layout. The VisualStateManager class, used by the LayoutAwarePage class, manages states and the logic for transitioning between states for controls. For example, here is the XAML specification of the layout changes for the crop image page.

CropImageView.xaml

<VisualStateManager.VisualStateGroups>
    <!-- Visual states reflect the application's view state -->
    <VisualStateGroup x:Name="ApplicationViewStates">
        <VisualState x:Name="FullScreenLandscape"/>
        <VisualState x:Name="Filled"/>
        <VisualState x:Name="FullScreenPortrait">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CancelButton"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CancelButtonNoLabel"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>       
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SaveButton"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SaveButtonNoLabel"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
          </Storyboard>
        </VisualState>
        <VisualState x:Name="Snapped">
            <Storyboard>                  
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CancelButton"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CancelButtonNoLabel"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SaveButton"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ResumeCropStackPanel"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CropCanvas"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CropImageGrid"
                                               Storyboard.TargetProperty="Margin">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="20,50,20,0"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PageTitle"
                                               Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0"
                                            Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Note  We typically set the Style property when we need to update multiple properties or when there is a defined style that does what we want. We often update individual properties directly when we only need to update one property. 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. Use styles only when it makes sense to do so. For more info about styling controls, see Quickstart: styling controls.

 

In most cases you can provide complete support for the app's view states by using XAML. In Hilo, there is only one place where we needed to change the C++ code to support layout. This is in the crop image view model, where the app adjusts the position of the crop overlay to match the new layout. To do this, the app provides a handler for the SizeChanged event.

CropImageView.xaml

<Image x:Name="Photo" 
       AutomationProperties.AutomationId="ImageControl"
       HorizontalAlignment="Center" 
       VerticalAlignment="Center" 
       SizeChanged="OnSizeChanged"
       Source="{Binding Image}"/>

Hilo binds the SizeChanged event to the CropImageView::OnSizeChanged member function.

CropImageView.cpp

void CropImageView::OnSizeChanged(Object^ sender, SizeChangedEventArgs^ e)
{
    m_cropImageViewModel->CalculateInitialCropOverlayPosition(
        Photo->TransformToVisual(CropImageGrid), 
        Photo->RenderSize.Width, Photo->RenderSize.Height);

    if (!m_sizeChangedAttached)
    {
        SizeChanged += ref new SizeChangedEventHandler(this, &CropImageView::OnSizeChanged);
        m_sizeChangedAttached = true;
    }
}

When the image size changes as a result of a crop operation, the handler invokes the crop image view model's CalculateInitialCropOverlayPosition method, which makes the crop overlay fit the new layout. In addition, when the size of the page changes, such as when switching from the FullScreenLandscape view state to the Filled view state, the handler will again make the crop overlay fit the new layout.

Tip  When you develop an app in Visual Studio, you can use the Simulator debugger to test layouts. To do this, press F5 and use the debugger tool bar to debug with the Simulator.

 

See Adapting to different layouts for more info and a walkthrough of a sample app that responds to layout changes.

[Top]