Nowadays, there are a lot of different device types with extremely varying pixel densities.
When drawing images pixel-perfect they appear large on devices with low pixel densities and small on devices with high pixel densities.
So the solution is either to use the same image in different resolutions and choosing at runtime what actual image to show or to display scaled images. The choice is here between more effort during development/maintenance or to accept blurred images.
An alternative solution is the use of vector graphics. They are indeed resolution independent and are rendered perfectly on the actual device.
Probably vector graphics are not used as frequently as they should, because many feel that it is difficult to integrate vector graphics into normal apps.
Screencast
But actually it is very easy to create vector graphic elements for your XAML UI, as the following 5-minute screencast shows.
First it should be mentioned, that there were a few changes in the JavaScript Native Interoperation for Windows 8.1. So the AllowedScriptNotifyUris are no longer supported. Instead you must include the page’s URL in the ApplicationContentUriRules section of the app manifests, for both of them, the Windows App and the Windows Phone App.
Interoperation
To be able to call native code from a web page in a WebView control,
The web site must have a valid SSL certificate
Like mentioned above the page’s URI must be added to the Content URIs section of the Package.appxmanifest
On the native side you add an ScriptNotify EventHandler to the WebView
Then you can fire the event from JavaScript by calling window.external.notify
String parameters can be used
The other way round you can call arbitrary JavaScript functions by using WebView’s InvokeScriptAsync method. There you can also add String parameters. It’s a good idea to make sure that the first call to JavaScript can only happen after the web page is loaded completely.
Screencast
The screencast shows again all steps and is divided into the following sections:
Embedding a WebView control
Calling native code via window.external.notify from JavaScript in the WebView
Adding the URL of the web page to the ApplicationContentUriRules section of the app manifest.
Calling a JavaScript function in the WebView from native code
Source Code
MainPage.xmal.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using UniLayout.ViewModels;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace UniLayout
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
private MainPageViewModel _mainPageViewModel;
private bool _isInUpdate = true;
public MainPage()
{
this.InitializeComponent();
this.SizeChanged += MainPage_SizeChanged;
WebView.ScriptNotify += WebView_ScriptNotify;
WebView.Loaded += WebView_Loaded;
_mainPageViewModel = DataContext as MainPageViewModel;
_mainPageViewModel.Update();
}
void WebView_Loaded(object sender, RoutedEventArgs e)
{
_isInUpdate = false;
}
void WebView_ScriptNotify(object sender, NotifyEventArgs e)
{
_isInUpdate = true;
int value = int.Parse(e.Value);
_mainPageViewModel.SliderValue = value;
_isInUpdate = false;
}
void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
{
MainPageViewModel mainPageViewModel = DataContext as MainPageViewModel;
mainPageViewModel.Update();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
WebView.Navigate(new Uri("https://www.software7.biz/test/windowsJSInteraction/winjsinteract.html"));
}
private async void SetWebViewSlider(int sliderValue)
{
if(!_isInUpdate)
await WebView.InvokeScriptAsync("SetSliderValue", new String[] {sliderValue.ToString()});
}
private void Slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
SetWebViewSlider((int)(e.NewValue + .5));
}
}
}
There are many different devices out there with different form factors and different screens pixels densities.
In order to ease the development with different pixel densities one works with logical pixels. Windows Runtime automatically scales the UI based on the actual device pixel density.
Together with layout primitives like Grid, StackPanel, GridView and so on one can create often very efficiently great looking adaptive UIs especially if you even can limit the layout orientation to either portrait or landscape.
Starting with the Windows 8.1 Runtime it’s possible to write Universal apps targeting phones, tablets and desktop at once (without the need for PCLs or Linked Files etc.). The template in Visual Studio sets two different XAML files for Phone and Windows 8.1 projects, to be able to create the best user experience for different kinds of devices, but sometimes it makes a lot of sense to use one shared XAML file.
Requirements
Before we start, a description of the use case in brief:
One page app
Universal (phone, tablet)
Support of portrait and landscape orientation
One header area with a main title
The main area is divided into two stripes of equal height
Each stripe should have a rotated subtitle on the left
Main title and subtitles are variable (think of database fields or localizations into different languages)
Both subtitles should have the same font size
The font size for the main title should be 42pt and the subtitle 20pt (if possible)
The subtitles should be centered and use 80% of the height of a stripe at maximum
The subtitles should use the same baseline
If the device is rotated the layout must adapt using these rules
The title areas should be double as large as the font size
Some of the requirements are contradictory, e.g. use a font size of 20pt and 80% of the stripe height at maximum with different screen sizes and orientation.
Therefore:
If there is not enough space for a title, the font size is reduced
Is the font size of one subtitle is decreased, the font size of the other title must also be adapted
We don’t want to calculate the whole layout by ourselves, so to be able to still use the goodness of the XAML layout primitives is also a requirement.
The following illustration of the desired user interface should give you a better understanding of the requirements.
Illustration of the intended layout
Essential Workflow
Regardless of which specific implementation one chooses (e.g. layout calculation in the MVVM’s ViewModel or an implementation as Behavior), the essential workflow is the same:
Use the current main title and calculate the width of the TextBlock using a font size of 42pt
If it uses more than 80% of the available horizontal space decrease the font size in 10-percent steps until it fits
Calculate the header height
Calculate the height of the stripes
Determine the TextBlock widths of both subtitles based on an initial font size of 20pt
If the width of one of them is larger than 80% of the height of the containing stripe, decrease the font size in 10-percent steps
Based on the font size calculate the width of the subtitle area
Screencasts
I assume that you already have a basic understanding of XAML. Nevertheless, I show all the steps for this project in the screencasts.
Whether your work with two separate XML files or a shared one depends on the actual use case. The following screencast shows how to share one XAML Layout. Before watching the screencasts it makes perhaps some sense to read a little bit more about the requirements below the screencasts section.
Screencast #1 shows:
Configure the usage of one shared XAML
Create a dummy UI in XAML with the areas
Create a ViewModel using the MVVM pattern
Use Resharper to create the properties
Change the setters with a regular expression at once (to fire PropertyChangedEvents)
Screencast #2 shows how to use converter classes for the binding:
Screencast #3 finally shows:
The core algorithm for the adaptive layout
A brief test
Source Code
MainPageViewModel.cs
using System;
using System.Collections.Generic;
using System.Text;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using UniLayout.Common;
namespace UniLayout.ViewModels
{
class MainPageViewModel : BindableBase
{
private const int BaseFontSizeMainTitle = 42;
private const int BaseFontSizeSubTitle = 20;
private string _mainTitle = "Possibly Very Long Title";
private string _upperTitle = "Upper Long Title";
private string _lowerTitle = "Lower Area";
private int _fontSizeMainTitle;
private int _fontSizeSubTitle;
private int _mainTitleHeight;
private int _headerHeight;
private int _subTitleHeight;
private int _upperTitleWidth;
private int _upperTitleHeight;
private int _lowerTitleWidth;
private int _lowerTitleHeight;
public string MainTitle
{
get { return _mainTitle; }
set { SetProperty(ref _mainTitle, value); }
}
public string UpperTitle
{
get { return _upperTitle; }
set { SetProperty(ref _upperTitle, value); }
}
public string LowerTitle
{
get { return _lowerTitle; }
set { SetProperty(ref _lowerTitle, value); }
}
public int FontSizeMainTitle
{
get { return _fontSizeMainTitle; }
set { SetProperty(ref _fontSizeMainTitle, value); }
}
public int FontSizeSubTitle
{
get { return _fontSizeSubTitle; }
set { SetProperty(ref _fontSizeSubTitle, value); }
}
public int MainTitleHeight
{
get { return _mainTitleHeight; }
set { SetProperty(ref _mainTitleHeight, value); }
}
public int HeaderHeight
{
get { return _headerHeight; }
set { SetProperty(ref _headerHeight, value); }
}
public int SubTitleHeight
{
get { return _subTitleHeight; }
set { SetProperty(ref _subTitleHeight, value); }
}
public int UpperTitleWidth
{
get { return _upperTitleWidth; }
set { SetProperty(ref _upperTitleWidth, value); }
}
public int UpperTitleHeight
{
get { return _upperTitleHeight; }
set { SetProperty(ref _upperTitleHeight, value); }
}
public int LowerTitleWidth
{
get { return _lowerTitleWidth; }
set { SetProperty(ref _lowerTitleWidth, value); }
}
public int LowerTitleHeight
{
get { return _lowerTitleHeight; }
set { SetProperty(ref _lowerTitleHeight, value); }
}
public void Update()
{
double width = Window.Current.Bounds.Width;
int fsMainTitle = BaseFontSizeMainTitle;
int fsSubTitle = BaseFontSizeSubTitle;
int h, w;
TextBlockSizeCalc(_mainTitle, fsMainTitle, out w, out h);
while (w > width*.8)
{
fsMainTitle = (int) Math.Floor(fsMainTitle*.9);
TextBlockSizeCalc(_mainTitle, fsMainTitle, out w, out h);
}
FontSizeMainTitle = fsMainTitle;
MainTitleHeight = h*2;
double height = Window.Current.Bounds.Height;
double lowerAreaHeight = (height - MainTitleHeight)/2;
int w1, h1;
int w2, h2;
TextBlockSizeCalc(_upperTitle, fsSubTitle, out w1, out h1);
TextBlockSizeCalc(_lowerTitle, fsSubTitle, out w2, out h2);
w = Math.Max(w1, w2);
while (w > lowerAreaHeight*.8)
{
fsSubTitle = (int) Math.Floor(fsSubTitle*.9);
TextBlockSizeCalc(_upperTitle, fsSubTitle, out w1, out h1);
TextBlockSizeCalc(_lowerTitle, fsSubTitle, out w2, out h2);
w = Math.Max(w1, w2);
}
FontSizeSubTitle = fsSubTitle;
UpperTitleWidth = w1;
UpperTitleHeight = h1;
LowerTitleWidth = w2;
LowerTitleHeight = h2;
SubTitleHeight = Math.Max(h1, h2)*2;
}
private readonly TextBlock _offscreenTextBlock = new TextBlock();
private readonly Size _availableSize = new Size(0, 0);
private readonly Rect _finalRect = new Rect(0, 0, 0, 0);
private void TextBlockSizeCalc(string txt, int fontSize, out int width, out int height)
{
_offscreenTextBlock.Text = txt;
_offscreenTextBlock.FontSize = fontSize;
_offscreenTextBlock.Measure(_availableSize);
_offscreenTextBlock.Arrange(_finalRect);
width = (int) Math.Ceiling(_offscreenTextBlock.ActualWidth);
height = (int) Math.Ceiling(_offscreenTextBlock.ActualHeight);
}
}
}
IntToGridLengthConverter.cs
using System;
using System.Collections.Generic;
using System.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace UniLayout.Converter
{
class IntToGridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
int v = (int) value;
return new GridLength(v);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
WidthToRotatedMarginConverter.cs
using System;
using System.Collections.Generic;
using System.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace UniLayout.Converter
{
class WidthToRotatedMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
int width = (int) value;
return new Thickness(-width/2.0, 0, -width/2.0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}