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.

Friday, March 24, 2017

Introducing NUnit on Visual Studio 2010 in Visual Basic

As part of my research on Xamarin I started looking at NUnit as a unit tester. I was curious to see how I would implement it on my Visual Basic Purchasing project. We are still using Visual Studio 2010 so the implementation details are a little different. But it can be done.

Let's start by installing NUnit.

Installing NUnit

Start Visual Studio 2010
In the menu click Tools -> Extension Manager
On the left, click Online Gallery.
In the search area, enter NUnit and hit Enter.
Scroll down to "Visual NUnit 2010" and select it.
Hit [Download]
Restart Visual Studio


Creating the project to be tested

Start a new class library project and call it "Calc".
Rename "Class1" to "Add"
Add a public method called Add so the code looks like this.

Public Class Add

    Public Function Add(op1 As Integer, op2 As Integer) As Integer
        Return op1 + op2
    End Function
End Class

Build the project

Creating the test project

Right-click on the Add class and select "Create Unit Tests..."
Ensure only "Add(System.Int32, System.Int32)" is checked and click [OK]. You don't need to create a test for the class's constructor. When prompted for the new project's name call it "CalcTest".


Open the CalcTest project, open AddTest.vb. In the AddTest method you may need to change "Add" to "Add.Add".

Change op1 value to 1, op2 value to 2, expected to 3. Remove the Assert.Inconclusive line.

    <TestMethod()> _
    Public Sub AddTest()
        Dim target As Add.Add = New Add.Add()
        Dim op1 As Integer = 1 
        Dim op2 As Integer = 2         
        Dim expected As Integer = 3 
        Dim actual As Integer
        actual = target.Add(op1, op2)
        Assert.AreEqual(expected, actual)
    End Sub

Make CalcTest the startup project.

Run the application. You will see a Test Results window that looks like this.


Now go back to Calc.Add and change the Add method to replace the "+" with "-" like this.

Public Class Add

    Public Function Add(op1 As Integer, op2 As Integer) As Integer
        Return op1 - op2
    End Function
End Class

Rebuild and run again. The "Test Results" panel will now show an error.


Wednesday, March 22, 2017

Xamarin - getting started

Nothing to do with WPF except they both use XAML. I finally figured out how to get the whole emulator thing working. It's ridiculously difficult.

I installed Visual Studio 2017 Professional when Microsoft released it. It has better integration of Xamarin Studio than 2015 had. This made the process of creating my first Android App in Visual Studio much easier - but still complicated.

I also broke down and bought a book "Xamarin 4.x Cross-Platform Application Development - Third Edition" from Amazon.com. It's not a great book but it has more detail than the online examples I could find.

So here is the list of things I had to do to get my first app working.

I got my copy of VS2017 through my MSDN subscription. You should be able to get a free community version from Microsoft.

Setting up Visual Studio for the first time.

Start Visual Studio 2017 (any version)
Click on Tools -> Android -> Android SDK Manager.



Make sure that the following are installed or checked...
Tools/Android SDK Tools
Tools/Android SDK Platform-tools
Tools/Android SDK Build-tools (as many versions as you want - recommend 23.0.3)
Android 7.1.1 (API 25)/SDK Platform
Android 7.1.1 (API 25)/System images for all devices you want to emulate
Extras/Android Support Repository
Extras/Google USB driver
Extras/Intel Hardware Accelerated Execution Manager

Click [Install packages]

Close Visual Studio.

Now you need to install the hardware accelerator. In File Manager browse to C:\Program Files (x86)\Android\android-sdk\extras\intel\Hardware_Accelerated_Execution_Manager and run intelhaxm-android.exe.

Open Visual Studio again. Now we are ready to develop.

Click on Tools -> Android -> Android Emulation Manager



You may see a different list of virtual devices.

Select "VisualStudio_android-23_arm_phone" and click [Start...]. This launches an Android phone emulator. You will see a "Launch Options" popup. Click [Launch].

You will now see a "Starting Android Emulator" popup. Make sure there are no errors listed. In the example below the only error concerns the audio which I don't care about. You can close it once the green bar is full.



At the same time the emulator will popup and once it has initialized it will look like a phone. It can take as long as five minutes to initialize. While it is initializing it shows the word "android".



When you run your program it will connect to the emulator and run there. It's quicker to have the emulator running before you build. However, the build will start an emulator if you don't have one running but it takes forever.

While the emulator is initializing we can start developing.

In the Visual Studio menu click File -> New -> Project. You will see the "New Project" popup.



In the left panel under Templates select Visual C# and then Android. In the right panel select Single-View App (Android). Enter a project name and click [OK].

This creates a single-view application with some minimal functionality that is useful for us but which will be removed most of the time. After Visual Studio creates the app your screen will look something like this.



You can explore the solution if you want or you can just build and run the application (hit F5). Remember we have the emulator running already showing the clock, icons, etc. If the emulator is not initialized (looking like the image above) you should wait until it is.

The process of connecting takes some time. Your output window and the status bar at the bottom of Visual Studio will show the progress. The status bar will show the following messages as the deployment progresses.

Starting deploy...
Deploying...
Android application is debugging
Ready

It takes fully two minutes for the emulator to start showing this program on my computer and this is a trivial program with the emulator already running. However, eventually, it looks like this.



Click in [HELLO WORLD, CLICK ME!] to test the application.

You have to understand how incredibly slow Xamarin is in its default configuration. I think a lot of the problems I was having is that I'm used to Visual Studio only taking a few seconds from hitting F5 to running even on massive solutions.

Are you old enough to remember the bad old days of batch compiles and builds when we had to wait five or ten minutes before we could start debugging? I'm talking about the HP3000 COBOL compiler and build engine. Xamarin has taken us back to the old days.