Responsive Design - Programming Windows Store Apps with C# (2014)

Programming Windows Store Apps with C# (2014)

Chapter 12. Responsive Design

Windows Store apps allow you to run several applications side by side in the foreground, which allows you to multitask without having to switch between fullscreen applications. This feature appears popular with early adopters of Windows 8, and this is a trend we predict will continue. If you were planning a weekend trip to London, you could have a map application, weather application, and a travel application open to check nearby travel destinations. There are many cases in which being able to run applications side by side is helpful. Figure 12-1 shows the three applications: Map, Weather, and Travel.

Map, Weather, and Travel side by side

Figure 12-1. Map, Weather, and Travel side by side

The three applications have adjustable dividers between them to give us control of how much screen real estate they get. The default minimum width of an application is 500 pixels, but there is an even smaller size that was previously referred to as the “Snapped” mode. The smallest size is 320 pixels and applications have to opt in for that.

With two applications that support the smallest size, we let the third application fill out the rest of the screen (illustrated in Figure 12-2). This is really the trick with the view—we need to build a UI that can either adapt from being in the responsive view or the smallest view, or we have to provide a secondary UI that will be used for the smallest view. (The intent here is that every app has to be useful in either mode. Microsoft is keen to ensure that it isn’t the case that one mode is “more special” than the other.)

On both sides of the screen, we have applications that support the 320-pixel width

Figure 12-2. On both sides of the screen, we have applications that support the 320-pixel width

This is actually much easier to do with a proper MVVM model like the one that we have taken the time to build thus far. From time to time, we’ll have to duplicate sets of controls for the smallest versus the responsive size view, but that’s fine because all we have to do is configure the duplicated controls with the same bindings. The view model is sufficiently abstracted from the view that it doesn’t care whether we’re in either mode, nor does it care how many bindings each control has.

You need to have a display that has a width greater than or equal to 1024 pixels to be able to run several applications at the same time.

NOTE

If you are developing on a machine with a lower screen resolution than that, use the simulator because it allows you to simulate resolutions higher than your base hardware.

Charms are enabled for the application you are using at the moment, which is indicated by a small white line in the middle of the divider closest to the application being used.

The general rule of snapped view is that the app should actually be functional when in that mode. It doesn’t have to be fully functional—not every function has to be carried over—but it should not be a struggle for the user to use a snapped app. You may also encounter development requirements that mean that the smallest view makes no sense at all. I’m a fan of it. It’s well worth implementing, and it’s a great differentiator from the iPad.

Updating the Grid View

By using a fluid layout that lets the application adapt to various screen sizes, we already have great support for the default minimum width—500 pixels. We do, however, want to support the smallest size as well, which might make the user bring our application to the foreground more often and interact with it. Support for the 320-pixel width is set in the app manifest under minimum width.

The VisualStateManager

All of this work hinges on the VisualStateManager class within WinRT. The purpose of this class is to handle state changes, one example of which is the transition from the “Default” state (which includes scaling down to 500 pixels in width) to the “Small” state (320 pixels wide).

The VisualStateManager class has been around for a while—even before WinRT—and is worth learning well. The manager class handles states, and the states allow us to get a specific appearance of a control when it is in a specific state.

For the register page, we could define some visual states like the following:

<VisualStateManager.VisualStateGroups>

<VisualStateGroup>

<VisualState x:Name="Default"/>

<VisualState x:Name="Small">

<Storyboard>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"backButton" Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value="

