×
Namespaces

Variants
Actions
Revision as of 21:31, 9 March 2014 by kiran10182 (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Advanced Techniques for Big UI

From Nokia Developer Wiki
Jump to: navigation, search
Featured Article
09 Mar
2014

Note.pngNote: This is an entry in the Nokia Imaging and Big UI Wiki Competition 2013Q4.

This article shows various techniques for adapting a Windows Phone application to both big screens and higher screen resolutions. Applications should not simply "scale up" on these devices, instead they should "light up" with new functionality and presentation.

WP Metro Icon UI.png
WP Metro Icon WP8.png
Article Metadata
Code Example
Source file: Media:BigUI.zip
Tested with
SDK: Windows Phone 8.0 SDK
Compatibility
Platform(s):
Windows Phone 8
Article
Created: to_pe (14 Dec 2013)
Last edited: kiran10182 (09 Mar 2014)

Contents

Introduction

Designing for Windows Phone

Designing applications for Windows Phone 7.x (from now on WP7) was easy since there was only one supported resolution - 480 x 800 (WVGA). In addition, screen sizes did not vary that much between vendors. This "unfragmentation" was hailed as a great feature for the young OS since applications would look the same on all devices that ran Windows Phone 7.

Windows Phone 8 introduced two additional resolutions: 768 x 1280 (WXGA) and 720 x 1280 (720p). The first is an up-scaled version of the original resolution and they share the same aspect ratio (15:9). The latter resolution actually has a different aspect ratio (16:9). This meant that applications had some extra space at the bottom for the UI, but in general if layout is done dynamically, UI does not need to be altered significantly to account for the extra space. The devices got a little larger, but this also meant little to the application design process.

However, coming with GDR3 update, Windows Phone 8 got a new resolution - 1080p. Along with the new, and currently the highest available, resolution, devices also grew in size. For the first time Windows Phone 8 is ran on devices with 6 inches in screen diagonal. Applications can no longer simply scale up to accommodate such large devices, they need to change their layout a bit as well.

This article presents a couple ideas for adapting the application's UI for the large screen. It also contains some helper classes useful for detecting current resolution and current screen size. There is also a small trick used here to use 720p emulator image as a 1080p device with "faked" 6-inch screen. This allows for easy testing of mentioned techniques without having a physical device with such a large screen or with a 1080p resolution (or both).

Solving design problems

When designing UI in XAML, the editor presumes that the resolution is either 480x800 (WXGA, WVGA) or 480x853 (720p or 1080p) in device independent pixels. When running on devices with higher resolution, UI is simply scaled up and no additional effort is needed to ensure that the applications will be displayed correctly on the device. UI should never rely on exact pixels and layout should be designed to stretch automatically to fill all available space through various layout controls.

Even though this works as expected, there are two problems that arise when designing without considering higher resolutions and/or screen size.

Firstly, graphical assets that are designed for the lowest (XAML) resolution might suffer when shown on large screens and might appear pixelated. For best results, application should bundle different assets for different resolutions in order to appear highly polished.

As for big screens, simply stretching the UI in both dimensions might yield unsatisfactory results. Stretching will actually decrease content densitiy on the screen and UI might appear empty. Increasing image sizes, displaying more text or even adding more controls might improve the look and feel dramatically.

This article presents advanced techniques for improving UI specifically on big screens or 1080p screens. They can be advanced even further for some additional scenarios, but that is left as an exercise for the reader.

The first technique shown here shows how to create a completely different version of some pages that are only used when the application detects it is running on large screen. This is a powerful technique that can radically alter the design beyond simple restyling.

The second technique shows how to change styles dynamically to make small overall changes to the UI when the first technique is too powerful.

The third technique shows how to use different assets depending on the resolution. This will prevent "visible pixels" when running on high end devices and will make the application look polished.

Using custom Uri mapper to change pages dynamically

The two techniques mentioned in this article deal with assets or styles for individual elements. This covers a lot places where special design for big screens is needed. However, there are moments when this is not enough and when the page has to be designed entirely from scratch for big screens. The code behind should remain untouched, only the UI has to be adapted.

But if there are different pages for different UIs, how will the navigation work? It is not feasible to change every code location where navigation is required to specify the target page. Luckily, there is a simple solution in the form of UriMapper. This is a property of the PhoneApplicationFrame which allows for UI rewriting. The full solution will involve multiple pieces. First, a class named BigUIUriMapper is created and added as a mapper for the application frame. The class will perform rewriting if needed. By default, it won't change the navigation if the application is running on "small" screens. If it is running on big screens, URI will be rewritten to point directly to the override.

Different .xaml files for different scenarios

The project structure is shown on the image to the right. There are different pages for big screens and for devices with 1080p screen resolution. This is just a sample and depending on the application requirements, this may change. But for this simple demo, there are overrides for big screen and for 1080p screens. When creating a base frame, either PhoneApplicationFrame or TransitionFrame (if using Windows Phone Toolkit), simply initialize the otherwise uninitialized property UriMapper:

RootFrame = new PhoneApplicationFrame
{
UriMapper = new BigUIUriMapper()
};

The rest of the logic is implemented in the mapper itself. This technique is similar to the first technique in this article in the way that it rewrites URIs in runtime. Here is the full implementation for the mapper:

public class BigUIUriMapper : UriMapperBase
{
public BigUIUriMapper()
{
IsFullHD = ResolutionHelper.CurrentResolution == Resolutions.FullHD ||
ResolutionHelper.CurrentResolution == Resolutions.HD;
IsBigScreen = ScreenSizeHelper.IsBigScreen;
}
 
public override Uri MapUri(Uri uri)
{
if (IsBigScreen)
{
var newUri = uri.OriginalString.Replace("Views/", "ViewsBig/");
return new Uri(newUri, UriKind.Relative);
}
else if (IsFullHD)
{
var newUri = uri.OriginalString.Replace("Views/", "Views1080/");
return new Uri(newUri, UriKind.Relative);
}
return uri;
}
 
public bool IsFullHD { get; set; }
public bool IsBigScreen { get; set; }
}

This rather simple approach simply rewrite URIs according to the naming convention specific for this project. When navigating to /Views/MainPage.xaml, the URI is rewritten to navigate to /ViewsBig/MainPage.xaml when running on big screen device. But what if only a few pages require such drastic redesigns, using this solution there should be multiple copies of the same page in multiple folders. And that is not practical at all.

This problem can be solved by checking if the new file, the one rewriter points to, exists at all. To get the list of all xaml files in the project, the following field and code are added to the class and to the constructor respectively:

// BigUIUriMapper field
private readonly List<string> _xamlFiles = new List<string>();
 
public BigUIUriMapper()
{
//
// ...
//
 
var assemblyFullName = System.Reflection.Assembly.GetExecutingAssembly().ToString();
var baseName = assemblyFullName.Substring(0, assemblyFullName.IndexOf(",")) + ".g";
 
var manager = new ResourceManager(baseName, System.Reflection.Assembly.GetExecutingAssembly());
var set = manager.GetResourceSet(System.Globalization.CultureInfo.InvariantCulture, true, true);
foreach (DictionaryEntry resource in set)
{
Debug.WriteLine(resource.Key);
if (resource.Key.ToString().EndsWith(".xaml"))
{
var doc = XDocument.Load(resource.Value as Stream);
if (doc.Root.Name == "{clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone}PhoneApplicationPage")
{
_xamlFiles.Add(resource.Key.ToString().ToLowerInvariant());
Debug.WriteLine(resource.Key + " is a Page");
}
}
}
}

Now, after rewriting the URI, it is checked if that file actually exists at all before navigating to it. For this change the implementation for the MapUri function:

public override Uri MapUri(Uri uri)
{
if (IsBigScreen)
{
var newUri = uri.OriginalString.Replace("Views/", "ViewsBig/");
if (_xamlFiles.Any(i => newUri.ToLowerInvariant().IndexOf(i, StringComparison.Ordinal) != -1))
{
return new Uri(newUri, UriKind.Relative);
}
}
else if (IsFullHD)
{
var newUri = uri.OriginalString.Replace("Views/", "Views1080/");
if (_xamlFiles.Any(i => newUri.ToLowerInvariant().IndexOf(i, StringComparison.Ordinal) != -1))
{
return new Uri(newUri, UriKind.Relative);
}
}
return uri;
}

This saves a lot of time and adds rewriting as a feature only for those pages that actually require it. The image below shows how all three techniques from this article can be combined to create a final effect:

  1. avatar image is screen resolution dependent
  2. list style is screen size dependent
  3. page layout is screen size dependent (notice that the title is different; a subtle change, but more of a Proof of Concept at this point)

Add caption here

Adapt style to the screen size or resolution

The previous technique relied on screen's resolution to change the asset dynamically, but it did not take into account the screen size. While using higher resolution assets can increase the quality dramatically due to its crisp view, it doesn't affect the overall application presentation on larger screens. Applications will still look stretched out and the application is essentially wasting space.

The idea is to detect that the application is running on big screen in runtime and adapt or change the layout then, instead of hardcoding it in the application. Big screen is anything larger than 5.0, but some application might start changing their appearance at even smaller devices.

With GDR3 update there are three new properties queryable via the DeviceExtendedProperties class: PhysicalScreenResolution, RawDpiX and RawDpiY. If we are not running on GDR3, the device should be under 5 inches anyway.

But before presenting the full snippet for determining screen size, here is a snippet containing a version of TryGetValue that is strongly typed. It shortens the code needed and removes the need for temporary variables.

public static class DeviceExtendedPropertiesHelper
{
public static bool TryGetValue<T>(string propertyName, out T value)
{
value = default(T);
object temp;
if (DeviceExtendedProperties.TryGetValue(propertyName, out temp) &&
temp is T)
{
value = (T)temp;
return true;
}
return false;
}
}

And now for the snippet of the helper class:

public static class ScreenSizeHelper
{
private static double? _screenSize;
 
public static double ScreenSize
{
get
{
#if !RELEASE
// in debug mode and in emulator
// fake 1080p when running in 720p
if (Microsoft.Devices.Environment.DeviceType == DeviceType.Emulator &&
App.Current.Host.Content.ScaleFactor == 150)
{
return 6.0;
}
#endif
if (!_screenSize.HasValue)
{
double dpiX, dpiY;
Size resolution;
 
if (DeviceExtendedPropertiesHelper.TryGetValue("RawDpiX", out dpiX) &&
DeviceExtendedPropertiesHelper.TryGetValue("RawDpiY", out dpiY) &&
DeviceExtendedPropertiesHelper.TryGetValue("PhysicalScreenResolution", out resolution) &&
dpiX > 0 && dpiY > 0)
{
var hor = resolution.Width / dpiX;
var ver = resolution.Height / dpiY;
_screenSize = Math.Sqrt(hor * hor + ver * ver);
}
else
{
_screenSize = 0;
}
}
 
return _screenSize.Value;
}
}
 
public static bool IsBigScreen
{
get { return ScreenSize > 5.0; }
}
}

The code is rather short and should be self-explanatory. There is some math involved in calculating the physical dimensions from both screen resolution and DPI values, but the result of the calculation is cached for subsequent queries. The three properties used in the above code sample are new on GDR3 devices. In case that one or more of these properties is missing, it just means that the application is running on pre-GDR3 hardware and we can safely assume that the screen size is 4 or smaller.

Another assumption is that big screens are devices with more than 5 in screen diagonal. Some application might benefit from adapting to even smaller resolutions such as 4.5. In that case, changing the hard coded value in the last property is enough.

It is noticeable that the screen size detection code presumes that the emulator running 720p image is actually a 6 device. This is done purely for testing reasons if application testing is done on the emulator. Unless there is an actual physical device available for testing, this is the recommended method.

Now that the screen size can be detected, it is easy to change the templates used across the application on application startup. Right after calling InitializeComponent in the App constructor, add a call to the method named CheckForBigUI:

public App()
{
// Global handler for uncaught exceptions.
UnhandledException += Application_UnhandledException;
 
// Standard XAML initialization
InitializeComponent();
 
CheckForBigUI();
 
// Phone-specific initialization
InitializePhoneApplication();
 
// ... remaining code ...
}

For this code to work, it is presumed that those styles which should be overridden, when running on a big screen, are defined in App.xaml and are not included in the target page directly or defined locally (e.g. ListBox.ItemTemplate should not use inline DataTemplate definition). Let's presume that all styles and data templates are defined in a file named Default.xaml and let's presume that the overrides are placed in file named DefaultBig.xaml. App.xaml should look like this:

<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Default.xaml" />
<!-- Unomment the following dictionary to test Big UI -->
<!--<ResourceDictionary Source="DefaultBig.xaml" />-->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>

And now for the CheckForBigUI implementation:

private void CheckForBigUI()
{
if (ScreenSizeHelper.IsBigScreen)
{
var bigUITheme = new ResourceDictionary
{
Source = new Uri("/PivotApp1;component/DefaultBig.xaml", UriKind.Relative)
};
Resources.MergedDictionaries.Add(bigUITheme);
}
}

The effect of running this application on either 720p emulator in debug mode or on physical device with large screen is demonstrated on the image below. Notice that the avatar image is actually 1080p image proving compatibility with the previous technique from this article. The code is quite simple - in runtime the code checks if the device has a big screen and, in case it does, includes the resource dictionary with the proper overloads. The full path to the resource dictionary is required and cannot be shortened.

Add caption here

Let's see what both Default.xaml and DefaultBig.xaml contain. Firstly, the content of Default.xaml:

<ResourceDictionary 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
<DataTemplate x:Key="OneLineTemplate">
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>

It is a simple two line per item template similar to the mail client. When running on a big screen, instead of displaying just the first line, the template is modified to show one extra line and an avatar image. This is visible on the image above. The override for big screens is placed in DefaultBig.xaml:

<ResourceDictionary 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pivotApp1="clr-namespace:PivotApp1">
 
<DataTemplate x:Key="OneLineTemplate">
<Grid Margin="0,0,0,17">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
 
<Image Grid.RowSpan="2" Grid.Row="1"
Width="99" Height="76"
Margin="12,0,0,0"
pivotApp1:ResolutionResource.ImageSource="/Assets/avatar.jpg" />
 
<TextBlock Text="{Binding LineOne}"
Grid.ColumnSpan="2"
TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}"
Grid.Column="1" Grid.Row="1"
Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
<TextBlock Text="{Binding LineThree}"
Grid.Column="1" Grid.Row="2"
Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</Grid>
</DataTemplate>
</ResourceDictionary>

