Getting the most out of your pixels - adapting to view state changes

In Windows 8, your apps run on a variety of screen sizes and under various view states. A user might have your app snapped to the side of a 25-inch desktop monitor, or fill the whole screen of a 10-inch widescreen tablet. In each case, you want your app to take full advantage of the available space. In this post, I show you how you can track the current size and view state of your app in code, and give you tips on how to write your app in the Windows 8 Consumer Preview to handle screen size and view state changes.

At //build/ we gave you some ideas on how to design your apps for different screen scenarios – see, for example, the XAML talk or the HTML talk. Recently on the Building Windows 8 blog we also shared some of our research and design thoughts on screen scaling. Often you can use pure markup to adapt to changing screen sizes without having to write explicit code. But there are times when you need to keep track of what view state your app is in – that is, whether your app is in portrait, full-screen, filled, or snapped mode – and write code to react accordingly. For example, if you’re using an HTML ListView to display items, you might want to use a GridLayout when in Full-screen mode, but use ListLayout when in Snapped mode, as in this figure. For XAML, you might want to similarly switch between a GridView control and a ListView control. To figure out how to do this, let’s start by looking at how to detect resize and view state changes in code.

The full view state of a weather app shows 3 rows of 3 tiles each, arranged in a grid; the snapped view screen on right shows the same tiles arranged in a single column of tiles

The full view state screen on the left has a Grid layout (HTML) or GridView control (XAML):
the snapped view state screen on the right has a List layout (HTML) or ListView control (XAML).

The basics of resize and view state changes

The basic pattern for handling screen resize and view state changes is the same for Windows 8 apps written in XAML and in HTML: simply attach callback functions to the appropriate events that the respective frameworks provide, and query for any additional necessary info.

For example, in JavaScript, you can create a handler for the basic window resize event, which is a simple callback function, as in the next code example. You can use this event to detect when the display area for an app has been resized, and find the new dimensions of the app area.

JavaScript

 function handleResize(eventArgs) {
    var appWidth = eventArgs.view.outerWidth;
    var appHeight = eventArgs.view.outerHeight;
    ...
}

window.addEventListener("resize", handleResize);

Similarly, in XAML, you can use the SizeChanged event on the current window to set up an event handler:

C#

 private void OnWindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
    double AppWidth = e.Size.Width;
    double AppHeight = e.Size.Height;
}

Window.Current.SizeChanged += OnWindowSizeChanged;

When programming your Windows 8 apps, you can also use the WinRT to directly query the current view state. The WinRT provides an enumeration value that tells you the current view state of an app.

JavaScript

 var currentViewState = Windows.UI.ViewManagement.ApplicationView.value;

C#

 var CurrentViewState = Windows.UI.ViewManagement.ApplicationView.Value;

Windows 8 apps written in JavaScript also support the msMatchMedia function, which operates through the DOM window object, and can be useful when you have a condition with multiple media queries, such as the combination of full-screen landscape and a screen width of 1600 pixels:

JavaScript

 var isSnapped = window.msMatchMedia(“(-ms-view-state:fullscreen-landscape) and 
(min-width: 1600px”).matches;

JavaScript

 function handleSnappedMode(mql) {
if (mql.matches) {
        ...
        }
}
window.msMatchMedia(“(-ms-view-state:fullscreen-landscape) and (min-width: 
1600px”).addListener(handleSnappedMode);

Resize and view state in consumer preview

Now that we reviewed the basics of how to access view state and screen size info, let’s discuss how to deploy this functionality to deal with screen resizing. In the Windows 8 Consumer Preview, the order of resize events and view state change events is deterministic, so that view state events (and associated callback functions) always occur before resize events. Because of this, you must always wait for the resize event to fire before accessing the current view state. This gives you an easy and uniform approach to handle size and view state changes in your code. By waiting for the resize events to fire (and waiting for any accompanying callback functions to be invoked) you can be sure that the info returned for current display area and view state are in sync.

I also recommend that you use resize events to trigger code that handles layout changes, because there are some screen resize events (e.g. changing the monitor resolution, or a remote connection into a PC) that don’t cause a change in view state. In addition, it’s good programming practice to make sure that the code that handles layout changes is in one place, rather than having different code be invoked for different screen change events.

For example, let’s say you want to write an app that downloads background images of different sizes depending on the current screen resolution. In snapped mode, you might want to download a small image to tile in the background, and in filled mode, you might want a standard 4:3 aspect ratio image, or a 16:9 aspect ratio image in fullscreen-landscape mode. In addition, depending on the height of the screen, you might want to select different image resolutions for each aspect ratio.

Following this guidance, we set a callback function based on a resize event, and, within this callback function, use a WinRT API to query for the current view state, as here:

JavaScript

 function handleResize(eventArgs) {
    var currentViewState = Windows.UI.ViewManagement.ApplicationView.value;
    var appHeight = eventArgs.view.outerHeight;
    var appWidth = eventArgs.view.outerWidth;

    // downloadImage requires accurate view state and app size!
    downloadImage(currentViewState, appHeight, appWidth);
}

window.addEventListener("resize", handleResize);

 

C#

 private void OnWindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
    var CurrentViewState = Windows.UI.ViewManagement.ApplicationView.Value;
    double AppWidth = e.Size.Width;
    double AppHeight = e.Size.Height;

    // DownloadImage requires accurate view state and app size!
    DownloadImage(CurrentViewState, AppHeight, AppWidth);
}

Window.Current.SizeChanged += OnWindowSizeChanged;

In contrast, if we were to query the app display area size in a callback function triggered by a view state change, we would get the correct view state info, but the app display area size wouldn’t be updated yet. So, under those circumstances, if our app transitioned from snapped to fullscreen-landscape, AppWidth might be 320px, and view state might be fullscreen-landscape. If we pass these values to DownloadImage, we download the wrong image size!

The guidance on using callbacks triggered by resize events applies to other scenarios too. For example, in JavaScript, if you wanted to change the layout of a ListView control from Grid to List depending on view state, then you could set a callback to occur when the view state changes. But setting the ListView layout before the screen had resized would cause two layout passes for the ListView control – one before the screen is resized, and one after. (The WinJS ListView control automatically handles resize events, but not view state change events.) Unlike the DownloadImage example we saw earlier, the user might not be able to directly see a difference, but the extra time the app takes for layout would make it slower and less responsive. So, it’s better to wait for a callback due to screen resize before you query the current view state and set the ListView layout accordingly:

JavaScript

 function handleResize(eventArgs) {
    var isSnapped = (Windows.UI.ViewManagement.ApplicationView.value === 
Windows.UI.ViewManagement.ApplicationViewState.snapped);
    listView.layout = isSnapped ? new WinJS.UI.ListLayout() : new WinJS.UI.GridLayout();
}

window.addEventListener("resize", handleResize);

For XAML, to switch between these different states use the VisualStateManager (VSM) APIs to define what XAML elements should be displayed for individual views. If you are using the Visual Studio 2011 Beta tools, you can see an example of these APIs defined in the project templates for the Grid App template:

XAML

 <VisualStateManager.VisualStateGroups>
    <!-- Visual states reflect the application's view state -->
    <VisualStateGroup>
        <VisualState x:Name="FullScreenLandscape"/>
        <VisualState x:Name="Filled"/>

        <!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
        <VisualState x:Name="FullScreenPortrait">
            <!-- definition of what UI elements should display or change would go here in Storyboard 
elements -->
        </VisualState>

        <!-- The back button and title have different styles when snapped, and the list representation 
is substituted for the grid displayed in all other view states -->
        <VisualState x:Name="Snapped">
            <!-- definition of what UI elements should display or change would go here in Storyboard 
elements -->
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Notice the VisualState groups defined for the different view states. Your code can then change them when the state has changed in your SizeChanged event:

C#

 private void OnWindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
    // get the view state after the size has changed
    string CurrentViewState = Windows.UI.ViewManagement.ApplicationView.Value.ToString();
 
    // using VisualStateManager make sure we navigate to the correct state definition
    VisualStateManager.GoToState(this, CurrentViewState, false);
}

As you can see from this example, using VSM combined with the SizeChanged and ApplicationView APIs provides a flexible mechanism to adapt to screen layout changes.

Conclusion

In this post we looked at how you can detect screen resize events and view state changes in code. For Consumer Preview, I recommend that you listen to screen size event changes before querying the WinRT for view state info just like I did here. If you're following this guidance, your code will see the correct current screen size and view state info every time the app is resized. For further examples, take a look at our Windows 8 SDK sample. We also invite your further thoughts and feedback on this topic!

--Chris Jones, Program Manager, Windows

With special thanks to Tim Heuer, who helped craft this blog post.