{StaticResource SnappedBackButtonStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"pageTitle" Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value="

{StaticResource SnappedPageHeaderTextStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"helpText" Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"registrationForm" Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0"

Value="Collapsed"/>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>

</VisualStateGroup>

</VisualStateManager.VisualStateGroups>

Let’s take a look at what is going on here. First of all, we define the VisualStateManager.VisualStateGroups element inside the element where we want to add the changes. When using visual states for managing the resizing of the application, it will most often be inside the outermost grid. Inside VisualStateManager.VisualStateGroups, we create a group of states, and we give them appropriate names so we can access them from our code. We then create a storyboard, which is a container timeline that lets us animate dependency properties from one state to another. This is similar to how styles work: you get a list of directives and the XAML subsystem walks through each one and affects the dependency properties. In the storyboard directives that we have in the preceding code snippet, the first two change the styles of the button and caption to make them smaller. The third one changes the visibility of a TextBox from collapsed to visible, and the fourth one changes the visibility of the registration form from visible to collapsed. On the register and logon page, we’ll just tell the user that this view doesn’t work in the smallest mode, whereas the Reports and Report page will support the 320 pixel size. Notice how we don’t define any storyboards for the default state. We don’t have to reverse the actions done in the other states.

When the Reports page was created, we used a grid page template. Whenever we use this template, Visual Studio creates a GridView control with the name itemsGridView and a ListView control with the name itemsListView. Both controls were bound to the same data source, however, the ListView one was hidden. Throughout the life of the app, we’ve been able to snap it from one side to the other. It’s just that it wouldn’t have worked properly because we hadn’t created working snap views. To make the logon page work properly in snapped view, we need to do two things. The ListView uses templates just like the GridView, and so we need a new template. If you recall, back in Chapter 4 we built MyGridView so we could eventually host commands for use with the MVVM approach—commands like ItemClickedCommand. Next we’ll buildMyListView so we can do the same to the list view.

Creating MyListView

The implementation of ItemClickedCommand on MyListView will be identical to the one we built on MyGridView in Chapter 8. Thus, I’ll just present the code without going through it in detail. Here it is:

public class MyListView : ListView

{

// as per the grid...

public static readonly DependencyProperty ItemClickedCommandProperty =

DependencyProperty.Register("ItemClickedCommand", typeof(ICommand),

typeof(MyListView),

new PropertyMetadata(null, (d, e) => ((MyListView)d).

ItemClickedCommand = (ICommand)e.NewValue));

public MyListView()

{

this.ItemClick += MyListView_ItemClick;

}

void MyListView_ItemClick(object sender, ItemClickEventArgs e)

{

if (this.ItemClickedCommand == null)

return;

// ok...

var clicked = e.ClickedItem;

if (this.ItemClickedCommand.CanExecute(clicked))

this.ItemClickedCommand.Execute(clicked);

}

public ICommand ItemClickedCommand

{

get { return (ICommand)GetValue(ItemClickedCommandProperty); }

set { SetValue(ItemClickedCommandProperty, value); }

}

}

Again, as per Chapter 8, now that we’ve replicated the ItemClickedCommand, we also need to replicate the template that we created for displaying report items in the grid. This new template will be used for showing those same report items, but in a list.

Here’s the template. Note how it uses the same bindings as the main report template. That’s the point of snapped view—all we do is change the declaration of the UI and it should all just work:

<!-- add to StandardStyles.xaml -->

<DataTemplate x:Key="ReportItem80SnappedItemTemplate">

<Grid Margin="6">

<Grid.ColumnDefinitions>

<ColumnDefinition Width="Auto"/>

<ColumnDefinition Width="*"/>

</Grid.ColumnDefinitions>

<Border Background="{StaticResource

ListViewItemPlaceholderBackgroundThemeBrush}" Width="60" Height="60">

<Image Source="{Binding ImageUri}" Stretch="UniformToFill"/>

</Border>

<StackPanel Grid.Column="1" Margin="10,0,0,0">

<TextBlock Text="{Binding Title}" Style="{StaticResource

ItemTextStyle}" MaxHeight="40"/>

<TextBlock Text="{Binding Description}" Style="{StaticResource

CaptionTextStyle}" TextWrapping="NoWrap"/>

</StackPanel>

</Grid>

</DataTemplate>

Finally, just a quick change to the list control. We need to change its type, change the template, and bind up the ItemClickedCommand. Note that we reuse the same command in the view-model. Again, we don’t need to change the view-model at all for this to work. Here’s the change:

<!-- Modify ReportsPage.xml -->

<local:MyListView

x:Name="itemListView"

AutomationProperties.AutomationId="ItemsListView"

AutomationProperties.Name="Items"

TabIndex="1"

Grid.Row="1"

Visibility="Collapsed"

Margin="0,-10,0,0"

Padding="10,0,0,60"

