Category Archives: Windows Phone 8

Easily Adding Vector Graphics to your Windows XAML UI

Images vs. Vector Graphics

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.

Source Code

...
        <Canvas Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center" Height="40" Grid.RowSpan="2" VerticalAlignment="Center" Width="120">
        	<Path Data="M60,40 L0,30 L40,30 L40,20 L80,20 L80,30 L120,30 z" Fill="CadetBlue" Height="20" Stretch="Fill" Canvas.Top="20" UseLayoutRounding="False" Width="120"/>
        	<Path Data="M60,40 L0,30 L40,30 L40,20 L80,20 L80,30 L120,30 z" Fill="White" Height="20" Stretch="Fill" UseLayoutRounding="False" Width="120" RenderTransformOrigin="0.5,0.5">
        		<Path.RenderTransform>
        			<CompositeTransform Rotation="-180"/>
        		</Path.RenderTransform>
        	</Path>
        </Canvas>
...

WebView JavaScript Native Interoperation in Windows 8.1

Changes in Window 8.1

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));
        }
    }
}

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UniLayout"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ViewModels="using:UniLayout.ViewModels"
    xmlns:converter="using:UniLayout.Converter"
    x:Class="UniLayout.MainPage"
    mc:Ignorable="d">

    <Page.DataContext>
        <ViewModels:MainPageViewModel/>
    </Page.DataContext>
    
    <Page.Resources>
         <converter:IntToGridLengthConverter x:Key="IntToGridLengthConverter"/>
         <converter:WidthToRotatedMarginConverter x:Key="WidthToRotatedMarginConverter"/>
        <converter:IntToStringConverter x:Key="IntToStringConverter"/>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Grid.RowDefinitions>
            <RowDefinition Height="{Binding Path=MainTitleHeight, Converter={StaticResource IntToGridLengthConverter}}"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{Binding Path=SubTitleHeight, Converter={StaticResource IntToGridLengthConverter}}"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="DarkSlateGray">
            <TextBlock Text="{Binding Path=MainTitle}" 
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       FontSize="{Binding FontSizeMainTitle}" 
                       TextAlignment="Center"/>
        </Border>

        <Border Grid.Row="1" Grid.Column="0"  Background="DarkKhaki">
            <TextBlock 
                Text="{Binding Path=UpperTitle}" 
                FontSize="{Binding FontSizeSubTitle}" Width="{Binding UpperTitleWidth}" Height="{Binding UpperTitleHeight}" 
                Margin="{Binding Path=UpperTitleWidth, Converter={StaticResource WidthToRotatedMarginConverter}}" 
                TextAlignment="Center" 
                RenderTransformOrigin="0.5, 0.5">
                <TextBlock.RenderTransform>
                    <RotateTransform Angle="-90"/>
                </TextBlock.RenderTransform>
            </TextBlock>
        </Border>

        <Border Grid.Row="2" Grid.Column="0"  Background="DarkOrange">
            <TextBlock 
                Text="{Binding Path=LowerTitle}" 
                FontSize="{Binding FontSizeSubTitle}" Width="{Binding Path=LowerTitleWidth}" Height="{Binding Path=LowerTitleHeight}" 
                Margin="{Binding Path=LowerTitleWidth, Converter={StaticResource WidthToRotatedMarginConverter}}" 
                TextAlignment="Center" 
                RenderTransformOrigin="0.5, 0.5">
                <TextBlock.RenderTransform>
                    <RotateTransform Angle="-90"/>
                </TextBlock.RenderTransform>
            </TextBlock>
        </Border>

        <Border Grid.Row="1" Grid.Column="1"  Background="CadetBlue">
            <StackPanel Margin="20">
                <StackPanel Orientation="Horizontal">
                    <TextBlock FontSize="20" Text="Native Slider:" Margin="0,0,12,0"/>
                    <TextBlock FontSize="20" Text="{Binding Path=SliderValue, Converter={StaticResource IntToStringConverter}}"/>
                </StackPanel>
                <Slider Value="{Binding Path=SliderValue, Mode=TwoWay}" Minimum="0" Maximum="100" ValueChanged="Slider_ValueChanged"/>
            </StackPanel>
        </Border>

        <Border Grid.Row="2" Grid.Column="1"  Background="AntiqueWhite">
            <WebView x:Name="WebView"/>
        </Border>


    </Grid>
