XF Platform Specific Styling

Introduction

Sometimes you need to specify XAML that is specific to the platform. Xamarin Forms already has a special class to help with this called OnPlatform. But I find that it isn’t always granular enough to handle the different instances of Windows: Windows, Windows Phone, Windows Tablet and Xbox. We can define our own custom handler to help us with this.

It looks like the following in XAML.

<!--
here is the declaration in the content page attributes:
xmlns:h="clr-namespace:AppName.Helpers"
-->
<Label Text="Display Custom Platform Styling">
    <Label.Margin>
        <h:OnCustomPlatform
            x:TypeArguments="Thickness"
            Android="5,40"
            iOS="10,20"
            Windows="20"
            WinPhone="4,8,4,8"
            WinTablet="12,8,12,8"
            Xbox="40" />
    </Label.Margin>
</Label>

A couple of things about the above code: the h: refers to the namespace that contains our OnCustomPlatform class. The other thing to note is that I don’t know if the Device and TargetIdiom objects in Xamarin Forms work with the Xbox, so use with caution if you are going to try this with an Xbox app.

How the Class Works

using Xamarin.Forms;

public sealed class OnCustomPlatform<T>
{
    public T Android { get; set; } = default(T);
    public T iOS { get; set; } = default(T);
    public T WinPhone { get; set; } = default(T);
    public T Windows { get; set; } = default(T);
    public T WinTablet { get; set; } = default(T);
    public T Xbox { get; set; } = default(T);
    public T Other { get; set; } = default(T);

    public static implicit operator T(OnCustomPlatform<T> p)
    {
        switch (Device.RuntimePlatform)
        {
            case Device.Android:
                return p.Android;
            case Device.iOS:
                return p.iOS;
            case Device.Windows:
                if (Device.Idiom == TargetIdiom.Desktop)
                    return p.Windows;
                else if (Device.Idiom == TargetIdiom.Phone)
                    return p.WinPhone;
                else if (Device.Idiom == TargetIdiom.Tablet)
                    return p.WinTablet;
                else if (Device.Idiom == TargetIdiom.TV)
                    return p.Xbox;
                else
                    return default(T);
            default:
                return p.Other;
        }
    }
}

And that is all there is. It is actually surprisingly simple to do though I admit, after a while it gets pretty verbose the more complex you get with your page.

Using IValueConverter

I was down in Las Vegas all of last week for the Autodesk Forge DevCon and Autodesk University: a full four days of learning about all of the up and coming Autodesk products and technologies and networking. Lots of fun, lots of new people to talk to. I had a chat with a somewhat new’sh dev and he was complaining about the difficulties of using XAML. We delved into a bit of MVVM and he wanted to know, in effect, how to bind visual properties.

From there we went to the use of value converters and all the potential they have for presentation without having to embed visual elements and other complex logic into your view model. And it is available across WPF, UWP and Xamarin Forms.

A Simple Case

My first value converter was in WPF and making visual elements visible or not based on a value in my view model. Lets pretend we have some additional controls in our window that are only visible if the user is an accounting user. In our view model, it is a bool property named IsAccounting. If we try to bind the IsAccounting property to the control Visibility property, a binding error occurs that says the type of the value is incorrect for the target property: because of the error we won’t see the UI change.


<!-- fails -->
<TextBox Text="{Binding AccountCode}" Visibility="{Binding IsAccounting}" />

We need a value converter that will change the bool value to a Visibility value.


public class BoolToVisConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool v = (bool) value;
        return v ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

To use it, make sure that you have the namespace that the value converter resides in declared in your Window XAML and then the converter itself referenced in the resource dictionary:

xmlns:local="clr-namespace:Demo"
<Window.Resources>
    <ResourceDictionary>
        <local:BoolToVisConverter x:Key="boolVisConverter" />
    </ResourceDictionary>
</Window.Resources>

Finally you add the converter to the binding expression of the control:


<TextBox Text={Binding SecretAccountCode} Visibility={Binding IsAccounting,Converter={StaticResource boolVisConverter}}" />

And the application looks like this:

2017-11-19boolvisconverter

I like this because it keeps the app logic free of the presentation code, and this makes your view model simpler, testable and portable.

Something More Powerful

Let’s take a look at where the IValueConverter can really help. In this example we will change the color of the text in a textbox based and an image based on the selected item in a combo box. I setup an enum with some employee types:

public enum EmployeeTypeEnum
{
	CEO,
	VicePres,
	EngDirector,
	Engineer,
}

and added EmployeeTypes and SelectedEmployeeType properties to the view model.


/// initialize this collection in the constructor with the enum values.
public ObservableCollection<EmployeeTypeEnum> EmployeeTypes { get; private set; } =
    new ObservableCollection<EmployeeTypeEnum>();

private EmployeeTypeEnum _selectedEmployeeType = EmployeeTypeEnum.CEO;
public EmployeeTypeEnum SelectedEmployeeType
{
    get => _selectedEmployeeType;
    set => SetProperty<EmployeeTypeEnum>(ref _selectedEmployeeType, value);
}

Here are our value converters, one of them will convert the SelectedEmployeeType value to a foreground color and the other will convert the SelectedEmployeeType value to an image.

public class EmployeeColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        EmployeeTypeEnum empType = (EmployeeTypeEnum) value;
        switch(empType)
        {
            case EmployeeTypeEnum.CEO:
                return Brushes.Aqua;
            case EmployeeTypeEnum.EngDirector:
                return Brushes.Green;
            case EmployeeTypeEnum.Engineer:
                return Brushes.Red;
            case EmployeeTypeEnum.VicePres:
                return Brushes.Blue;
            default:
                return Brushes.Black;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class EmployeeImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        EmployeeTypeEnum empType = (EmployeeTypeEnum) value;
        switch (empType)
        {
            case EmployeeTypeEnum.CEO:
                return new Uri("/ceo.png", UriKind.Relative);
            case EmployeeTypeEnum.EngDirector:
                return new Uri("/director.png", UriKind.Relative);
            case EmployeeTypeEnum.Engineer:
                return new Uri("/engineer.png", UriKind.Relative);
            case EmployeeTypeEnum.VicePres:
                return new Uri("/vicepres.png", UriKind.Relative);
	    default:
	        return null;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Add our converters to the resource dictionary:

<local:EmployeeColorConverter x:Key="empColorConverter" />
<local:EmployeeImageConverter x:Key="empImageConverter" />

The markup for our XAML looks like this


<ComboBox ItemsSource="{Binding EmployeeTypes}" SelectedItem="{Binding SelectedEmployeeType}" />
<TextBox Text="{Binding SelectedEmployeeType}" Foreground="{Binding SelectedEmployeeType,Converter={StaticResource empColorConverter}}" Margin="0,2" />

<Image Width="48" Height="48" Margin="4" Source="{Binding SelectedEmployeeType,Converter={StaticResource empImageConverter}}" />

And that is it. We have a simple and portable view model that can be used on other platforms.

2017-11-19_6-35-24

Feel free to check out the sample code in GitHub.

I also would like to give a shout-out to SyncFusion as I used their free MetroStudio icon generator to build my icons.