ItemsSource="{Binding Source={StaticResource itemsViewSource}}"

ItemTemplate="{StaticResource ReportItem80SnappedItemTemplate}"

IsItemClickEnabled="true"

ItemClickedCommand="{Binding SelectionCommand}"

/>

In the preceding code, Collapsed is given for the Visibility property to indicate that the element should not be displayed. We have to manually switch between the states when the window’s size changes, so before we add the states, let’s go ahead and create a responsive base page that inherits from StreetFooPage. Inside that class, we’ll listen for the SizeChanged event and use VisualStateManager.GoToState to switch state depending on the new screen size, like so:

public class ResponsiveStreetFooPage : StreetFooPage

{

public ResponsiveStreetFooPage()

{

SizeChanged += OnSizeChanged;

}

private const double SmallMode = 320;

private void OnSizeChanged(object sender, SizeChangedEventArgs e)

{

VisualStateManager.GoToState(this, e.NewSize.Width <= SmallMode ?

"Small" : "Default", true);

}

}

Then we simply let the views that we want to use states inherit from that class; for us that would be the ReportsPage, ReportPage, LogonPage, and RegisterPage.

Modifying the App Bar

The app bar as currently configured won’t work properly in snapped view on the Reports page. We need to adjust that. As mentioned, in snapped view, we can fit only three buttons on the app bar. One option is to hide a button by adding a transition to the state transition storyboard. In order to do this, we have to name the button by declaring an x:Name attribute on the control.

But when we first built this app bar in Chapter 4, we constructed it so that it had options on the left and right. This became irrelevant in Chapter 10 when we took the selection off and made a click on a report go to the report singleton page. If we strip out all the grid layout stuff and name the button that we want to get rid of the “show location” button, we get the following:

<!-- Modify markup in ReportsPage.xml -->

<Page.BottomAppBar>

<AppBar>

<StackPanel HorizontalAlignment="Right" Orientation="Horizontal"

Grid.Column="2">

<Button Style="{StaticResource NewAppBarButtonStyle}" Command=

"{Binding NewCommand}" />

<Button x:Name="appbarShowLocation" Style="{StaticResource

ShowLocationAppBarButtonStyle}" Command="{Binding ShowLocationCommand}" />

<Button Style="{StaticResource RefreshAppBarButtonStyle}"

Command="{Binding RefreshCommand}" />

<Button Style="{StaticResource LogoutAppBarButtonStyle}"

Command="{Binding LogoutCommand}" />

</StackPanel>

</AppBar>

</Page.BottomAppBar>

Now that the button has a name, we can address it within the storyboard and hide it when the storyboard is enacted. This “enacting” happens when the user tells Windows that she wants to move the app into snapped mode. Here’s the code:

<!-- Modify markup in ReportsPage.xml -->

<VisualState x:Name="Small">

<Storyboard>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"backButton" Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value="

{StaticResource SnappedBackButtonStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"pageTitle" Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value=

"{StaticResource SnappedPageHeaderTextStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"itemListView" Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"itemGridView" Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0"

Value="Collapsed"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"appbarShowLocation" Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0"

Value="Collapsed"/>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>

Run the app and open the app bar in snapped view on the Reports page and you’ll see something like Figure 12-3.

The app bar in snapped view

Figure 12-3. The app bar in snapped view

There may be cases where you can’t just reduce the number of buttons like this. In a later section, we’ll look at how to create a More button with a pop-up menu.

Updating Singleton Views

The next type of view that we’re going to look at is exemplified in the report singleton page. In this situation, we’re going to create an entirely new view. We’ll swap out the old view and replace it with the new view using the storyboard.

The first thing to address is how to best use the XAML designer. This will then let us lay out the new view with minimal guesswork. This is done by bringing up the Device view via the Design→Device menu. At the top of this view, you can click Windows OS Edge and then use the slider to set the minimum size. In Figure 12-4, you can see the left selection in play. The hatched portion of the device viewport shows the region occupied by the splitter and the filled view. The device window doesn’t know of our states and therefore cannot be used with them. In Blend, which comes with Visual Studio, you can select states and see how they look there and add any modifications that you want. We won’t cover the use of Blend in this book, but it is an excellent piece of software that helps tremendously where the Visual Studio designer is lacking.

Showing the application at 320 pixels wide to the left

Figure 12-4. Showing the application at 320 pixels wide to the left

What you can see in the image is what we want to accomplish in the smallest width. For the screenshot, I simply cheated by changing the visibility of the containers, which I will talk more about in the following section.

What I’m proposing that we do here is approach this in a standard way. A first step is to let the page inherit from the new base page we mentioned earlier as you can see in the image. We’ll assume that when we want to display a form in snapped view, we’ll contain that form within aStackPanel. We’ll also assume that the vertical extent of that StackPanel may go off the bottom edge of the screen. Thus we’ll contain the StackPanel within a ScrollViewer configured to support this. Both of these controls will have styles applied, which we’ll build in a moment. In terms of the actual view, because the view doesn’t have any logic behind it and because everything is driven with data binding, we can just copy and paste the controls that we already had into the new container structure. Here’s the change:

<!-- Modify markup in ReportPage.xaml -->

<ScrollViewer x:Name="containerSnapped" Style="{StaticResource

SnappedContainerScrollViewer}" Grid.Row="1">