This way application has a standard look on variety of hardware already present in the market while still containing something new when running on so called "phablets". Using this technique it is easy to change the look and feel of many controls just by specifying overrides in a separate file. It is possible to use this technique to change themes depending on range of other factors such as DPI, resolution, memory constraints, etc.

Using attached properties for selecting resolution dependent assets

One way for using screen resolution dependent assets is described on MSDN: Multi-resolution apps for Windows Phone 8, but it is quite verbose and not very portable. While it is a good solution and it solves the problem of selecting an image source dynamically at run-time, hard-coding all assets in the code should be avoided.

The first thing we need is a good helper class that will help us find out the resolution at run-time. Here is a slightly modified version of the helper class found on that link above:

public enum Resolutions { WVGA, WXGA, HD, FullHD };
 
public static class ResolutionHelper
{
private static bool IsWvga
{
get{return App.Current.Host.Content.ScaleFactor == 100;}
}
 
private static bool IsWxga
{
get{return App.Current.Host.Content.ScaleFactor == 160;}
}
 
private static bool IsHD
{
get{return App.Current.Host.Content.ScaleFactor == 150;}
}
 
private static bool Is720p
{
get
{
if (App.Current.Host.Content.ScaleFactor != 150)
return false;
 
Size size;
if (DeviceExtendedPropertiesHelper.TryGetValue("PhysicalScreenResolution", out size))
{
return size.Width == 720;
}
return true;
}
}
 
private static bool Is1080p
{
get
{
if (App.Current.Host.Content.ScaleFactor != 150)
return false;
 
Size size;
if (DeviceExtendedPropertiesHelper.TryGetValue("PhysicalScreenResolution", out size))
{
return size.Width == 1080;
}
return false;
}
}
 
public static Resolutions CurrentResolution
{
get
{
if (IsWvga) return Resolutions.WVGA;
if (IsWxga) return Resolutions.WXGA;
#if !RELEASE
if (Is720p &&
Microsoft.Devices.Environment.DeviceType == DeviceType.Emulator)
return Resolutions.FullHD;
#else
if (Is720p) return Resolutions.HD;
#endif
if (Is1080p) return Resolutions.FullHD;
 
throw new InvalidOperationException("Unknown resolution");
}
}
}

