共用方式為


Using controls 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

Controls are the core of Windows 8 and XAML. Learn about some of the controls, including Image, Grid and GridView, ProgressRing, Button, TextBlock, AppBar, StackPanel, ListView, SemanticZoom, Canvas and ContentControl, and Popup, and how we used binding and styles to implement the Hilo C++ UI.

Download

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

You will learn

  • How to use binding to link the UI to code-behind.
  • How to apply data templates.
  • How to write custom controls.
  • How to handle invalid images when using binding.
  • How to convert your model data to correctly display.

Applies to

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

You might notice the declarative, rather than imperative, nature of XAML. XAML forms a tree of objects, which you can think of like a DOM in HTML. If you're just getting started, see Quickstart: Create a UI with XAML and then read Quickstart: adding controls and handling events.

Note  The topic Creating and navigating between pages explains how we used Blend for Visual Studio and the Visual Studio XAML Designer to work with XAML pages and controls. Although these tools are easy to use, don't overstyle your controls. For example, not every button needs a gradient background or rounded corners. See Index of UX guidelines for Windows Store apps for the recommended guidelines.

 

Here are a few important concepts in XAML that may be unfamiliar to C++ developers:

  • Code-behind connects XAML markup with compiled code that implements the logic of your app. For example, for a Button control, you implement the handler for the Click event in your C++ code. This C++ code is an example of code-behind.

    Although you can add logic directly into code behind, in Hilo, code behind connects the XAML to view models that define its logic.

  • A template or data template defines the structure, layout, and animations that are associated with a control, plus its styles. A template affects the look and feel of a control, but not its behaviors. For example, list boxes and most other controls use strings to represent their data elements. Templates enable you to use graphics and other visual features to represent data. You use the DataTemplate class to define the visual representation of data from XAML.

  • Binding provides a data-bound property value such that the value is deferred until run time. You use the Binding class to connect your UI to data.

  • A custom control aggregates functionality of other controls.

  • A data converter transforms one value into another. Use data converters together with binding to change the value or format of bound data to something that your app will display.

  • A resource refers to functionality that can be reused. For example, styles, brushes, and text that appear on a page are all examples of resources.

The way that you program Windows Store apps using XAML closely resembles Windows Presentation Foundation (WPF) and Silverlight. The main differences are that Windows Store apps are focused on touch and provide several new controls. Read Porting Silverlight or WPF XAML/code to a Windows Store app to learn the differences between Silverlight or WPF and Windows Store apps using XAML. Controls list and Controls by function list all available controls, including AppBar, GridView, SemanticZoom, and other controls that are new to Windows 8.

Note  A Windows Store app using C++ and XAML runs completely as native code. XAML controls are also accelerated by graphics hardware. The C++/CX syntax helps make it easier to target the Windows Runtime, and doesn't use the .NET Framework. Your C++ app targets .NET only when it uses a Windows Runtime component that uses .NET. For more info about C++/CX, see Writing modern C++ code.

 

[Top]

Data binding

Binding provides a data-bound property value such that the value is deferred until run time. Binding is key to effectively using XAML because it enables you to declaratively connect your UI to data. Binding also helps minimize the need for code behind. Because data is resolved at run time, you can clearly separate your UI from your data when you design your app. For Hilo, binding data at run time is critical because we don't know anything about the user's picture library at design time. For more info about binding, see Data binding overview and Binding markup extension.

Binding also supports the MVVM pattern. For more info about how we use binding in Hilo, see Using the MVVM pattern. Later sections on this page also describe how we used binding to link to both static and dynamic content.

[Top]

Data converters

A data converter transforms a value. A converter can take a value of one type and return a value of another type or simply change a value to produce the same type.

For Hilo, we use data converters to change the value or format of bound data when data types need special formatting that the view model doesn’t provide. Here are the data converters we defined for Hilo.

Converter type Converts from Converts to Description
BooleanToBrushConverter bool Platform::String On the calendar view, used to show orange for months that have pictures and grey for months that don't have pictures. The value true maps to orange (#F19720) and false maps to grey (#E2E2E2). This works because a type converter is present on the Brush class to convert the string value to a Brush.
FileSizeConverter unsigned long long Platform::String Used to convert file sizes in bytes to kilobytes or megabytes to make the file size more human-readable.

 

The Visual Studio project templates also provide the BooleanNegationConverter and BooleanToVisibilityConverter classes, which negate bool values and convert bool values to Visibility enumeration values, respectively. You can find these classes in the Common folder of the solution.

To create a data converter, you create a type that derives from IValueConverter. You then implement the Convert, ConvertBack methods, and any additional methods that you need. Here's the declaration for the FileSizeConverter class.

FileSizeConverter.h

[Windows::Foundation::Metadata::WebHostHidden]
public ref class FileSizeConverter sealed : public Windows::UI::Xaml::Data::IValueConverter
{
public:
    FileSizeConverter();
    FileSizeConverter(IResourceLoader^ loader);

    virtual Object^ Convert(Object^ value, Windows::UI::Xaml::Interop::TypeName targetType, Object^ parameter, Platform::String^ language);
    virtual Object^ ConvertBack(Object^ value, Windows::UI::Xaml::Interop::TypeName targetType, Object^ parameter, Platform::String^ language);

private:
    float64 ToTwoDecimalPlaces(float64 value);
    IResourceLoader^ m_loader;
};