<StackPanel Style="{StaticResource SnappedContainerStackPanel}">

<TextBlock Style="{StaticResource HeadingTextBlock}">Details

</TextBlock>

<Image Source="{Binding Item.ImageUri}" HorizontalAlignment=

"Left" Width="320" Height="240" Stretch="Uniform"

Margin="0,0,0,10"></Image>

<local:MarkupViewer Markup="{Binding Item.Description}">

</local:MarkupViewer>

<TextBlock Style="{StaticResource HeadingTextBlock}"

Padding="0,10,0,0">Map</TextBlock>

<local:MyMap Width="300" Height="300" ShowTraffic="true"

PushpinPoint="{Binding Item, Converter=

{StaticResource IMappablePointConverter}}"></local:MyMap>

</StackPanel>

</ScrollViewer>

Note that I’ve put an x:Name="containerSnapped" attribute on the ScrollViewer. This will be our convention for containers that are used in snapped mode. Similarly, on the ScrollViewer that we built in Chapter 10 to provide the panorama view, we’ll add anx:Name="containerFill" attribute. Here’s the change—I’ve presented the caption controls above the control by way of orientation:

<!-- Modify markup in ReportPage.xaml -->

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="Auto"/>

<ColumnDefinition Width="*"/>

</Grid.ColumnDefinitions>

<Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.

CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>

<TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding

Item.Title}" Style="{StaticResource PageHeaderTextStyle}"/>

</Grid>

<ScrollViewer x:Name="containerFill" Style="{StaticResource

HorizontalScrollViewerStyle}" Grid.Row="1">

<Grid>

Finally, we can change the storyboard to swap over the two containers:

<!-- Modify markup in ReportPage.xaml -->

<VisualState x:Name="Small">

<Storyboard>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"backButton" Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value=

"{StaticResource SnappedBackButtonStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"pageTitle" Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value=

"{StaticResource SnappedPageHeaderTextStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"containerSnapped" Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName=

"containerFill" Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0"

Value="Collapsed"/>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>

Run the app and you can snap the report singleton page as Figure 12-5 illustrates. What’s quite cool is being able to move the slider between the smallest, the medium, and the full views and seeing the view adjust itself.

The report singleton page in the smallest view

Figure 12-5. The report singleton page in the smallest view

Adding a More Button to the App Bar

Modifying EditReportPage so that it supports the smallest view operates in a similar fashion to that of ReportPage; it is basically just a repeat of the work we just did. For that reason, I won’t repeat it in these pages, but you’ll find the code download has it working properly.

What I will go through is how to modify the app bar. At the moment, if you snap the edit page the app bar looks a little like Figure 12-6—i.e., the Save button is missing. Really what’s happened is that it’s been obscured by the Capture Location button.

The edit page app bar with the Save button missing

Figure 12-6. The edit page app bar with the Save button missing

What we’re going to do is, when we go into the smallest view, we’ll hide the two buttons on the left and show a new button on the “right,” labeled “More.” I say “right” because it will appear this button is actually on the left of the view, but it’s part of the set designated “right.”

