Wednesday, May 24, 2017

Xamarin Forms Device Orientation

I stole this idea shamelessly from Charles Petzold's new book on Xamarin Forms.

I want to create a screen that re-arranges itself when the user changes the orientation of the device. In this example, the text box and slider will be above and below in portrait mode but they will be side-by-side in landscape mode.


We can achieve this by defining a 2x2 grid. The entry element goes into the top left cell and stays there. The slider goes into the bottom left cell in portrait mode or the top right cell when in landscape mode. Any row or column that is empty gets collapsed. We use MVVM to bind the row heights, column widths, and the row and column of the slider element.

It seems to me that the cleanest viewmodel for this requirement contains an IsPortrait property and also a Text property to bind the Entry and Slider elements. I used converters to expand and collapse rows and columns based on the value of the IsPortrait property.

I'm also using a new feature introduced in C# 5.0 called "CallerMemberName" which allows a parameter to default to the name of the calling member. I will be using it in a generic SetProperty method in my ViewModel which will greatly reduce the amount of coding required for the properties.

Let's start with a new Xamarin.Forms project called SampleOrientation. I'm using Visual Studio 2017. Remember to select the PCL option after clicking [OK].


Let's start by creating our ViewModel. Create a new folder in the portable project called ViewModels and add a new class called BaseViewModel. I plan on using this base class for all my view models. It contains all the code to implement and simplify INotifyPropertyChanged. If you don't know what that is, now would be a good time to do some research. We will call SetProperty from our property setters. Note it returns true if the value changed so we can do other things if we need to.

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace SampleOrientation.ViewModels
{
    class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(storage, value)) return false;
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Now we add a new class file called SampleOrientationViewModel to the ViewModels folder. It derives from BaseViewModel and adds the properties needed by our application. We can now add our two properties to the view model class. Note how compact the setters are now. We also add an OnSizeChanged event handler which gets called whenever the size of the page is changed (such as when the orientation changes). It will either set or clear the IsPortrait property.

using System;
using Xamarin.Forms;

namespace SampleOrientation.ViewModels
{
    class SampleOrientationViewModel : BaseViewModel

    {
        private bool _IsPortrait = true;
        public bool IsPortrait
        {
            get { return _IsPortrait; }
            set { SetProperty(ref _IsPortrait, value); }
        }

        private String _Text = "0";
        public String Text
        {
            get { return _Text; }
            set { SetProperty(ref _Text, value); }
        }

        public void OnSizeChanged(object sender, EventArgs e)
        {
            IsPortrait = (((ContentPage)sender).Height > ((ContentPage)sender).Width);
        }
    }
}

Let's jump over to MainPage.xaml and add the reference to our new view model. We need to add a new namespace declaration to the ContentPage tag and also start a resource dictionary. That allows us to set the page's binding context. Once we have done this, all binding paths will be resolved in our view model.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:SampleOrientation"
             xmlns:vm="clr-namespace:SampleOrientation.ViewModels"
             BindingContext="{StaticResource vm}"
             x:Class="SampleOrientation.MainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <vm:SampleOrientationViewModel x:Key="vm"/>
        </ResourceDictionary>
    </ContentPage.Resources>

Before we go any further, let's write our converters. We need a converter to control the height of row 1, one to control the width of column 1, others to control the row and column that the slider will be in, and one more to convert between the Entry.Text and the Slider.Value. That's five in total.

Add a new class file to the portable project called Converters. Converters can be shared between pages of a project but view models tend not to be. Add the following converter classes to Converters.cs. It looks like a lot of code, but you can see it's very repetitive. The converters are in the SampleOrientation namespace so they can be references by the local xmlns that Visual Studio already declared for us.

using System;
using System.Globalization;
using Xamarin.Forms;

namespace SampleOrientation
{
    class GetSliderRow : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // In portrait mode the slider goes into row 1 (below the Entry)
            return (bool)value ? 1 : 0;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    class GetSliderColumn : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // In landscape mode the slider goes into column 1 (to the right of the Entry)
            return (bool)value ? 0 : 1;
        }

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

    class StringToDouble : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Parse a text into a double if possible
            double d;
            if (double.TryParse((string)value, out d))
                return d;
            else
                return 0;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.ToString();
        }
    }

    class GetRow1Height : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // In portrait mode grid row 1 must be visible
            if ((bool)value)
                return new GridLength(1, GridUnitType.Star);
            else
                return new GridLength(0, GridUnitType.Absolute);
        }

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

    class GetCol1Width : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // In portrait mode grid col 1 must be hidden
            if ((bool)value)
                return new GridLength(0, GridUnitType.Absolute);
            else
                return new GridLength(1, GridUnitType.Star);
        }

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

I get a reference to the view model and register its OnSizeChanged event handler for the page's SizeChanged event. I would prefer to do this in XAML but I can't figure out how to declare an event handler that is not in MainPage.xaml.cs. Yet.