And here's its implementation.

FileSizeConverter.cpp

Object^ FileSizeConverter::Convert(Object^ value, TypeName targetType, Object^ parameter, String^)
{
    float64 size = static_cast<float64>(safe_cast<uint64>(value));
    std::array<String^, 3> units = 
    { 
        m_loader->GetString("BytesUnit"), 
        m_loader->GetString("KilobytesUnit"), 
        m_loader->GetString("MegabytesUnit") 
    };
    unsigned int index = 0;

    while (size >= 1024)
    {
        size /= 1024;
        index++;
    }

    return ToTwoDecimalPlaces(size) + " " + units[index];
}

float64 FileSizeConverter::ToTwoDecimalPlaces(float64 value)
{
    float64 f;
    float64 intpart;
    float64 fractpart;
    fractpart = modf(value, &intpart);
    f = floor(fractpart * 100 + 0.5) / 100.0;
    return intpart + f;
}

Object^ FileSizeConverter::ConvertBack(Object^ value, TypeName targetType, Object^ parameter, String^)
{
    throw ref new NotImplementedException();
}

In the FileSizeConverter::Convert method, we use safe_cast to convert from Object^ to the value type uint64 (safe_cast throws Platform::InvalidCastException if the conversion fails). We cast to uint64 because we bind the converter to the IPhoto::FileSize property, which returns uint64. We then convert the uint64 value to float64 because the computation uses floating-point arithmetic.

We must implement the ConvertBack method because it's part of the IValueConverter interface. But we throw NotImplementedException to indicate that we have not implemented this functionality. You should throw this exception instead of returning a default value when you don't expect the method to be called. That way, during development, you can verify that the method is never called. If you return a default value, the app can behave in ways you don't expect. In Hilo, we don't expect ConvertBack to be called because we're not performing two-way binding. (In some applications, a field that is bound to a date might allow the user to type a new date value. This two-way binding would require both the Convert and ConvertBack methods to work properly.)

To use your converter from XAML, first add it to your resource dictionary. Here's how we did so for the image view.

ImageView.xaml

<Page.Resources>
    <local:FileSizeConverter x:Key="FileSizeConverter" />
    <Style x:Key="FilmStripGridViewItemStyle" 
           BasedOn="{StaticResource HiloGridViewItemStyle}" 
           TargetType="GridViewItem">
        <Setter Property="Margin" Value="0,0,2,4" />
    </Style>
</Page.Resources>

You must also include the header file for the converter in the code-behind for the page.

ImageView.xaml.h

#include "FileSizeConverter.h" // Required by generated header
#include "ImageView.g.h"

To convert a bound value, use the Binding::Converter property from XAML. This example uses FileSizeConverter to make the image size on disk more human-readable on the popup that displays additional file info. (For more info about this popup, see Press and hold to learn in this guide.)

ImageView.xaml

<TextBlock Foreground="{StaticResource ApplicationForegroundThemeBrush}" 
           Text="{Binding Path=CurrentPhotoImage.FileSize, Converter={StaticResource FileSizeConverter}}" />

For more info about converters, see Data binding overview.

[Top]

Common controls used in Hilo

Here are some of the common controls that we used in Hilo. You can also refer to the .xaml files that appear in the Views folder in the Visual Studio project.

Tip  See Controls list and Controls by function for all available controls. Bookmark these pages so that you can come back to them when you want to add another feature to your app. Also use the Toolbox window in Visual Studio and the Assets tab in Blend for Visual Studio to browse and add controls to your pages.

 

  • Image
  • Grid and GridView
  • ProgressRing
  • Button
  • TextBlock
  • AppBar
  • StackPanel
  • ListView
  • SemanticZoom
  • Canvas and ContentControl
  • Popup

Image

We use the Image control to display pictures in the app. We found it relatively straightforward to use the standard properties to set the size, alignment, and other display properties. At times, we don't specify properties such as the size to allow the framework to fit the image to the available size. For example, for a Grid control, if a child's size is not set explicitly, it stretches to fill the available space in the grid cell. For more info about the layout characteristics of common controls, see Quickstart: Adding layout controls.

Here's the XAML for the Image.

CropImageView.xaml

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

Because the majority of images are of the user's photos, we found binding to be a very useful way to specify what to load at runtime without the need to write any code-behind. We also needed to handle image files that cannot be loaded (for example, when the image file is corrupt.) The Hilo Photo class, which is bound to the view model and provides the image data, subscribes to the Image::ImageFailed event, which is called when an error occurs during image retrieval.

PhotoImage.cpp

m_image = ref new BitmapImage();
m_imageFailedEventToken = m_image->ImageFailed::add(ref new ExceptionRoutedEventHandler(this, &PhotoImage::OnImageFailedToOpen));

The handler for the Image::ImageFailed event, PhotoImage::OnImageFailedToOpen, uses the ms-appx:// syntax to load a default image when an error occurs during image retrieval.

PhotoImage.cpp