NOTE

Don’t be tempted to create smaller buttons on the app bar; they won’t be user-friendly.

When the More button is pressed it’ll display a pop up. This is done using the Windows.UI.Popups.PopupMenu class. But there’s a wrinkle—it’s easier to build a PopupMenu programmatically rather than doing it declaratively in XAML. And, even if you could declare it in XAML, when you show the pop up you have to give it a set of coordinates to use and that is much easier to manage programmatically.

To understand the problem, PopupMenu relies on you adding UICommand instances to a collection that it manages and then calling ShowAsync, passing in some coordinates.

Thus we have a dilemma; we’ve come so far without having to write traditional code-behind, do we really have to do it for this? There are two ways to go with this: we could create framework infrastructure within the view-model handler to expose out view-agnostic commands, and then create a special MoreAppBarButton control that understands how to use this infrastructure to create and show a PopupMenu. Or we could just code it up in the codebehind. I’m proposing doing it in the codebehind, for the simple reason that it’s view-specific code. The view-model doesn’t care how we’ve structured our app bar; that’s a presentation-specific thing.

Turning back to the app bar, what we want to do is restructure that so that we have a named panel on the left that we can hide, and a More button on the right that we can show. Here’s the code:

<!-- Modify markup in EditReportPage.xaml -->

<Page.BottomAppBar>

<AppBar IsSticky="true" IsOpen="true">

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="auto"/>

<ColumnDefinition Width="50*"/>

</Grid.ColumnDefinitions>

<StackPanel Orientation="Horizontal" x:Name="appbarPanelLeft">

<Button Style="{StaticResource TakePhotoAppBarButtonStyle}"

Command="{Binding TakePhotoCommand}" />

<Button Style="{StaticResource

CaptureLocationAppBarButtonStyle}" Command="{Binding CaptureLocationCommand}" />

</StackPanel>

<StackPanel HorizontalAlignment="Right" Orientation="Horizontal"

Grid.Column="1" Margin="0,1,5,-1">

<Button x:Name="appbarMore" Style="{StaticResource

MoreAppBarButtonStyle}" Visibility="Collapsed" Click="HandleMoreButton" />

<Button Style="{StaticResource SaveAppBarButtonStyle}"

Command="{Binding SaveCommand}" />

<Button Style="{StaticResource CancelAppBarButtonStyle}"

Command="{Binding CancelCommand}" />

</StackPanel>

</Grid>

</AppBar>

</Page.BottomAppBar>

Luckily, we are given a MoreAppBarButtonStyle by default. Notice that I’ve defined a Click attribute on the More button. This is to power the codebehind. In the editor view, double-click the More button and Visual Studio will create the handler for you. We’ll do that in a moment.

Before we code up the handler, here’s the change we need to make to the storyboard for transitioning into the smallest view. We hide the panel and show the button:

<VisualState x:Name="Small">

<Storyboard>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"

Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource

SnappedBackButtonStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="titleNew"

Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource

SnappedPageHeaderTextStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="titleEdit"

Storyboard.TargetProperty="Style">

<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource

SnappedPageHeaderTextStyle}"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="containerSnapped"

Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="containerFill"

Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="appbarPanelLeft"

Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>

</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="appbarMore"

Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>

I didn’t take you through the specifics of building the form as it should be obvious given the work we did previously, but in the page I have two forms: one for the default view and one for the smallest view. For consistency with ReportPage I’ve called one containerFill and the othercontainerSnapped.

NOTE

You’ll find that the code download shows fully implemented support for the smallest view size.

Previously, I mentioned that we need to create UICommand instances for use with PopupMenu. These take a display string and a delegate. We can just defer through to the commands on the view-model. Again, although we’re doing codebehind here we don’t want to break the separation of concerns provided by MVVM.

Here’s the code—although I need to point out at this point that the “Take Picture” command will actually fail, but at least we’ll be able to see the pop up:

// Add method to EditReportPage...

private async void HandleMoreButton(object sender, RoutedEventArgs e)