I also added a try/catch around InitializeComponent because in Visual Studio 2017 all XAML errors are swallowed before they get to me and rethrowing them solves the problem. Here is the full MainPage.xaml.cs.

using SampleOrientation.ViewModels;
using Xamarin.Forms;

namespace SampleOrientation
{
    public partial class MainPage : ContentPage
    {
        SampleOrientationViewModel vm;
        public MainPage()
        {
            try
            {
                InitializeComponent();
                vm = (SampleOrientationViewModel)this.BindingContext;
                this.SizeChanged += vm.OnSizeChanged;
            }
            catch { throw; }
        }
    }
}

Now it is time to finish the XAML. Here is MainPage.xaml in its entirety.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:SampleOrientation"
             xmlns:vm="clr-namespace:SampleOrientation.ViewModels"
             BindingContext="{StaticResource vm}"
             x:Class="SampleOrientation.MainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:GetSliderRow x:Key="GetSliderRow"/>
            <local:GetSliderColumn x:Key="GetSliderColumn"/>
            <local:GetRow1Height x:Key="GetRow1Height"/>
            <local:GetCol1Width x:Key="GetCol1Width"/>
            <local:StringToDouble x:Key="StringToDouble"/>
            <vm:SampleOrientationViewModel x:Key="vm"/>
        </ResourceDictionary>
    </ContentPage.Resources>
    <Grid RowSpacing="0" ColumnSpacing="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="{Binding IsPortrait, Converter={StaticResource GetRow1Height}}"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="{Binding IsPortrait, Converter={StaticResource GetCol1Width}}"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Frame Grid.Row="0" Grid.Column="0" OutlineColor="DarkBlue">
                <Entry Keyboard="Numeric" Text="{Binding Text}" VerticalOptions="Center" HorizontalOptions="FillAndExpand" BackgroundColor="PaleGoldenrod"></Entry>
        </Frame>
        <Frame Grid.Row="{Binding IsPortrait, Converter={StaticResource GetSliderRow}}" 
               Grid.Column="{Binding IsPortrait, Converter={StaticResource GetSliderColumn}}">
            <Slider Value="{Binding Text, Converter={StaticResource StringToDouble}}" VerticalOptions="Center" HorizontalOptions="FillAndExpand"></Slider>
        </Frame>
    </Grid>
</ContentPage>

Note that none of the elements have names. You don't need them. Everything can be done through binding. Other than the location of the SizeChanged event handler, everything is pure MVVM.

Friday, May 19, 2017

Xamarin Forms Toast

I like the toast feature in native Xamarin that displays a message which automatically disappears after a preset time.

Toast.makeText(this, "You have won the Nobel Prize for programming", Toast.LENGTH_LONG).show();

However, there is no equivalent in Xamarin Forms yet. But there are several good plug-ins that will offer similar functionality in different ways. I found Toasts.Forms.Plugin by Adam Pedley and Egor Bogatov. It is documented here.

I'm using PCL so I also need their PCL plugin. In Visual Studio 2017 you use it like this...

Start a new Cross-Platform project (Xamarin.Forms) in C#, call it Toaster, and select the PCL option.