void PhotoImage::OnImageFailedToOpen(Object^ sender, ExceptionRoutedEventArgs^ e)
{
    assert(IsMainThread());

    // Load a default image.
    ClearImageData();
    m_image = ref new BitmapImage(ref new Uri("ms-appx:///Assets/HiloLogo.png"));  
    OnPropertyChanged("Image");
}

For more info about ms-appx:// and other URI schemes, see How to load file resources.

The next section describes how we used binding to display the filmstrip that appears on the image page.

For more info about images, see Quickstart: Image and ImageBrush, XAML images sample, and XAML essential controls sample.

Grid and GridView

Grids are the heart of every page in the Hilo app because they enabled us to position child controls where we wanted them. A Grid is a layout panel that supports arranging child elements in rows and columns. A GridView presents a collection of items in rows and columns that can scroll horizontally. A GridView can also display variable-sized collections.

We used Grid to display fixed numbers of items that all fit on the screen. We used GridView to display variable-sized collections. For example, we used GridView on the image browser page to group photos by month. We also used GridView to display the filmstrip that appears in the navigation bar on the image page.

Here's the XAML for the GridView that represents the filmstrip.

ImageView.xaml

<GridView x:Name="PhotosFilmStripGridView"
          Grid.Column="1"
          AutomationProperties.AutomationId="PhotosFilmStripGridView"
          IsItemClickEnabled="False"
          ItemContainerStyle="{StaticResource FilmStripGridViewItemStyle}"
          ItemsSource="{Binding Photos}"
          SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"
          SelectionMode="Single"
          VerticalAlignment="Center">
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Height="138" Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
    <GridView.ItemTemplate>
        <DataTemplate>
            <Border>
                <Image Source="{Binding Path=Thumbnail}" 
                       Height="138" 
                       Width="200" 
                       Stretch="UniformToFill" />
            </Border>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Note  The x:Name attribute specifies the name of the control instance to the code. In this example, the compiler generates a member variable named PhotosFilmStripGridView for the ImageView class. This member variable appears in the file ImageView.g.h, which is generated by the compiler.

 

This GridView uses FilmStripGridViewItemStyle, which is defined in ApplicationStyles.xaml, to customize the appearance of the GridViewItems that are displayed in the GridView. Each GridViewItem is an Image control, wrapped in a Border control, and is defined in the DataTemplate of the GridView. In order to support UI virtualization, a VirtualizingStackPanel is used to display the GridViewItems, and is defined in the ItemsPanelTemplate of the GridView. For more information about UI virtualization, see UI virtualization for working with large data sets in this guide.

The next example shows how the ImageView::OnFilmStripLoaded method scrolls the selected item into view.

ImageView.xaml.cpp

// Scrolls the selected item into view after the collection is likely to have loaded.
void ImageView::OnFilmStripLoaded(Object^ sender, RoutedEventArgs^ e)
{
    auto vm = dynamic_cast<ImageViewModel^>(DataContext);
    if (vm != nullptr)
    {
        PhotosFilmStripGridView->ScrollIntoView(vm->SelectedItem);
    }

    PhotosFilmStripGridView->Loaded::remove(m_filmStripLoadedToken);
}

An interesting aspect of GridView is how it binds to data. To enable MVVM on the image view page, we set the pages'DataContext to the ImageVM property of the ViewModelLocator class (for more info on MVVM, see Using the MVVM pattern.)

ImageView.xaml

<local:HiloPage
    x:Name="pageRoot"
    x:Uid="Page"
    x:Class="Hilo.ImageView"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Hilo"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding Source={StaticResource ViewModelLocator}, Path=ImageVM}">

As a result, child controls of the page bind to the ImageViewModel class (the ViewModelLocator::ImageVM property returns an ImageViewModel object.) For the filmstrip, we bind to the ImageViewModel::Photos property to display the photos for the current month.

ImageView.xaml

<GridView x:Name="PhotosFilmStripGridView"
          Grid.Column="1"
          AutomationProperties.AutomationId="PhotosFilmStripGridView"
          IsItemClickEnabled="False"
          ItemContainerStyle="{StaticResource FilmStripGridViewItemStyle}"
          ItemsSource="{Binding Photos}"
          SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"
          SelectionMode="Single"
          VerticalAlignment="Center">

The ImageViewModel::Photos property is a collection of IPhoto objects. To display a single photo, we use an Image element that is bound to the IPhoto::Thumbnail property.

ImageView.xaml

<GridView.ItemTemplate>
    <DataTemplate>
        <Border>
            <Image Source="{Binding Path=Thumbnail}" 
                   Height="138" 
                   Width="200" 
                   Stretch="UniformToFill" />
        </Border>
    </DataTemplate>
</GridView.ItemTemplate>

At run time, the binding between the view and the view model occurs when the page is created. When a user navigates to the page, the ImageViewModel::OnNavigatedTo method sets the file system query for the current month's pictures. When the GridView first displays the photos for the current month, the ImageViewModel::Photos property invokes the ImageViewModel::OnDataChanged method, which in turn invokes the ImageViewModel::QueryPhotosAsync method. The ImageViewModel::QueryPhotosAsync method invokes GetPhotosForDateRangeQueryAsync on the shared pointer instance of the FileSystemRepository class, which was passed into the ImageViewModel constructor, and passes the m_query member variable which contains the file system query for the current month's pictures.