{

var popup = new PopupMenu();

popup.Commands.Add(new UICommand("Take Picture", (args) =>

this.ViewModel.TakePhotoCommand.Execute(null)));

popup.Commands.Add(new UICommand("Capture Location", (args) => this.ViewModel.

CaptureLocationCommand.Execute(null)));

// show...

await popup.ShowAsync(((FrameworkElement)sender).

GetPointForContextMenu());

}

The sender will come through as, obviously, the button. We’ll need to use this to get a point to show the context menu. GetPointForContextMenu is an extension method on FrameworkElement that we need to build.

I’ll present this without much comment as it’s a little specialized for this book. This code needs to be added to the FrameworkElementExtender class that we built in Chapter 4 and modified again in Chapter 7.

// Add method to FrameworkElementExtender...

internal static Point GetPointForContextMenu(this FrameworkElement

element)

{

GeneralTransform transform = element.TransformToVisual(null);

Point point = transform.TransformPoint(new Point());

return point;

}

Run the code and bring up the app bar on the edit report page while in snapped view. You’ll see something like Figure 12-7 when you press the More button.

The pop-up menu for the More button

Figure 12-7. The pop-up menu for the More button

As mentioned, “Take Picture” will crash. Select the option and you’ll get an exception message like this:

System.InvalidOperationException: A method was called at an unexpected time.

(Exception from HRESULT: 0x8000000E)

at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebugger

Notification(Task task)

at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()

at StreetFoo.Client.EditReportPageViewModel.<CaptureImageAsync>d__17.MoveNext

() in c:\BookCode\Chapter13\StreetFoo.Client\StreetFoo.Client\Model\Instances\

EditReportPageViewModel.cs:line 84

--- End of stack trace from previous location where exception was thrown ---

at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebugger

Notification(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.GetResult()

at StreetFoo.Client.EditReportPageViewModel.<<.ctor>b__0>d__4.MoveNext() in

c:\BookCode\Chapter13\StreetFoo.Client\StreetFoo.Client\Model\Instances\

EditReportPageViewModel.cs:line 26

What this error is trying to tell you is that you can’t take a photo when the application is in the smallest width.

We need to “unsnap” the view before we call the command. There are a few places where you can’t access APIs from within snapped views. These normally occur when you would end up in a situation where the user might be unclear as to which app owned the helper UI. (The file pickers are another example of this.) This is down to the modality of the Windows 8 application model because you don’t have a pop up to do the helper UI, the user can’t “see” which application the helper UI is related to.

As we can’t access this function, we’ll just display a message telling the user this. Here’s the change:

// Modify method in EditReportPage...

private async void HandleMoreButton(object sender, RoutedEventArgs e)

{

var popup = new PopupMenu();

popup.Commands.Add(new UICommand("Take Picture", (args) => {

await this.ShowAlertAsync("Make the app full screen to use

the camera.");

}));

popup.Commands.Add(new UICommand("Capture Location", (args) =>

this.ViewModel.CaptureLocationCommand.Execute(null)));

// show...

await popup.ShowAsync(((FrameworkElement)sender).GetPointFor

ContextMenu());

}

Run the same operation again and the view will unsnap and you’ll be able to take a photo.

Handling Views That Don’t Support 320-Pixel Width

From time to time you’ll come across views that you don’t want to have in the smallest width. That’s fine—the application only has to remain mostly or “usefully” functional, not completely functional. In this situation it’s helpful to put up a view that explains that the user is better off adjusting the width of the application to get the functionality (see Figure 12-8). This is what we did at the very beginning of this chapter when we talked about the visual states.

The view presented when a page doesn’t support the smallest width

Figure 12-8. The view presented when a page doesn’t support the smallest width

We did this simply by adding a TextBlock with the text and set the visibility to collapsed:

<TextBlock x:Name="helpText" Grid.Row="1" Grid.Column="1" Visibility="Collapsed"

Text="Logon cannot be used in this mode"/>

We then set it back to visible in the “Small” state:

<VisualState x:Name="Small">

<Storyboard>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="helpText"

Storyboard.TargetProperty="Visibility">

<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>

</ObjectAnimationUsingKeyFrames>

</Storyboard>

</VisualState>