</Page>


winjsinteract.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=400"/>
    <title></title>

    <link rel="stylesheet" href="js/jquery.mobile-1.4.5.min.css" />
    <script type="text/javascript" src="js/jquery-1.11.2.min.js"></script>
    <script type="text/javascript" src="js/jquery.mobile-1.4.5.min.js"></script>

</head>


<body>

<div id="content-div" style="margin:20px">
    <div id="slider-div">
        <input type="range" id="slider" value="50" min="0" max="100">
    </div>
</div>

<script type="application/javascript">

    $("#slider-div").change(function() {
        var value = $("#slider").val();
        window.external.notify(value);
    })

    function SetSliderValue(newValueFromNative) {
        $("#slider").val(newValueFromNative);
        $("#slider").slider("refresh");
    }

</script>

</body>
</html>

Shared XAML Layouts for Windows Universal Apps

 
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.

Layout

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();
        }
    }
}

MainPage.xaml

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UniLayout"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ViewModels="using:UniLayout.ViewModels"
    xmlns:converter="using:UniLayout.Converter"
    x:Class="UniLayout.MainPage"
    mc:Ignorable="d">

    <Page.DataContext>
        <ViewModels:MainPageViewModel/>
    </Page.DataContext>

    <Page.Resources>
         <converter:IntToGridLengthConverter x:Key="IntToGridLengthConverter"/>
         <converter:WidthToRotatedMarginConverter x:Key="WidthToRotatedMarginConverter"/>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Grid.RowDefinitions>
            <RowDefinition Height="{Binding Path=MainTitleHeight, Converter={StaticResource IntToGridLengthConverter}}"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{Binding Path=SubTitleHeight, Converter={StaticResource IntToGridLengthConverter}}"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="DarkSlateGray">
            <TextBlock Text="{Binding Path=MainTitle}"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       FontSize="{Binding FontSizeMainTitle}"
                       TextAlignment="Center"/>
        </Border>

        <Border Grid.Row="1" Grid.Column="0"  Background="DarkKhaki">
            <TextBlock
                Text="{Binding Path=UpperTitle}"
                FontSize="{Binding FontSizeSubTitle}" Width="{Binding UpperTitleWidth}" Height="{Binding UpperTitleHeight}"
                Margin="{Binding Path=UpperTitleWidth, Converter={StaticResource WidthToRotatedMarginConverter}}"
                TextAlignment="Center"
                RenderTransformOrigin="0.5, 0.5">
                <TextBlock.RenderTransform>
                    <RotateTransform Angle="-90"/>
                </TextBlock.RenderTransform>
            </TextBlock>
        </Border>

        <Border Grid.Row="2" Grid.Column="0"  Background="DarkOrange">
            <TextBlock
                Text="{Binding Path=LowerTitle}"
                FontSize="{Binding FontSizeSubTitle}" Width="{Binding Path=LowerTitleWidth}" Height="{Binding Path=LowerTitleHeight}"
                Margin="{Binding Path=LowerTitleWidth, Converter={StaticResource WidthToRotatedMarginConverter}}"
                TextAlignment="Center"
                RenderTransformOrigin="0.5, 0.5">
                <TextBlock.RenderTransform>
                    <RotateTransform Angle="-90"/>
                </TextBlock.RenderTransform>
            </TextBlock>
        </Border>

        <Border Grid.Row="1" Grid.Column="1"  Background="CadetBlue"/>

        <Border Grid.Row="2" Grid.Column="1"  Background="AntiqueWhite"/>

    </Grid>
</Page>