For more info about GridView, see Adding ListView and GridView controls and Item templates for grid layouts.

ProgressRing

We use the ProgressRing control to indicate that an operation is in progress. We perform any long-running operations in the background, so the user can interact with the app, and we use progress rings to show that an operation is occurring and will complete shortly. Use progress controls to indicate operations that take 2 or more seconds to complete.

Here's the XAML for the image browser page. The progress ring appears when the OS is loading thumbnail data.

ImageBrowserView.xaml

<ProgressRing x:Name="ProgressRing" 
              IsActive="{Binding InProgress}" 
              Height="60"
              Width="60"
              Foreground="{StaticResource HiloHighlightBrush}"/>

We bind the IsActive property to the ImageBrowserViewModel::InProgress property (a Boolean value). The ImageBrowserViewModel::InProgress property returns true when the view model is querying the OS for images. After the query finishes, the ImageBrowserViewModel::InProgress property returns false and the view model object raises the PropertyChanged event to instruct the XAML runtime to hide the progress ring.

We use the ProgressRing control instead of the ProgressBar control to indicate that an operation is in progress because the time remaining to complete that operation is not known. Use ProgressBar to provide more detailed info about when an operation will complete. Progress bars and progress rings should not prevent the user from canceling the operation or navigating to another page. For more info about performing and canceling background operations, see Async programming patterns in C++.

Note  Always set the ProgressRing::IsActive and ProgressBar::IsIndeterminate properties to false when you've finished showing progress info. Even when these controls are not visible, their animations can still consume CPU cycles and thus cause performance problems or shorten battery life. Also, when ProgressRing::IsActive is false, the ProgressRing is not shown, but space is reserved for it in the UI layout. To not reserve space for the ProgressRing, set its Visibility property to Collapsed.

 

For more progress control guidelines, see Guidelines and checklist for progress controls.

Button

A Button control responds to user input and raises a Click event. In Hilo, we use buttons to:

  • Define a back button on each page.
  • Enable the user to navigate to the browse pictures page from the hub page.
  • Enable the user to navigate to all photos for a given month.
  • Add commands to app bars.

Here's the XAML for the Button control that enables the user to navigate to all photos for a given month.

ImageBrowserView.xaml

<Button AutomationProperties.AutomationId="MonthGroupTitle"
        Content="{Binding Title}"
        Command="{Binding ElementName=MonthPhotosGridView, Path=DataContext.GroupCommand}"
        CommandParameter="{Binding}"
        Style="{StaticResource HiloTextButtonStyle}" />

An important part of this implementation is the fact that the button's Command binds to the GroupCommand property on the view model. Also, we bind the button's Content property to a text string. However, you can bind the button's content to any class that derives from Panel. The Button is styled using the HiloTextButtonStyle, which uses the SubheaderTextStyle specified in StandardStyles.xaml, with no other adornment.

The ImageBrowserViewModel constructor sets the m_groupCommand (the backing ICommand value for the GroupCommand property) to point to the NavigateToGroup method.

ImageBrowserViewModel.cpp

m_groupCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &ImageBrowserViewModel::NavigateToGroup), nullptr);

The NavigateToGroup method navigates to the image browser page for the current month.

ImageBrowserViewModel.cpp

void ImageBrowserViewModel::NavigateToGroup(Object^ parameter)
{
    auto group = dynamic_cast<IPhotoGroup^>(parameter);
    assert(group != nullptr);
    if (group->Items->Size > 0)
    {
        auto photo = dynamic_cast<IPhoto^>(group->Items->GetAt(0));
        assert(photo != nullptr);
        
        create_task(photo->GetDateTakenAsync()).then([this, photo](DateTime dateTaken)
    {
        ImageNavigationData data(photo->Path, dateTaken);
        ViewModelBase::GoToPage(PageType::Image, data.SerializeToString());
    }).then(ObserveException<void>(m_exceptionPolicy));

    }
}

See Executing commands in a view model in this guide for more info on binding commands to the view model.

For more info about buttons, see Adding buttons and XAML essential controls sample.

TextBlock

The TextBlock control displays text. We use text blocks to display the title of each page and to fill the Popup control that displays additional info about a picture when the user presses and holds the current picture.

Binding and the use of resources are two important aspects of displaying text. Binding helps defer the value of a text block until run time (for example, to display info about a picture.) Resources are important because they enable you to more easily localize your app. For more info about localization, see Making your app world ready in this guide.

For more info about text blocks, see Quickstart: Displaying text and XAML text display sample.

AppBar

The AppBar control is a toolbar for displaying app-specific commands. Hilo displays a bottom app bar on every page, and a navigation bar on the image view page that displays a filmstrip view of all photos for the current month. The Page::BottomAppBar property can be used to define the bottom app bar and the Page::TopAppBar property can be used to define the navigation bar.

Here's what the buttons look like on the bottom app bar on the image view page.

