In a previous posting I demonstrated how you can create a powerful display of grouped data using the GridView, which is typically used to fill the screen. Every Windows 8 application must also support SnapView, however, in which your application is allocated 320x768 pixels – that is, your application is squeezed into a relatively thin sliver on the left or right of the screen.
GridView leaves much to be desired in SnapView and the common solution is to hide your GridView and to display the same data in a ListView, which works well in those dimensions as it scrolls vertically.
(Don’t be confused: GridView and ListView are controls, SnapView is a state of the application)
In the image to the right (click on the image to expand), Visual Studio is shown in the Fill state and our application is shown in the SnapView. In the SnapView we display the data in a ListView and we make the title smaller.
There are a few steps to implementing an application that supports SnapView well with these controls. What you want is:
First, you need to know which view you are in, and you need to respond to a change in views. Here are the steps:
To get started, create a new Blank App application. Recreate the previous application, or download the previous application here.
At this point you should have an application that displays a GridView. Put that application into snapped view (Windows – dot) and notice that what you get is a thin slice of a GridView. The GridView itself is unchanged.
We want to add a title to the page, so update the outermost grid with two rows and place the title TextBlock in the top row,
<
Grid
Background
=
"{StaticResource ApplicationPageBackgroundThemeBrush}"
>
<
Grid.RowDefinitions
>
<
RowDefinition
Height
=
"50"
/>
<
RowDefinition
Height
=
"*"
/>
</
Grid.RowDefinitions
>
<
TextBlock
x:Name
=
"Title"
Text
=
"Cities"
FontSize
=
"42"
Grid.Row
=
"0"
/>
Run the application and notice that your GridView scrolls underneath the title, which is just what we want.
When we are in SnappedView we want to display this same data in a ListView. Since ListView and GridView share all the same methods, events and properties, you can copy and paste the GridView so that you have two, one above the other. Change the second GridView to a ListView and fix up anyplace in that ListView that says GridView (e.g., change the ItemContainerStyle TargetType from GridViewItem to ListViewItem). Name the GridView FullView and name the ListView SnappedView (this is arbitrary and you can name them anything you want as long as you match up the names with the storyboard targets).
Here’s the complete ListView,
<
ListView
x:Name
=
"SnappedView"
Grid.Row
=
"1"
ItemsSource
=
"{Binding Source={StaticResource cvs}}"
Visibility
=
"Collapsed"
>
<
ItemsControl.ItemsPanel
>
<
ItemsPanelTemplate
>
<
StackPanel
Orientation
=
"Vertical"
/>
</
ItemsPanelTemplate
>
</
ItemsControl.ItemsPanel
>
<
ItemsControl.ItemContainerStyle
>
<
Style
TargetType
=
"ListViewItem"
>
<
Setter
Property
=
"HorizontalContentAlignment"
Value
=
"Left"
/>
<
Setter
Property
=
"Padding"
Value
=
"5"
/>
</
Style
>
</
ItemsControl.ItemContainerStyle
>
<
ItemsControl.GroupStyle
>
<
GroupStyle
>
<
GroupStyle.HeaderTemplate
>
<
DataTemplate
>
<
TextBlock
Text
=
"{Binding Key}"
FontSize
=
"26.67"
/>
</
DataTemplate
>
</
GroupStyle.HeaderTemplate
>
<
GroupStyle.Panel
>
<
ItemsPanelTemplate
>
<
VariableSizedWrapGrid
Orientation
=
"Vertical"
ItemWidth
=
"220"
/>
</
ItemsPanelTemplate
>
</
GroupStyle.Panel
>
</
GroupStyle
>
</
ItemsControl.GroupStyle
>
</
ListView
>
Notice that it is nearly identical to the GridView and that, most important, its ItemsSource is bound to the same CollectionViewSource as the GridView. This is very important as it will enable both controls to point to the same data. In fact, by using the CollectionViewSource, if we select an item in the GridView and then switch to SnapView, the same item will be selected.
You now need to add the ViewStateManager markup to move into and out of the SnapView. Put this code below the GridView and ListView but within the outermost Grid. (Thanks to Michael Crump for pointing out the importance of where you place the VSM markup)
You begin by declaring the Visual State Groups and within that the ApplicationViewStates Group. Within the ApplicationViewStatesGroup you’ll declare four Visual States:
They must have these names. The first three will be empty but the fourth, Snapped, will have a Storyboard to implement the changes you want to have occur when you move into Snapped view.
<
VisualStateManager.VisualStateGroups
>
<
VisualStateGroup
x:Name
=
"ApplicationViewStates"
>
<
VisualState
x:Name
=
"FullScreenLandscape"
/>
<
VisualState
x:Name
=
"Filled"
/>
<
VisualState
x:Name
=
"FullScreenPortrait"
/>
<
VisualState
x:Name
=
"Snapped"
>
<
Storyboard
>
</
Storyboard
>
</
VisualState
>
</
VisualStateGroup
>
</
VisualStateManager.VisualStateGroups
>
Inside the Storyboard we’ll add markup to change the size of the Title from its initial size of 42 to 20,
<
ObjectAnimationUsingKeyFrames
Storyboard.TargetName
=
"Title"
Storyboard.TargetProperty
=
"FontSize"
>
<
DiscreteObjectKeyFrame
KeyTime
=
"0"
Value
=
"20"
/>
</
ObjectAnimationUsingKeyFrames
>
Notice that, while we are using animation syntax, there really is no animation; the change happens instantly (KeyTime=”0”). We’ll add two other AnimationKeyFrames, one to collapse the GridView and one to show the ListView,
<
ObjectAnimationUsingKeyFrames
Storyboard.TargetName
=
"FullView"
Storyboard.TargetProperty
=
"Visibility"
>
<
DiscreteObjectKeyFrame
KeyTime
=
"0"
Value
=
"Collapsed"
/>
</
ObjectAnimationUsingKeyFrames
>
<
ObjectAnimationUsingKeyFrames
Storyboard.TargetName
=
"SnappedView"
Storyboard.TargetProperty
=
"Visibility"
>
<
DiscreteObjectKeyFrame
KeyTime
=
"0"
Value
=
"Visible"
/>
</
ObjectAnimationUsingKeyFrames
>
All of these changes happen, essentially, instantaneously.
We’re all set, when the view state changes to snapped we’ll diminish the size of the title and we’ll hide the GridView and show the ListView. When we move out of SnappedView we’ll return to the default settings (since there is no storyboard for those states).
But how do we get the view state to change?
Many of the templates used to create new applications create a class, LayoutAwarePage, in the Common folder. This page is somewhat complex, and we’ll create our own simpler version. Create a new class named LayoutAwarePage in the Common folder. The new class will inherit from Page and will implement two event handlers: one for when the window changes size and one for when the page is loaded,
public class LayoutAwarePage : Page
{
public LayoutAwarePage()
{
Window.Current.SizeChanged += WindowSizeChanged;
Loaded += LayoutAwarePage_Loaded;
}
Both event handlers call a helper method: SetVisualState,
private void LayoutAwarePage_Loaded( object sender, RoutedEventArgs e )
{
SetVisualState();
}
private void WindowSizeChanged( object sender, WindowSizeChangedEventArgs e )
{
SetVisualState();
}
The job of SetVisualState is to obtain the current visual state and to ask the VisualStateManager to go to the state with that name. This forces the change in ViewState that is picked up by the page.
public void SetVisualState()
{
string viewValue= ApplicationView.Value.ToString();
VisualStateManager.GoToState( this, viewValue, false );
}
using GridView.Common;
public sealed partial class MainPage : LayoutAwarePage
In addition, add a namespace to MainPage.xaml,
xmlns:common="using:GridView.Common"
and change the type of the page from Page to LayoutAwarePage
<common:LayoutAwarePage x:Class="GridView.MainPage"
<
common:LayoutAwarePage.Resources
>
<
CollectionViewSource
x:Name
=
"cvs"
IsSourceGrouped
=
"True"
ItemsPath
=
"Items"
/>
</
common:LayoutAwarePage.Resources
>
Jesse Liberty has three decades of experience writing and delivering software projects. He is the author of 2 dozen books and has been a Distinguished Software Engineer for AT&T and a VP for Information Services for Citibank and a Software Architect for PBS. You can read more on his personal blog or follow him on twitter