Now open NuGet (Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution"). Click on Browse and enter "Toasts.Forms" in the search terms. You will see two packages.

Click on the first one (Toasts.Forms.Plugin) and Check "Project" in the right panel.




You will need to install it in all your projects because it uses Dependency Services. Click [Install].
Now repeat for Toasts.Forms.Plugin-PCL.

If you open the References nodes in your projects you will see references to Toasts.Forms.Plugin.Abstractions and other necessary references were added automatically.

Using the Toasts plugin is fairly simple. We start by registering the Dependency Service in the platform projects.

For each project add the following to MainActivity.cs or MainPage.xaml.cs
using Xamarin.Forms;
using Plugin.Toasts;

For Android we add the registration at the end of MainActivity.OnCreate like this...

DependencyService.Register<ToastNotification>();
ToastNotification.Init(this);

For iOS and WinPhone you can use the similar...

DependencyService.Register<ToastNotification>();
ToastNotification.Init();

For iOS you also have to request permissions. Here's an example for system version 10.0.

UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound, (granted, error) =>
{
    // Deal with the problem
}

Now you can write a simple method in the portable project (perhaps put it in a utility class) to display a toast.
void DisplayToast(String title, String text)
{
    Device.BeginInvokeOnMainThread(async () =>
    {
        var notifier = DependencyService.Get<IToastNotificator>();
        var options = new NotificationOptions()
        {
            Title = title,
            Description = text
        };
        var result = await notifier.Notify(options);
    });
}

And you can easily call the method to display a toast from the portable project like this.

void OnPhoneCall(object sender, EventArgs e)
{
    if (sender == "Nobel Prize Committee")
        DisplayToast("Nobel Prize", "Congratulations - you have won the Nobel Prize for programming");
}



Thursday, May 18, 2017

Xamarin Forms: Referencing an image resource from XAML

Stolen shamelessly from Charles Petzold's superb book "Creating Mobile Apps with Xamarin.Forms" - First Edition Chapter 13. Available for free from Xamarin.com

Xamarin Forms does not contain a mechanism for referencing an embedding image in XAML. Here is a fairly simple markup extension that fixes that problem. Hopefully the Xamarin people will embed this into Xamarin.Forms in an upcoming release.

Start a new Xamarin.Forms PCL project and call it ImageXAML. File -> New -> Project...

Don't forget to select PCL after clicking [OK]
Now add a new class to ImageXAML (Portable) called ImageResourceExtension. Edit the class to look like this.

using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace ImageXAML
{
    [ContentProperty ("Source")]
    class ImageResourceExtension : IMarkupExtension
    {
        public string Source { get; set; }
        public object ProvideValue (IServiceProvider serviceProvider)
        {
            if (Source == null) return null;
            return ImageSource.FromResource(Source);
        }
    }
}

This class implements IMarkupExtension which exposes a Source property and a ProvideValue method. You populate the source property with a resource reference in the XAML and the framework uses ProvideValue to get the image resource. The ContentProperty decoration allows you to simplify your XAML because it knows the Content property is called Source.

This example assumes that Source is a resource but it would be easy to write versions that assume it is a URL, etc.

Add a new folder to ImageXAML (portable) called Images and add jpg, bmp, or png image to it. You must set the Build Action to Embedded Resource. If you forget to do this the call to FromResource() will not return anything and will not throw an error.

Your Solution Explorer will look something like this...


Now open MainPage.xaml and replace the label with an image tag. Note the format of the embedded resource reference is <Namespace>.<Path>.<FileName>


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ImageXAML"
             x:Class="ImageXAML.MainPage">

    <Image Source="{local:ImageResource ImageXAML.Images.Happy.jpg}"></Image>

</ContentPage>

Run the project. You will see something like this...


Wednesday, May 17, 2017

Problems with Xamarin

Xamarin.Forms uses XAML in similar ways to WPF. But in Visual Studio 2017 it has many problems. The worst of these is the inability of the editor, compiler, builder, or even the run-time to report errors in XAML. When you make XAML errors the InitializeComponents call never returns and you get a blank screen. Here are a few simple errors that will give you the dreaded blank-screen of nausea.

Let's start with something that works - here is the MainPage.xaml from the default Xamarin.Forms PCL project template.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Broken"
             x:Class="Broken.MainPage">

<Label Text="Welcome to Xamarin Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />

</ContentPage>

Which looks like this on the emulator.


Now let's make a mistake and reference a non-existent style.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Broken"
             x:Class="Broken.MainPage">

<Label Text="Welcome to Xamarin Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center"
           Style="{StaticResource MyStyle}"/>

</ContentPage>

Now we get a blank screen. If you put a breakpoint on InitializeComponent() and then single-step you will see it never returns.

Let's add the style but get the case wrong on the key (like we've never done that!)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Broken"
             x:Class="Broken.MainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style x:Key="myStyle" TargetType="Label">
                <Setter Property="Background" Value="Red"></Setter>
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
<Label Text="Welcome to Xamarin Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center"
           Style="{StaticResource MyStyle}"/>

</ContentPage>

We get the same blank page. In fact you get a blank page if you forget the TargetType parameter, if you don't specify any <SETTER>s, you get the property name wrong, you get the value wrong, or pretty much any other error you think of. Add to this the fact that everything is case-sensitive and there is almost no intellisense and you can see that developing Xamarin.Forms in Visual Studio 2017 is bloody frustrating.

Update: There is a solution to this but it is non-intuitive. There is an exception being thrown but it is being thrown away by Xamarin Forms. You simply have to catch the exception and rethrow it like this.

        public MainPage()
        {
            try
            {
                InitializeComponent();
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }

Then you get a useful error message popping up.


Inherited default styles

I have been studying Xamarin Forms lately and in the chapter on styles I found myself wondering if you can inherit default styles.

For example, you can specify a default style for all controls of a particular class. You can also explicitly use styles for ancestor classes - for example a label can reference a style that targets a control. I wondered if you could specify a default style for a class and have it affect all derived classes.

Here's the code that tests this question. I define a default style for "Control" and check to see if a "Label" control will inherit it. If it does, the label will have a red background.

<Window x:Class="InheritDefaultStyle.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:InheritDefaultStyle"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <ResourceDictionary>
            <Style x:Key="InheritedStyle" TargetType="Control">
                <Setter Property="Background" Value="Green"/>
            </Style>
            <Style TargetType="Control">
                <Setter Property="Background" Value="Red"/>
            </Style>
        </ResourceDictionary>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <Label Style="{StaticResource InheritedStyle}" Content="If the background is GREEN you can inherit a named style"/>
        <Label Content="If the background is RED you can inherit a default style"></Label>
    </StackPanel>
</Window>

The result of this test is...


As you can see, a default style only applies to the control type specified in the TargetType, but not to descendants. This means if I create a custom control that derives from Label it will not inherit the default style for Label. This is not what most developers would expect or want.