You can add buttons that enable navigation or critical app features on a page. Place buttons that are not critical to the navigation and use of your app in an app bar. For example, on the image browser page, we enable the user to click on the text for a given month to jump to all photos for that month because we felt that it was crucial to navigation. We added the rotate, crop, and cartoon effect commands to the app bar because these are secondary commands and we didn't want them to distract the user.

Here's the XAML for the bottom app bar that appears on the image view page. This app bar contains Button elements that enable the user to enter rotate, crop, and cartoon effect modes.

ImageView.xaml

<local:HiloPage.BottomAppBar>
    <AppBar x:Name="ImageViewBottomAppBar"
            x:Uid="AppBar"
            AutomationProperties.AutomationId="ImageViewBottomAppBar"
            Padding="10,0,10,0">
        <Grid>
            <StackPanel HorizontalAlignment="Left" 
                        Orientation="Horizontal">
                <Button x:Name="RotateButton"
                        x:Uid="RotateAppBarButton"
                        Command="{Binding RotateImageCommand}" 
                        Style="{StaticResource RotateAppBarButtonStyle}" 
                        Tag="Rotate" />
                <Button x:Name="CropButton"
                        x:Uid="CropAppBarButton"
                        Command="{Binding CropImageCommand}"
                        Style="{StaticResource CropAppBarButtonStyle}"
                        Tag="Crop" />
                <Button x:Name="CartoonizeButton"
                        x:Uid="CartoonizeAppBarButton"
                        Command="{Binding CartoonizeImageCommand}"
                        Style="{StaticResource CartoonEffectAppBarButtonStyle}"
                        Tag="Cartoon effect" />
                <Button x:Name="RotateButtonNoLabel"
                        Command="{Binding RotateImageCommand}" 
                        Style="{StaticResource RotateAppBarButtonNoLabelStyle}" 
                        Tag="Rotate"
                        Visibility="Collapsed">
                    <ToolTipService.ToolTip>
                        <ToolTip x:Uid="RotateAppBarButtonToolTip" />
                    </ToolTipService.ToolTip>
                </Button>
                <Button x:Name="CropButtonNoLabel"
                        Command="{Binding CropImageCommand}"
                        Style="{StaticResource CropAppBarButtonNoLabelStyle}"
                        Tag="Crop"
                        Visibility="Collapsed">
                    <ToolTipService.ToolTip>
                        <ToolTip x:Uid="CropAppBarButtonToolTip" />
                    </ToolTipService.ToolTip>
                </Button>
                <Button x:Name="CartoonizeButtonNoLabel"
                        Command="{Binding CartoonizeImageCommand}"
                        Style="{StaticResource CartoonEffectAppBarButtonNoLabelStyle}"
                        Tag="Cartoon effect"
                        Visibility="Collapsed">
                    <ToolTipService.ToolTip>
                        <ToolTip x:Uid="CartoonizeAppBarButtonToolTip" />
                    </ToolTipService.ToolTip>
                </Button>
            </StackPanel>
        </Grid>
    </AppBar>
</local:HiloPage.BottomAppBar>

We define two sets of buttons—one set for landscape mode and one for portrait mode. Per UI guidelines for app bars, we display labels on the buttons in landscape mode and hide the labels in portrait mode. Here's the XAML that defines the visibility for the buttons in portrait mode.

ImageView.xaml

<VisualState x:Name="FullScreenPortrait">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackButton" 
                                       Storyboard.TargetProperty="Style">
            <DiscreteObjectKeyFrame KeyTime="0" 
                                    Value="{StaticResource PortraitBackButtonStyle}"/>
        </ObjectAnimationUsingKeyFrames>

        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemGridView"
                                       Storyboard.TargetProperty="ItemsPanel">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="{StaticResource HubVerticalVirtualizingStackPanelTemplate}" />
        </ObjectAnimationUsingKeyFrames>

        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemGridView" 
                                       Storyboard.TargetProperty="Padding">
            <DiscreteObjectKeyFrame KeyTime="0" 
                                    Value="96,0,10,56"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RotateButton"
                                       Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="Collapsed"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CropButton"
                                       Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="Collapsed"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CartoonizeButton"
                                       Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="Collapsed"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RotateButtonNoLabel"
                                       Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="Visible"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CropButtonNoLabel"
                                       Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="Visible"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CartoonizeButtonNoLabel"
                                       Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="Visible"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualState>

Tip  We authored this XAML by hand. However, many developers prefer to use Blend for Visual Studio to define visual states.

 

For more info about how we used app bars in Hilo, see Swipe from edge for app commands in this guide.

StackPanel

The StackPanel control arranges its children into a single line that can be oriented horizontally or vertically. Our use of StackPanel includes:

  • Arranging buttons on the app bar.
  • Displaying a progress ring next to another control, such as a Button.
  • Displaying elements in a Popup control to implement press and hold. For more info, see Press and hold to learn in this guide.

Here's an example of how we used StackPanel to arrange buttons on the bottom app bar of the hub page.

MainHubView.xaml