You can notice that in when application is compiled in debug build and is running on emulator, 720p emulator is used to fake a 1080p device. Until Microsoft issues an official 1080p emulator, this is the only way to test applications on such screen sizes unless you have a physical device.

This resolution will come in handy in the actual code. Before we show the code, let's add the necessary assets to the projects as seen on the images below. It is ideal to have image for every resolution.

Different assets for different resolutions. Different assets for different resolutions.

The following snippet will show you how to bind such resolution-dependent asset to the Image control and the same technique can be used for other XAML controls and other dependency properties e.g. Background property. The gist of the idea is to use an attached property to rewrite the path to the asset given in the XAML. This means that the image is loaded after processing its path and it even works in Visual Studio when the device display is changed in the Device tool window. This is why consistent naming is important for this technique.

public static class ResolutionResource
{
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.RegisterAttached("ImageSource", typeof(string), typeof(Image), new PropertyMetadata(default(string), ImageSource_Changed));
 
public static void SetImageSource(UIElement element, string value)
{
element.SetValue(ImageSourceProperty, value);
}
 
public static string GetImageSource(UIElement element)
{
return (string)element.GetValue(ImageSourceProperty);
}
 
private static void ImageSource_Changed(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
var path = (string)args.NewValue;
var ext = Path.GetExtension(path);
var folder = Path.GetDirectoryName(path);
var name = Path.GetFileNameWithoutExtension(path);
 
switch (ResolutionHelper.CurrentResolution)
{
case Resolutions.WXGA:
path = Path.Combine(folder, string.Format("{0}.screen-wxga{1}", name, ext));
break;
case Resolutions.HD:
path = Path.Combine(folder, string.Format("{0}.screen-720p{1}", name, ext));
break;
case Resolutions.FullHD:
path = Path.Combine(folder, string.Format("{0}.screen-1080p{1}", name, ext));
break;
 
default:
// this is wvga, nothing to change here
break;
}
 
((Image)o).Source = new BitmapImage(new Uri(path, UriKind.Relative));
}
}

Instead of using the Source dependency property, the above attached property is used in the following way:

<Image pivotApp1:ResolutionResource.ImageSource="/Assets/avatar.jpg" />

Where pivotApp1 is the local app namespace where the attached property is defined. Note that it can be abbreviated to something shorter if one is fond of shorter names. The image below the same page on different devices.

Different assets for different resolutions.

Summary

This article demonstrates various advanced techniques for adapting Windows Phone applications to the big screens. The first technique demonstrates how to use different assets depending on the screen resolution. The second technique shows how to change styles and templates for controls depending on the screen size and allows for even further theming depending on various other factors. The third and final techniques shows how to change entire page depending on the environment, either big screen or 1080p resolution.

It is recommended to take these techniques as a base for customizing individual applications and they are not a complete solution, they are more of a showcase of what is possible.

Source Code

Download File:BigUI.zip

This page was last modified on 9 March 2014, at 21:31.
169 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×