<local:HiloPage.BottomAppBar>
    <AppBar x:Name="MainHubViewBottomAppBar"
            x:Uid="AppBar"
            AutomationProperties.AutomationId="MainHubViewBottomAppBar"
            IsOpen="{Binding Path=IsAppBarOpen, Mode=TwoWay}"
            IsSticky="{Binding Path=IsAppBarSticky, Mode=TwoWay}"
            Padding="10,0,10,0"
            Visibility="{Binding Path=IsAppBarEnabled, Mode=TwoWay, Converter={StaticResource BoolToVisConverter}}">
        <Grid>
            <StackPanel HorizontalAlignment="Left" 
                        Orientation="Horizontal">
                <Button x:Name="RotateButton"
                        x:Uid="RotateAppBarButton"
                        Command="{Binding RotateImageCommand}" 
                        Style="{StaticResource RotateAppBarButtonStyle}" 
                        Tag="Rotate" />
                <Button x:Name="CropButton"
                        x:Uid="CropAppBarButton"
                        Command="{Binding CropImageCommand}"
                        Style="{StaticResource CropAppBarButtonStyle}"
                        Tag="Crop" />
                <Button x:Name="CartoonizeButton"
                        x:Uid="CartoonizeAppBarButton"
                        Command="{Binding CartoonizeImageCommand}"
                        Style="{StaticResource CartoonEffectAppBarButtonStyle}"
                        Tag="Cartoon effect" />
                <Button x:Name="RotateButtonNoLabel"
                        Command="{Binding RotateImageCommand}" 
                        Style="{StaticResource RotateAppBarButtonNoLabelStyle}" 
                        Tag="Rotate"
                        Visibility="Collapsed">
                    <ToolTipService.ToolTip>
                        <ToolTip x:Uid="RotateAppBarButtonToolTip" />
                    </ToolTipService.ToolTip>
                </Button>
                <Button x:Name="CropButtonNoLabel"
                        Command="{Binding CropImageCommand}"
                        Style="{StaticResource CropAppBarButtonNoLabelStyle}"
                        Tag="Crop"
                        Visibility="Collapsed">
                    <ToolTipService.ToolTip>
                        <ToolTip x:Uid="CropAppBarButtonToolTip" />
                    </ToolTipService.ToolTip>
                </Button>
                <Button x:Name="CartoonizeButtonNoLabel"
                        Command="{Binding CartoonizeImageCommand}"
                        Style="{StaticResource CartoonEffectAppBarButtonNoLabelStyle}"
                        Tag="Cartoon effect"
                        Visibility="Collapsed">
                    <ToolTipService.ToolTip>
                        <ToolTip x:Uid="CartoonizeAppBarButtonToolTip" />
                    </ToolTipService.ToolTip>
                </Button>
            </StackPanel>
        </Grid>
    </AppBar>
</local:HiloPage.BottomAppBar>

We set the Orientation property to Horizontal to arrange the children of the StackPanel from left to right. Set Orientation to Vertical to arrange children from top to bottom.

ListView

The ListView control displays data vertically. One way that we use this control is to display photos vertically on the image view page when the app is in snapped view.

Here's the XAML for the ListView.

ImageView.xaml

<ListView x:Name="SnappedPhotosFilmStripListView"
          Grid.Row="1"
          AutomationProperties.AutomationId="SnappedPhotosFilmStripListView"
          Margin="0,-10,0,0"
          Padding="10,0,0,60"
          ItemsSource="{Binding Photos}" 
          IsItemClickEnabled="False"
          ItemContainerStyle="{StaticResource HiloListViewItemStyle}"
          SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
          SelectionMode="Single"
          Visibility="Collapsed">
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Vertical" />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
    <ListView.ItemTemplate>
        <DataTemplate>
            <Border>
                <Image Source="{Binding Path=Thumbnail}" 
                       Stretch="UniformToFill" />
            </Border>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

This ListView uses HiloListViewItemStyle, which is defined in ApplicationStyles.xaml, to customize the appearance of the ListViewItem instances that are displayed in the ListView. Each ListViewItem is an Image control, wrapped in a Border control, and is defined in the DataTemplate of the ListView. In order to support UI virtualization, a VirtualizingStackPanel is used to display the ListViewItem instances, and is defined in the ItemsPanelTemplate of the ListView. For more information about UI virtualization, see UI virtualization for working with large data sets in this guide.

When the app enters snapped view, we hide the photo grid that contains the Image control, and show the photos as a vertical film strip.

ImageView.xaml

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PhotoGrid" 
                               Storyboard.TargetProperty="Visibility">
    <DiscreteObjectKeyFrame KeyTime="0" 
                            Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SnappedPhotosFilmStripListView" 
                               Storyboard.TargetProperty="Visibility">
    <DiscreteObjectKeyFrame KeyTime="0" 
                            Value="Visible"/>
</ObjectAnimationUsingKeyFrames>

We felt that the vertical layout that ListView provides made it a natural way to display groups of photos in snapped view. (ListView resembles GridView—they both inherit ListViewBase. The main difference is how they define their layout.)

For more info about ListView, see Adding ListView and GridView controls, XAML ListView and GridView essentials sample, and XAML ListView and GridView customizing interactivity sample.

SemanticZoom

The SemanticZoom control lets the user zoom between two views of a collection of items. For more info about how we use this control to help navigate between large sets of pictures, see Pinch and stretch to zoom in this guide.

Canvas and ContentControl

The Canvas control supports absolute positioning of child elements relative to the top left corner of the canvas. We used Canvas to control the position of the rectangle that you adjust to crop a photo.

Tip   Use a dynamic layout control, such as a Grid, when you don't need absolute positioning. A dynamic layout adapts automatically to different screen resolutions and orientations. Fixed layout controls, such as Canvas enable greater control, but you must manually update the layout when the orientation or page layout changes.

 

Here's the XAML for the Canvas.

CropImageView.xaml

<Canvas x:Name="CropCanvas" HorizontalAlignment="Left" VerticalAlignment="Top">
    <ContentControl x:Name="CropOverlay"
                    Canvas.Left="{Binding CropOverlayLeft}"
                    Canvas.Top="{Binding CropOverlayTop}"
                    Height="{Binding CropOverlayHeight}"
                    MinHeight="100"
                    MinWidth="100"
                    Template="{StaticResource HiloCroppingOverlayTemplate}"
                    Visibility="{Binding Path=IsCropOverlayVisible, Converter={StaticResource BoolToVisConverter}}"
                    Width="{Binding CropOverlayWidth}">
        <Grid Background="Transparent"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch"
              Tapped="OnCropRectangleTapped">
        </Grid>
    </ContentControl>
</Canvas>

Here's what the Hilo crop UX looks like.

The Canvas control contains one child element—a ContentControl that implements the draggable points that enable the user to position the bounds of the crop rectangle. A ContentControl represents a control with a single piece of content. Button and other standard controls inherit from ContentControl. We use ContentControl to define custom content. The ControlTemplate, HiloCroppingOverlayTemplate, is defined in the Resources section of CropImageView.xaml. This ControlTemplate defines one Thumb element for each of the 8 draggable points. HiloCroppingOverlayThumbStyle defines the color, shape, and other style elements for each point, and is defined in ApplicationStyles.xaml.

CropImageView.xaml

<ControlTemplate x:Key="HiloCroppingOverlayTemplate" TargetType="ContentControl">
    <Grid>
        <Thumb Canvas.ZIndex="1"
               DragDelta="OnThumbDragDelta"
               HorizontalAlignment="Stretch" 
               Style="{StaticResource HiloCroppingOverlayThumbStyle}"
               VerticalAlignment="Top" />
        <Thumb Canvas.ZIndex="1"
               DragDelta="OnThumbDragDelta" 
               HorizontalAlignment="Left"
               Style="{StaticResource HiloCroppingOverlayThumbStyle}"
               VerticalAlignment="Stretch" />
        <Thumb Canvas.ZIndex="1"
               DragDelta="OnThumbDragDelta"
               HorizontalAlignment="Right"
               Style="{StaticResource HiloCroppingOverlayThumbStyle}"
               VerticalAlignment="Stretch" />
        <Thumb Canvas.ZIndex="1"
               DragDelta="OnThumbDragDelta"
               HorizontalAlignment="Stretch"
               Style="{StaticResource HiloCroppingOverlayThumbStyle}"
               VerticalAlignment="Bottom" />
        <Thumb Canvas.ZIndex="1"
               DragDelta="OnThumbDragDelta"  
               HorizontalAlignment="Left"
               Style="{StaticResource HiloCroppingOverlayThumbStyle}"
               VerticalAlignment="Top" />
        <Thumb Canvas.ZIndex="1"
               DragDelta="OnThumbDragDelta" 
               HorizontalAlignment="Right"
               Style="{StaticResource HiloCroppingOverlayThumbStyle}"
               VerticalAlignment="Top" />
        <Thumb Canvas.ZIndex="1"
               DragDelta="OnThumbDragDelta" 
               HorizontalAlignment="Left"
               Style="{StaticResource HiloCroppingOverlayThumbStyle}"
               VerticalAlignment="Bottom" />
        <Thumb Canvas.ZIndex="1"
               DragDelta="OnThumbDragDelta" 
               HorizontalAlignment="Right"
               Style="{StaticResource HiloCroppingOverlayThumbStyle}"
               VerticalAlignment="Bottom" />
        <ContentPresenter Content="{TemplateBinding ContentControl.Content}" />
    </Grid>
</ControlTemplate>

For more info about the crop UX, see Using touch in this guide.

We used the Popup control to display additional info about a picture when the user presses and holds the current picture. For more info on how we used this control, see Press and hold to learn in this guide.

[Top]

Styling controls

Hilo'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 in a page's XAML file, in the App.xaml file, or in a separate resource dictionary. A resource dictionary can be shared across apps, and more than one resource dictionary can be merged in a single app. For more information, 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 often helps to avoid having to write custom controls. For more information, see Quickstart: control templates.

[Top]

UI virtualization for working with large data sets

Because Hilo is a photo app, we needed to consider how to most efficiently load and display the user's pictures. We understood that the user might have hundreds of pictures for any given month, or just a few, and we wanted to enable a great UX for either case.

We also wanted to keep the amount of memory the app consumes to a minimum. The chances of a suspended or inactive app being terminated rises with the amount of memory it uses.

Because only a subset of photos are displayed at the same time, we make use of UI virtualization. UI virtualization enables controls that derive from ItemsControl (that is, controls that can be used to present a collection of items) to only load into memory those UI elements that are near the viewport, or visible region of the control. As the user scrolls through the list, elements that were previously near the viewport are unloaded from memory and new elements are loaded.

Controls that derive from ItemsControl, such as ListView and GridView, perform UI virtualization by default. XAML generates the UI for the item and holds it in memory when the item is close to being visible on screen. When the item is no longer being displayed, the control reuses that memory for another item that is close to being displayed.

If you restyle an ItemsControl to use a panel other than its default panel, the control continues to support UI virtualization as long as it uses a virtualizing panel. Standard virtualizing panels include WrapGrid and VirtualizingStackPanel. Using standard non-virtualizing panels, which include VariableSizedWrapGrid and StackPanel, disables UI virtualization for that control.

Note  UI virtualization is not supported for grouped data. Therefore, in Hilo, we limited the size of groups to reduce the memory footprint of the app.

 

Tip  You can also use Semantic Zoom to more efficiently work with large data sets. For more info about how we use Semantic Zoom in Hilo, see Pinch and stretch to zoom.

 

For more info about working with large data sets, see Using virtualization with a list or grid and Load, store, and display large sets of data efficiently. Read Tuning performance to learn about other performance considerations we made in Hilo.

[Top]

Overriding built-in controls

On the hub page, we wanted the first picture to take the dimensions of four thumbnails with the remaining thumbnails being the normal width and height.

It can be challenging to define this kind of layout by using styles alone (in other words, to detect the first item in a collection and lay it out correctly). Instead, we defined the VariableGridView class, which derives from GridView.

VariableGridView.h

[Windows::Foundation::Metadata::WebHostHidden]
public ref class VariableGridView sealed : public Windows::UI::Xaml::Controls::GridView
{
protected:
    virtual void PrepareContainerForItemOverride(Windows::UI::Xaml::DependencyObject^ element, Platform::Object^ item) override;
};

We overrode the PrepareContainerForItemOverride method to enable the first image to span multiple rows and columns.

VariableGridView.cpp

void VariableGridView::PrepareContainerForItemOverride(DependencyObject^ element, Object^ item)
{
    auto model = dynamic_cast<IResizable^>(item);

    if (model != nullptr)
    {
        element->SetValue(VariableSizedWrapGrid::ColumnSpanProperty, model->ColumnSpan);
        element->SetValue(VariableSizedWrapGrid::RowSpanProperty, model->RowSpan);
    }

    GridView::PrepareContainerForItemOverride(element, item);
}

We define the IResizable interface to allow implementors to define the row and column spans in the parent container.

IResizable.h

public interface class IResizable
{
    property int ColumnSpan 
    {
        int get();
        void set(int value);
    }

    property int RowSpan
    {
        int get();
        void set(int value);
    }
};

The Photo class implements both IPhoto and IResizable. The constructor sets the default row and column span values to 1.

Photo.h

[Windows::UI::Xaml::Data::Bindable]
[Windows::Foundation::Metadata::WebHostHidden]
public ref class Photo sealed : public IResizable, public IPhoto, public Windows::UI::Xaml::Data::INotifyPropertyChanged

The HubPhotoGroup::QueryPhotosAsync method, which loads photos for the hub page, sets the column and row span values to 2 for the first photo in the collection.

HubPhotoGroup.cpp

bool firstPhoto = true;
for (auto photo : photos)
{
    if (firstPhoto)
    {
        IResizable^ resizable = dynamic_cast<IResizable^>(photo);
        if (nullptr != resizable)
        {
            resizable->ColumnSpan = 2;
            resizable->RowSpan = 2;
        }
        firstPhoto = false;
    }
    m_photos->Append(photo);
}

The first photo in the hub view now occupies two rows and two columns.

Note  In this solution, our model has control over the view and its layout. Although this violates the MVVM pattern (for more info, see Using the MVVM pattern), we tried to abstract this solution in such a way that it remains flexible. Because this solution targets a fairly narrow problem, we found the tradeoff between getting the app to work as we wanted and introducing some coupling between the view and the model, acceptable.

 

[Top]

Touch and gestures

The XAML runtime provides built-in support for touch. Because the XAML runtime uses a common event system for many user interactions, you get automatic support for mouse, pen, and other pointer-based interactions.

Tip  Design your app for the touch experience, and mouse and pen support come for free.

 

For more info about how we used touch in Hilo, see Using touch.

[Top]

Testing controls

When you test your app, ensure that each control behaves as you expect in different configurations and orientations. When we used a control to add a new feature to the app, we ensured that the control behaved correctly in snap and fill views and under both landscape and portrait orientations.

If your monitor isn't touch-enabled, you can use the simulator to emulate pinch, zoom, rotation, and other gestures. You can also simulate geolocation and work with different screen resolutions. For more info, see Running Windows Store apps in the simulator.

You can test your app on a computer that doesn't have Visual Studio but has hardware you need to test. For more info, see Running Windows Store apps on a remote machine.

For more info on how we tested Hilo, see Testing apps.

[Top]