11 June 2016

Floating ‘text balloons’ for context relevant information in Xamarin Forms

Picking the challenge apart

An Italian proverb says that a fool can ask more questions than seven wise men can answer – a modern variant could be that a designer can think up more things than a developer can build. I don’t pretend to be the proverbial wise man, and neither do I want to call my designer colleague a fool, and when he came up with the idea of introducing text balloons with context relevant information, floating on top on the rest of the UI, cross-platform, and preferably appearing with a nice animation, I indeed had to do some head scratching.

What he meant was this, and this is exactly how I created it

The issues I had to tackle, were:

  1. How do I create a text balloon in the first place, with a kind of pointy bit pointing to the UI element it belongs to?
  2. How do I get the absolute position of a UI element - that is, the one the user taps?
  3. How do I show the text balloon in situ?

Text balloon 101

tekstballon

This text balloon consists of a translucent grid that ties the components together. It contains two grids, one of them containing the label. The first grid is the bit that points up. This actually is a 15 by 15 square, rotated 45⁰, and moved a little bit to the left using the new Margins property that has finally made it to Xamarin Forms. The second and biggest grid is the green rectangle actually containing the text you want to show. Because it’s in XAML after the square, drawing precedence rules make that it be drawn on top of the first one. You would not see it at all, if is wasn’t for the fact this grid also has a margin - of 7 on the top so about half of the rotated square. The net result, as you can see, is a triangle sticking out of the rectangle, making the optical illusion of a kind of text balloon. In XAML, this looks like this

<Grid x:Name="MessageGridContainer" xmlns="http://xamarin.com/schemas/2014/forms"
           xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
           x:Class="XamarinFormsDemos.Views.Controls.FloatingPopupControl" 
           BackgroundColor="#01000000">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="75*"></ColumnDefinition>
    <ColumnDefinition Width="25*"></ColumnDefinition>
  </Grid.ColumnDefinitions>
  <Grid x:Name="MessageGrid" HorizontalOptions="Start" VerticalOptions="Start" >
    <Grid BackgroundColor="{StaticResource  AccentColor}" HeightRequest="15" WidthRequest="15" 
          HorizontalOptions="End" VerticalOptions="Start" Rotation="45" 
          Margin="0,0,4,0" InputTransparent="True"/>
    <Grid Padding="10,10,10,10" BackgroundColor="{StaticResource AccentColor}" Margin="0,7,0,0" 
          HorizontalOptions="FillAndExpand" InputTransparent="True">
      <Label  x:Name="InfoText" TextColor="{StaticResource ContrastColor}" 
             HorizontalOptions="Center" VerticalOptions="Center" 
             InputTransparent="True"/> 
    </Grid>
  </Grid>
</Grid>

The complete text balloon is contained in the “MessageGrid” grid; the pointy bit upward is emphasized using red and underlining. The complete control is contained within yet another grid “MessageGridContainer”, that fills the whole screen – or at least the part in which the text balloons appear. Is has three functions:

  • It provides a canvas to place the actual text balloons on
  • It is an event catcher – as the user taps ‘anywhere on the screen’ the text balloon disappears
  • It makes sure the text balloon never gets wider than 75% of the screen (this was a designer requirement) – hence the columns.

Some important details to take note of:

  • A few elements have set the property “InputTransparent”  to true. This means they will never receive any events (like tap) but those will be received by the elements lying ‘below’ them. In other words, they don’t block events. This makes the text balloon disappear even when you tap on the text balloon itself, as the events goes downwards and is processed by MessageGridContainer
  • MessageGridContainer itself is not opaque but has BackgroundColor "#01000000", that is, 1% black. For all intents and purposes it is opaque in the sense that you don’t see it, but if you leave it totally opaque is will also be opaque to events - on Windows 10 UWP. A little concession to a cross platform issue.
  • This whole contraption is called “FloatingPopupControl” – this is the control that handles showing, displaying and eventually removing the text balloon. ‘Something’ has to call it’s ShowMessageFor method to tell it what the balloon should contain, and under which control it should appear.We will come to that later

Determining absolute position of the ‘anchor element’

The anchor-element is the element under which the text balloon appear should appear when it’s tapped – in this sample, the i-symbol. It is actually pretty to simple to find the absolute position of a relatively placed element: this is achieved by going recursively upwards via the “Parent” propertie and get the sum of all X values and the sum of all Y values. You can actually find hints to this in the Xamarin developer forumsin the Xamarin developer forums, and I have put this into the following extension method:

using Xamarin.Forms;

namespace Wortell.XamarinForms.Extensions
{
  public static class ElementExtensions
  {
    public static Point GetAbsoluteLocation(this VisualElement e)
    {
      var result = new Point();
      var parent = e.Parent;
      while (parent != null)
      {
        var view = parent as VisualElement;
        if (view != null)
        {
          result.X += view.X;
          result.Y += view.Y;
        }
        parent = parent.Parent;
      }
      return result;
    }
  }
}

Positioning, showing, animating and removing text balloons

If you look at the code in the ShowMessageFor method - in the FloatingPopupControl code behind – you’ll see the code is only deferring to FloatingPopupDisplayStrategy. This is done because it’s not wise to put much code into a user control if you want to re-use part of that intelligence easily. It also makes adapting and changing animations easier. FloatingPopupDisplayStrategy has the following constructor:

public class FloatingPopupDisplayStrategy
{
  private readonly Label _infoText;
  private readonly View _overallView;
  private readonly View _messageView;

  public FloatingPopupDisplayStrategy(Label infoText, View overallView, View messageView)
  {
    _infoText = infoText;
    _overallView = overallView;
    _messageView = messageView;

    _overallView.GestureRecognizers.Add(new TapGestureRecognizer
    { Command = new Command(ResetControl) });
    _overallView.SizeChanged += (sender, args) => { ResetControl(); };
  }
}
  • infoText the text balloon name 
  • overallView is the canvas in which the text ballon is placed; it also receives the tap to remove the text balloon again
  • messageView is het containing grid of the text balloon itself

ShowMessageFor, and it’s little helper ExecuteAnimation, are implemented like this:

public virtual async Task ShowMessageFor(
  VisualElement parentElement, string text, Point? delta = null)
{
  _infoText.Text = text;
  _overallView.IsVisible = true;

  // IOS apparently needs to have some time to layout the grid first
  // Windows needs the size of the message to update first
  if (Device.OS == TargetPlatform.iOS || 
      Device.OS == TargetPlatform.Windows) await Task.Delay(25);
  _messageView.Scale = 0;

  var gridLocation = _messageView.GetAbsoluteLocation();
  var parentLocation = parentElement.GetAbsoluteLocation();

  _messageView.TranslationX = parentLocation.X - gridLocation.X -
                              _messageView.Width + parentElement.Width +
                              delta?.X ?? 0;
  _messageView.TranslationY = parentLocation.Y - gridLocation.Y +
                              parentElement.Height + delta?.Y ?? 0;

  _messageView.Opacity = 1;
  ExecuteAnimation(0, 1, 250);
}

private void ExecuteAnimation(double start, double end, uint runningTime)
{
  var animation = new Animation(
    d => _messageView.Scale = d, start, end, Easing.SpringOut);

  animation.Commit(_messageView, "Unfold", length: runningTime);
}
  • First the text that should be displayed in the text balloon is set
  • Next, the canvas in which the text balloon is placed is made visible. As stated before, it’s nearly invisible, but effectively it intercepts a tap
  • Windows and iOS now need a short timeout for some layout events. This feels a bit VB6’ey, doevents, right?
  • The text balloon is scaled to 0, effectively making it infinitely small (and invisible) 
  • Next, we calculate the text balloon’s current absolute location, as well as the anchor element’s(‘parentElement’) absolute location.
  • X and Y translation of the text balloon are calculated to position the text balloon at a location that will make the pointy bit end up just under the blue i-symbol
  • De message grid’s opacity is set to 1, so now the text balloon is visible (but still infinitely small)
  • A 250 ms bouncy animation (Easing.SpringOut) blows up the text balloon to scale 1 – it’s normal size.

Note: the delta uses in the calculation is a value intended to use as a correction value, in case the standard calculation does not yield the desired result (i.e. location). This will be explained later on.

And finally, the user must be able to dismiss the text balloon. This is done using the ResetControl methods. As we have seen in de constructor, this method gets called in case the user types at the invisible canvas, or if the canvas’ size changes.

private void ResetControl()
{
    if (_messageView.Opacity != 0)
    {
      _messageView.Opacity = 0;
      _overallView.IsVisible = false;
    }
}

This method does not need to be called explicitly at initialization, since he invisible grid changes size at the start of the app (because it gets child elements – the MessageGrid and its children), and the event wiring makes this call happen anyway. Another important reason to attach this method to the SizeChanged event is that in Windows 10 UWP apps windows sizes actually can be changed by the user. This may cause text balloons ending up in what is no longer being the right place, so they need to be removed as well. After all, as long as the text balloon is visible, the invisible background blocks any input, so as soon as the user starts working with the app, in any way, the text balloon needs to disappear and the app needs to be ready again.

Behavior intercepting tap event and relay to control

The only thing missing now is something to get the whole process going – respond to the tap on the i-symbol, providing the text balloon contents, and provide some optional positioning correcting for the text balloon. This is done by FloatingPopupBehavior:

using Wortell.XamarinForms.Behaviors.Base;
using Wortell.XamarinForms.Controls;
using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors
{
  public class FloatingPopupBehavior : BindableBehaviorBase<View>
  {
    private IGestureRecognizer _gestureRecognizer;

    protected override void OnAttachedTo(View bindable)
    {
      base.OnAttachedTo(bindable);
      _gestureRecognizer = new TapGestureRecognizer {Command = new Command(ShowControl)};
      AssociatedObject.GestureRecognizers.Add(_gestureRecognizer);
    }

    protected override void OnDetachingFrom(View bindable)
    {
      base.OnDetachingFrom(bindable);
      AssociatedObject.GestureRecognizers.Remove(_gestureRecognizer);
    }

    private void ShowControl()
    {
      if (AssociatedObject.IsVisible && AssociatedObject.Opacity > 0.01)
      {
        PopupControl?.ShowMessageFor(AssociatedObject, MessageText, new Point(Dx, Dy));
      }
    }

    #region PopupControl Attached Dependency Property      
    public static readonly BindableProperty PopupControlProperty =
      BindableProperty.Create(nameof(PopupControl), 
      typeof (IFloatingPopup), typeof (FloatingPopupBehavior),
        default(IFloatingPopup));


    public IFloatingPopup PopupControl
    {
      get { return (IFloatingPopup) GetValue(PopupControlProperty); }
      set { SetValue(PopupControlProperty, value); }
    }
    #endregion

    //MessageText Attached Dependency Property omitted

    //region Dx Attached Dependency Property omitted     

    //region Dy Attached Dependency Property omitted     

  }
}

This behavior is actually rather simple – as soon as the control tot which is attached is tapped, it calls the ShowMessageFor method of the control referenced in de PopupControl property. There are three additional property for determining which text is actually displayed, and two optional properties for a delta X and delta Y which, as we have seen, are included by the control when it actually places the text balloon on the right place.

Bringing it together in XAML

A simplified excerpt from FloatingPopupPage :

<ScrollView Grid.Row="1"  VerticalOptions="Fill" 
   HorizontalOptions="Fill" Margin="10,0,10,0" >
  <Grid>
    <Grid VerticalOptions="Start">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
      </Grid.RowDefinitions>
      <StackLayout Orientation="Horizontal" HorizontalOptions="Fill" >
        <ContentView  HorizontalOptions="FillAndExpand" VerticalOptions="Start">
          <Entry x:Name="NameEntry" Placeholder="Name" 
                  TextColor="{StaticResource AccentColor}" 
                  PlaceholderColor="{StaticResource SoftAccentColor}" />
        </ContentView>
        <ContentView>
          <Image Source="{extensions:ImageResource info.png}" VerticalOptions="Center"
                 HorizontalOptions="End" 
                 HeightRequest="{Binding Height, Source={x:Reference NameEntry}}">
            <Image.Behaviors>
              <behaviors:FloatingPopupBehavior MessageText="Fill in your name here"
                                               PopupControl="{x:Reference PopupControl}" 
                                               Dx="-6" Dy="4"/>
            </Image.Behaviors>
          </Image>
        </ContentView>
      </StackLayout>

    </Grid>
    <controls:FloatingPopupControl x:Name="PopupControl" VerticalOptions="Fill" 
                                   HorizontalOptions="Fill" />
  </Grid>
</ScrollView>

In red the actual i-symbol with the behavior attached to it, in green the popup control (including the label) itself. In the behavior’s properties we actually specify the text to be displayed as well the PopupControl reference, indicating this is the UI control that should actually handle the displaying of the text balloon. In addition it sports an optional extra delta x and delta y. Of course this could be hard coded into the control, but to have this extra flexibility in design time makes for an easier ‘constructable ’UI. As you can see, as soon as the parts are in place, actually using and re-using the components is pretty easy, making adding floating text balloons with contextual relevant information very easy indeed.

Also notice a neat trick to make sure that especially nice in Android, that sports a great rage of resolutions. I took an intentionally too big picture for the i-symbol, which is automatically sized to the height of the entry by using  HeightRequest="{Binding Height, Source={x:Reference NameEntry}}"

Some consequences of this approach

As stated repeatedly, a (nearly) invisible grid covers the whole screen, or at least part of the screen, while a text balloon is displayed – to give the text balloon space to be placed in, and to intercept a tap so it will be removed as soon as the user starts interacting with the app. The flip side is that the app is effectively blocked until the user taps, and this tap will not do anything but removing the text balloon. A plainly visible button will not respond while the text balloon is visible – that requires yet another tap. This may seem annoying, but I don’t think this will put off the user in any significant amount, as it;s likely he will stop using this functionality pretty soon as he/she has gotten the hang of the app. This is only an onboarding/adopting thing. You read the car’s manual only once (if you do it at all, and then never again unless in very extraordinary circustances)

Conclusion

Using only some pretty basic means, a few nifty tricks and a clear architectural approach it appears to be pretty simple to build a kind of re-usable infrastructure enabling the fast and flexible addition of context relevant information, which is displayed in a visually attractive way. It’s very easy to add text balloons this way, and it’s a useful tool to make onboarding and adoption of an app easier.

As usual, a sample project containing this (and previous code) can be found on GitHub

01 June 2016

Default HoloLens Toolkit occlusion script turns magenta

After following some on-line video’s and snooping through some other’s people code I felt I had to start bottom-up to get at least a feeling for how you setup a HoloLens app – even though I don’t have a HoloLens. Call it my natural curiosity. So I followed my fellow MVP Morten Nielsen’s blog – highly recommended – as he starts with the real basics. Very good if you hardly have a clue what you are doing ;)

In his third post, I ran into a snag. According to his second post, I had unzipped the HoloLens toolkit into the assets folder as instructed, and started to add occlusion to my project. Morten explains in detail what this is – it makes holograms disappear behind physical objects like a wall when they are ‘in front’ of the holograms (which, in reality, they never can be as they are projected on a screen not 2 inch from your eyes, but that is the magic of HoloLens).

So I added a rectangular box as a Hologram, having it stick out of a wall like halfway, so I was supposed only to see the front part. That I did, but I did also see something else:

image

Yikes – that is my box all right, but where did that horrible magenta color come from? To this moment, I still don’t know, but I found out how to fix it.

First of all, it really helps if, in addition to making the settings to your project that Morten describes in his first post, you tick at least “Unity C# project” as well in Build Settings:

image

I do the “Development Build” as well, although I don’t really know if this is neccesary).

This makes all the scripts end up in your Visual Studio solution as well, and what is more, you can debug them. Thus I learned that the script “SpatialMappingRenderer.cs” (in folder Assets\HoloToolkit\SpatialMapping\Scripts\SpatialMappingComponent) looks for materials “Occlusion” and “WireFrame” that are clearly not there.

image

If you debug this script (you can now thanks to the setting above) you will see both OcclusionMaterial and RenderingMaterial end up as null. The materials are in HoloToolkit/SpatialMapping/Materials but changing “HoloToolkit” to “HoloToolkit/SpatialMapping/Materials” does not have any effect.

So I went back to the Unity editor, selected the Spatial Mapping Renderer script again, changed the dropdown “Render mode” from “Occlusion” to “Material”, and that made the property “Render Material” pop up. I found the Occlusion Material on top of it, and it was accepted.

image

If I now debug the code, the Occlusion material is still null, but RenderingMaterial is set, and lo and behold:

image

Now I am seeing what I expect to see – a box sticking out of a wall.

Disclaimer – I am just happily stumbling along trough Unity3D and HoloLens, not hampered by much knowledge of either. I found a problem (that I maybe caused myself) and I fixed it. I hope it helps someone. If I messed up, maybe someone can enlighten me. Unlike some other people, I am not afraid to look a n00b because in this case, that’s just what I am.

The project itself can be found in a good old-fashioned ZIP file as I don’t want to clutter up my GitHub account with every little thing I try at this stage. I am still in the stage of ‘programming by changing things and observing their effects’, so I hope you will humor me. And maybe this will help someone, who knows.

18 May 2016

Generic databinding for MapIcons in Universal Windows Apps

Preface

Ever since I introduced databinding for the UWP map control (and it’s previous incarnations for Windows Phone 8.x and Windows 8.x) I have been asked to write ‘real’ data binding for map shapes. I have patiently tried to explain that the very nature of the map control makes this impossible as the map shapes are not templated controls drawn by the XAML renderer, but drawn by the map control itself – and that is what makes it so fast. So I encouraged people to write their own MapShapeDrawer child classes that converted a view model into a map shape. How hard can it be, I thought. Judging by the number of requests I got, apparently it is hard, indeed, or too inflexible. So I decided to take a shot at creating a more or less generic class for converting view models to MapIcons – the most commonly used scenario. Thus GenericMapIconDrawer was created.

If the previous paragraphs made no sense at all to you, because this is the first time you ever have encountered my map binding library, I encourage you to read this article first.

Demo

image

I have once again updated the demo application that goes with the WpWinNl project. If you first hit Show Area, then hit “Flags” a number of time, you will see the map slowly getting covered with round flags of Belgium, Germany, Italy, Netherlands, Sweden and the UK. Don’t ask me why I choose this particular group of countries – I just did. If you then hit the “Pirate!” button, one of the nation’s flags will turn into Jolly Rogers, the descriptive label will change in “Arrrr!” and the icon will seem to jump upward a little. If you press the “Pirate!” button again, the current Jolly Roger flags will disappear, and another nation is selected to turn into pirates. Unless it selects a nation that has already turned pirate, then nothing will happen. If you press the “Pirate!” button long enough, all nations will turn into pirates and then disappear. Crime does not pay, in the end. At least in this demo. See video below.

The purpose of this – admittedly – rather peculiar demo, which I created when I was entirely sober indeed, is to show that by merely changing properties things change on the map. So even when it is not strictly data binding, it sure acts like data binding is happening. By the way, on my Surface Pro 4 you hardly see the flickering – it seems like my trusted old 2011 i7 970 machine that I used for recording this video is finally showing it’s age.

How the demo works (aka how you should use the new library feature)

There is actually way more code to the demo than the actual changes to the WpWinNl.Maps package comprise. First of all, the base class for my geometry providing view models has been changed so that it's name property is an actual view model property raising a NotifyPropertyChanged, using the standard MVVMLight syntax

public class GeometryProvider : ViewModelBase
{
  private string _name;
  public string Name
  {
    get { return _name; }
    set { Set(() => Name, ref _name, value); }
  }
}

Then there is the FlagList class, a child class of GeometryProvider, that provides a list of randomly located flags from a randomly selected nation within a rectangle defined by two Geopoints

using System;
using System.Collections.Generic;
using Windows.Devices.Geolocation;
using Windows.Foundation;

namespace WpWinNl.MapBindingDemo.Models
{
  public class FlagList : GeometryProvider
  {
    public static readonly string[] Countries =
      { "Belgium", "Germany", "Italy", "Netherlands", "Sweden", "UK" };


    private Uri _iconUri;
    public Uri Icon
    {
      get { return _iconUri; }
      set { Set(() => Icon, ref _iconUri, value); }
    }

    private BasicGeoposition _point;
    public BasicGeoposition Point
    {
      get { return _point; }
      set { Set(() => Point, ref _point, value); }
    }

    private Point _anchorPoint = new Point(0.5, 0.5);
    public Point AnchorPoint
    {
      get { return _anchorPoint; }
      set { Set(() => AnchorPoint, ref _anchorPoint, value); }
    }

    private bool _isVisible = true;
    public bool IsVisible
    {
      get { return _isVisible; }
      set { Set(() => IsVisible, ref _isVisible, value); }
    }

    public static IEnumerable<FlagList> GetRandomFlags(
                          Geopoint point1, Geopoint point2, int nrOfPoints)
    {
      var flags = new List<FlagList>();
      var points = PointList.GetRandomPoints(point1, point2, nrOfPoints);
      var r = new Random(DateTime.Now.Millisecond * 2);
      foreach (var point in points)
      {
        var flagIdx = (int)Math.Round(r.NextDouble() * 5);
        flags.Add(new FlagList
        {
          Name = Countries[flagIdx],
          Icon = new Uri($"ms-appx:///Assets/{Countries[flagIdx]}.png"),
          Point = point.Point
        });
      }
      return flags;
    }
  }
}

Notice here, as well, that all properties are raising INotifyPropertyChanged, that IsVisible is true by default and that we have a default icon anchor point of 0.5, 0.5 – that is, the center of the icon falls on the location specified by "Point". 

On the MapBindingViewModel there is a new public property ObservableCollection<FlagList> Flags that only gets loaded up with initial data in the LoadFlags method

public void LoadFlags()
{
  Flags.AddRange(FlagList.GetRandomFlags(new Geopoint(_area.NorthwestCorner),
    new Geopoint(_area.SoutheastCorner), 50));
}
Which is called when you press the "Flags" button. It add 50 icons every time you press it. Then there is the method that changes a random flag into pirate flags
public void ChangeToPirate()
{
  foreach (var flag in Flags.Where(p => p.Name == "Arrr!").ToList())
  {
    flag.IsVisible = false;
  }

  var r = new Random(DateTime.Now.Millisecond * 2);
  var flagIdx = (int)Math.Round(r.NextDouble() * 5);
  var flagName = FlagList.Countries[flagIdx];)
  foreach (var flag in Flags.Where(p => p.Name == flagName).ToList())
  {
    flag.Name = "Arrr!";
    flag.Icon = new Uri("ms-appx:///Assets/JollyRoger.png");
    flag.AnchorPoint = new Point(0.5, 1);
  }
}

Any existing pirate flags are made invisible first, then new ones are created by setting the Name, the Icon and the AnchorPoint property. Thus the label changes, the icon, and the icon on the map seems to jump up about half it’s size as it’s anchor point is now center/bottom in stead of center/center (boy does that terminology bring back memories of my very first job)

In XAML, things are more or less still the same, with some additions:

<maps:MapControl x:Name="MyMap" Grid.Row="0" 
   ZoomLevel="{x:Bind ViewModel.ZoomLevel, Mode=OneWay}" 
   Center="{x:Bind ViewModel.Center, Mode=OneWay}">
  <interactivity:Interaction.Behaviors>
  
    <mapbinding:MapShapeDrawBehavior LayerName="Flags" 
      ItemsSource="{x:Bind ViewModel.Flags, Converter={StaticResource MapObjectsListConverter}}" 
                    PathPropertyName="Point">
      <mapbinding:MapShapeDrawBehavior.EventToHandlerMappers>
        <mapbinding:EventToHandlerMapper EventName="MapElementClick" 
                                         CommandName="SelectCommand" />
      </mapbinding:MapShapeDrawBehavior.EventToHandlerMappers>
      
      <mapbinding:MapShapeDrawBehavior.ShapeDrawer>
        <mapbinding:GenericMapIconDrawer 
           ImageUriPropertyName="Icon" 
           AnchorPropertyName="AnchorPoint" 
           TitlePropertyName="Name" 
           IsVisiblePropertyName="IsVisible" 
           CollisionBehaviorDesired="RemainVisible"/>
      </mapbinding:MapShapeDrawBehavior.ShapeDrawer>
      
    </mapbinding:MapShapeDrawBehavior>
  </interactivity:Interaction.Behaviors>

</maps:MapControl

As drawer we have the new GenericMapIconDrawer that has a boatload of new properties, basically instructing the GenericMapIconDrawer from which view model properties to get the values that should be applied to the MapIcon it is creating. So, technically, this is still not data binding – the properties are pulled from the view model using reflection. Which means that you really should test this using .NET Native tooling to see if those properties are not pulled out by the compiler – or else suffer the pain I suffered when I tried to publish my app.

How the code works

The GenericMapIconDrawer is surprisingly simple. It's basic setup is like this: a few properties and a CreateShape method that actually creates the Icon from the viewmodel:

using Windows.Devices.Geolocation;
using Windows.Foundation;
using System;
using System.Reflection;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Controls.Maps;

namespace WpWinNl.Maps
{
  public class GenericMapIconDrawer : MapShapeDrawer
  {
    protected object ViewModel;

    protected MapIcon Icon;

    public string TitlePropertyName { get; set; }

    public string AnchorPropertyName { get; set; }

    public string ImageUriPropertyName { get; set; }

    public string IsVisiblePropertyName { get; set; }

    public MapElementCollisionBehavior CollisionBehaviorDesired { get; set; }

    public override MapElement CreateShape(object viewModel, BasicGeoposition pos)
    {
      ViewModel = viewModel;

      Icon = new MapIcon
      {
        Location = new Geopoint(pos),
        CollisionBehaviorDesired = CollisionBehaviorDesired,
        ZIndex = ZIndex
      };

      SetPropertyValuesFromViewModel();

      return Icon;
    }
  }
}

The SetPropertyValuesFromViewModel is pull the additional four properties from the view model (the position is already being taken care of by the MapShapeDrawBehavior itself)

private void SetPropertyValuesFromViewModel()
{
  string title = null;
  if (TryGetPropertyValue(ViewModel, TitlePropertyName, ref title))
  {
    Icon.Title = title;
  }

  Point anchorPoint;
  if (TryGetPropertyValue(ViewModel, AnchorPropertyName, ref anchorPoint))
  {
    Icon.NormalizedAnchorPoint = anchorPoint;
  }

  Uri imageUri = null;
  if (TryGetPropertyValue(ViewModel, ImageUriPropertyName, ref imageUri))
  {
    Icon.Image = RandomAccessStreamReference.CreateFromUri(imageUri);
  }

  bool isVisble = true;
  if (TryGetPropertyValue(ViewModel, IsVisiblePropertyName, ref isVisble))
  {
    Icon.Visible = isVisble;
  }
}

And because I am a lazy b*****d I wrote a little helper method do to the repetitive heavy lifting for that

private static bool TryGetPropertyValue<T>(object obj, string propertyName, 
                                           ref T outValue)
{
  if (!string.IsNullOrWhiteSpace(propertyName))
  {
    var prop = obj.GetType().GetRuntimeProperty(propertyName);
    var result = prop?.GetValue(obj);
    if (result is T)
    {
      outValue = (T) prop.GetValue(obj);
      return true;
    }
  }
  return false;
}

Note, however, that only the position, label text, icon uri, anchor point and visibility are pulled from the view model. Z-index and collisionbehavior are not. Deep down in the MapShapeDrawBehavior, in the CreateShape method, there is another change that I want to draw your attention to:

var evt = viewModel.GetType().GetRuntimeEvent("PropertyChanged");
if (evt != null)
{
  var observable = Observable.FromEventPattern<PropertyChangedEventArgs>(
     viewModel, "PropertyChanged")
    .Subscribe(se =>
    {
      if (!LegacyMode || se.EventArgs.PropertyName == PathPropertyName)
      {
        ReplaceShape(se.Sender);
      }
    });

  TrackObservable(viewModel, observable);
}

Previously, the shape would only be replaced if the geometry changed. Now, unless the new property LegacyMode is set to true, this will happen at every PropertyChanged event. If you look carefully at the video, you will actually see the Jolly Rogers flickering, which is correct – since three properties are changed (Name, Icon and AnchorPoint) each flag is redrawn three times. This is quite inefficient, but unfortunately the way it works. You cannot change an Icon, only replace it. So for every property change it actually gets replaced indeed, and to that extent I also had to make a little change to ReplaceShape itself.

private void ReplaceShape(object viewModel)
{
  var shape = AssociatedObject.MapElements.FirstOrDefault(p => p.ReadData() == viewModel);
  if (shape != null)
  {
    var shapeLocation = AssociatedObject.MapElements.IndexOf(shape);
    if (shapeLocation != -1)
    {
      var newShape = CreateShape(viewModel);
      if (newShape != null)
      {
        // Previous code
        // AssociatedObject.MapElements[shapeLocation] = CreateShape(viewModel); 
        AssociatedObject.MapElements.RemoveAt(shapeLocation);
        AssociatedObject.MapElements.Insert(shapeLocation, newShape);
      }
    }
  }
  else
  {
    AddNewShape(viewModel);
  }
}

So this experiment did not only bring new (or at least easier to use) functionality – it also instilled a bug fix. Of course, you can work around the repeated drawing/flickering by making a view model that does not fire PropertyChanged on every property change, but handle this manually when you are done. But that kind of performance tweaking is outside of the scope of this article.

Concluding remarks

Data binding shapes in the classical way still is not possible, so I had to resort to something that acts like it. I hope this makes using this package for mapping a bit easier. Be advised that for massive changes to large datasets this may not be the most efficient way to get things done, but for your average project it makes things way easier.

It’s now downloadable from NuGet as version 3.0.6, and you can find the sources of the demo app here.

13 May 2016

Keeping input fields above the keyboard in UWP apps

Before you all think I am stark raving mad – it appears that it is actually possible to create XAML constructions that confuse the UWP renderer to such an extent that although it moves the user interface upwards - as it should - it does not always move it up far enough. This can be observed in the video below - as well as the fact that it is fixable.

A user observed this on my app Map Mania (it has been fixed since). I have only been able to repro this on Windows 10 mobile. Apparently it has something to do with going rampant on adaptive triggers, and another key part is the use of an bottom app bar.

The XAML is a simplified version of what I used for the post about a CompositeTrigger-enabled AdaptiveTrigger – basically, I use a simple viewmodel and VisualStateGroup with some Triggers to change what I see on the screen. The XAML is not too complicated:

<Grid >
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto"></RowDefinition>
    <RowDefinition Height="*"></RowDefinition>
  </Grid.RowDefinitions>
  <controls:PageHeader Text="Fix" FontSize="30" VisualStateNarrowMinWidth="0" 
            VisualStateNormalMinWidth="700"></controls:PageHeader >
  <Grid Grid.Row="1" Margin="12" x:Name="TopGrid">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="60"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
      </Grid.RowDefinitions>

      <Grid Margin="0,6,0,6" x:Name="NarrowMenu" >
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto"></ColumnDefinition>
          <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Text="Green" Grid.Row="0"  FontSize="20"
                   Margin="0,0,6,0" Tapped="{x:Bind ViewModel.ToggleDisplay}" >
        </TextBlock>

        <TextBlock Text="Red" Grid.Row="0" Grid.Column="1"  FontSize="20"
                   Margin="6,0,0,0" Tapped="{x:Bind ViewModel.ToggleDisplay}">
        </TextBlock>
        <Grid Height="2" Background="White" Grid.Row="1" Grid.Column="0" Margin="0,0,6,0" 
        x:Name="GreenUnderline"/>
        <Grid Height="2" Background="White" Grid.Row="1" Grid.Column="1" Margin="6,0,0,0" 
        x:Name="RedUnderline"/>
      </Grid>

      <Grid Background="Green" Grid.Row="1" x:Name="GreenArea"></Grid>
      <Grid Background="Red" Grid.Row="1" x:Name="WideRedArea"></Grid>

      <StackPanel Grid.Row="2" Orientation="Vertical" HorizontalAlignment="Stretch" 
         VerticalAlignment="Bottom" >
        <TextBlock  Text="Some label" x:Uid="MapName"  Margin="0,0,0,6"/>
        <TextBox TextWrapping="NoWrap"/>
      </StackPanel>
    </Grid>
  </Grid>
</Grid>

This stuff is based on Template10, but the actual usage is very limited. So first we have some heading, then the menu, then the two areas (green and red) that are used to fill the middle of the screen – it stands in for actual content – and then all the way below, in red and bold, the stackpanel that has some problems, as displayed in the video. When you click on the menu text(“Red” and “Green”) a command in the view model is called that flips a property “TabDisplay”. This triggers the VisualStateManager, which is in fact

The VisualStateManager is actually pretty simple:

<VisualStateManager.VisualStateGroups>
  <VisualStateGroup x:Name="WindowStates" >
    <VisualState x:Name="NarrowState_Red">
      <VisualState.StateTriggers>
        <StateTrigger IsActive="{x:Bind ViewModel.TabDisplay, Mode=OneWay}"/>
      </VisualState.StateTriggers>
      <VisualState.Setters>
        <Setter Target="WideRedArea.Visibility" Value="Visible"></Setter>

        <Setter Target="GreenUnderline.Visibility" Value="Collapsed"></Setter>
      </VisualState.Setters>
    </VisualState>

    <VisualState x:Name="NarrowState_Green">
      <VisualState.StateTriggers>
        <StateTrigger 
          IsActive=
"{x:Bind ViewModel.TabDisplay, Mode=OneWay,Converter={StaticResource BoolInvertConverter}}"/> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="WideRedArea.Visibility" Value="Collapsed"></Setter> <Setter Target="RedUnderline.Visibility" Value="Collapsed"></Setter> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups

So far, so good but when you use a construction like this, and you put anything below it, your might run into issues as I described. Unless you add a little something to the stackpanel:

<StackPanel Grid.Row="2" Orientation="Vertical" HorizontalAlignment="Stretch"
   VerticalAlignment="Bottom" >
  <interactivity:Interaction.Behaviors>
    <behaviors:KeepAboveInputPaneBehavior/>
  </interactivity:Interaction.Behaviors>
  <TextBlock  Text="Some label" x:Uid="MapName"  Margin="0,0,0,6"/>
  <TextBox TextWrapping="NoWrap"/>
</StackPanel

And people who know me won’t be surprised is it actually a behavior again :)

using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Microsoft.Xaml.Interactivity;

namespace WpWinNl.Behaviors
{
  public class KeepAboveInputPaneBehavior : Behavior<FrameworkElement>
  {
    private Thickness _originalMargin;

    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.Loaded += AssociatedObjectLoaded;
      _originalMargin = AssociatedObject.Margin;
    }

    private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      AssociatedObject.Loaded -= AssociatedObjectLoaded;
      InputPane.GetForCurrentView().Hiding += InputPaneHiding;
      InputPane.GetForCurrentView().Showing += InputPaneShowing;
    }

    protected override void OnDetaching()
    {
      InputPane.GetForCurrentView().Hiding -= InputPaneHiding;
      InputPane.GetForCurrentView().Showing -= InputPaneShowing;
    }

    private void InputPaneShowing(InputPane sender, InputPaneVisibilityEventArgs args)
    {
      AssociatedObject.Margin = 
        new Thickness(_originalMargin.Left, _originalMargin.Top, 
        _originalMargin.Right, _originalMargin.Bottom + args.OccludedRect.Height);
    }

    private void InputPaneHiding(InputPane sender, InputPaneVisibilityEventArgs args)
    {
      AssociatedObject.Margin = _originalMargin;
    }
  }
}

When the attached object is loaded, it’s original margins are recorded. When the input pane is showing, the height of the ‘OcculedRect’ is added to it, moving the attached object op to exactly above the input bar.

This is possibly a bug, or the SDK team just never imagined people doing odd things with the Visual State Manager – “A fool may ask more questions in an hour than a wise man can answer in seven years”, right ;). Whatever – I like I tell people: you can moan about things like this or cry foul at Microsoft, but I find it much more fun to try and fix them. QED

A sample solution, with the behavior, can be found here.

25 April 2016

Behavior for view model driven animated popups in Xamarin Forms

Preface

popupIn my previous post I showed the basics for animation behaviors in Xamarin Forms. Here’s another one I made, that in conjunction with some nifty element binding makes for a pretty fluid popup appearing from the middle. Of course, you can use the native alert box but I’d rather want to see is something as displayed to the right, especially with a nice animation. The advantage of such a custom made popup is that is looks much more consistent across platforms, which is a huge win especially for LOB apps.

You can see the behavior in action below:

... and all there is to it...

The actual code is pretty small now all the heavy lifting has been done by the base classes from the previous post:

using Wortell.XamarinForms.Behaviors.Base;
using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors
{
  public class AnimateScaleBehavior : AnimateFoldBehaviorBase
  {
    protected override void Init(bool newValue)
    {
      if (newValue)
      {
        FoldOutPosition = 1;
        FoldInPosition = 0;
        AssociatedObject.Scale = FoldInPosition;
      }
    }

    protected override void ExecuteAnimation(double start, double end, 
                                             uint runningTime)
    {
      var animation = new Animation(
        d => AssociatedObject.Scale = d, start, end, Easing.SinOut);

      animation.Commit(AssociatedObject, "Unfold", length: runningTime,
        finished: (d, b) =>
        {
          if (AssociatedObject.Scale.Equals(FoldInPosition))
          {
            AssociatedObject.IsVisible = false;
          }
        });
    }
  }
}

In stead of animating TranslationX and TranslationY, like in the previous post, now the Scale property is animated from 0 to 1 and back. Attentive readers may have seen something else though - as soon as the animation starts, a grey haze appears over the underlying screen, that only disappears when the animation is done. This is entirely done in XAML - using element binding.

The whole popup sits in MenuControl.xaml – this is a user control

<?xml version="1.0" encoding="utf-8" ?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
      //Rest of namespace stuff omitted
      IsVisible="{Binding IsVisible, Source={x:Reference AnimatedGrid}}">
  <ContentView BackgroundColor="{StaticResource SeeThrough}"
               HorizontalOptions="Fill" VerticalOptions="Fill" >

    <ContentView Padding="20,0,20,0" x:Name="AnimatedGrid" IsVisible="False">
      <ContentView.Behaviors>
        <behaviors:AnimateScaleBehavior IsVisible="{Binding IsMenuVisible}" 
           ViewIsInitialized="{Binding ViewIsInitialized}"/>
      </ContentView.Behaviors>
      <ContentView Style="{StaticResource MenuStyle}" HorizontalOptions="Fill"
                    VerticalOptions="CenterAndExpand" Padding="0,0,0,10">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
          </Grid.RowDefinitions>

          <ContentView  Grid.Row="0" Style="{StaticResource PopupHeaderStyle}">
            <Label Style="{StaticResource PopupHeaderTextStyle}" Text="Popup header" 
                VerticalOptions="Center"/>
          </ContentView>

          <StackLayout Orientation="Vertical" Grid.Row="1">

            <StackLayout Style="{StaticResource ContentStyle}"  Orientation="Vertical">
              <Label Style="{StaticResource MenuTextStyle}" Text="Here be text" />  
              <Label Style="{StaticResource MenuTextStyle}" Text="Here be more text"
                  VerticalOptions="Center"/>
            </StackLayout>

            <ContentView Style="{StaticResource Separator}"></ContentView>

            <StackLayout Style="{StaticResource ContentStyle}"  Orientation="Vertical">
              <Label Style="{StaticResource MenuTextStyle}" Text="Here be text" />
              <Label Style="{StaticResource MenuTextStyle}" Text="Here be more text" />
              <Label Style="{StaticResource MenuTextStyle}" 
                  Text="Here be more whatever UI elements you want" />
            </StackLayout>

            <Grid HeightRequest="10"></Grid>

            <Grid Style="{StaticResource ContentStyle}">
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="10"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
              </Grid.ColumnDefinitions>
                 <Button Text="Ok" Command="{Binding CloseMenuCommand}"></Button>
              <Button Text="Cancel" Grid.Column="2" 
                  Command="{Binding CloseMenuCommand}"></Button>
            </Grid>
          </StackLayout>
        </Grid>
      </ContentView>
    </ContentView>

    <ContentView.GestureRecognizers>
      <TapGestureRecognizer Command="{Binding CloseMenuCommand}"></TapGestureRecognizer>
    </ContentView.GestureRecognizers>
  </ContentView>

</Grid>

I have marked the interesting parts in red and bold. On top you see that the visibility of the control itself is bound to the visibility of the grid called “AnimatedGrid”. This grid is inside a ContentView which has a background color defined by the resource “SeeTrough. This you can find in app.xaml defined as being color #B2000000, aka "70% black" in designer lingo (i.e. black that is 30% transparent). AnimatedGrid is the top level element of the actual popup – it’s visibility is controlled by the AnimateScaleBehavior. As soon as the animation starts by IsVisible flipping value, the AnimatedGrid is first made visible, and it’s parent – and the SeeThrough ContentView – popup into view, only to disappear when the animation is completely finished. This is exactly what I wanted to achieve – the 70% haze turns the focus of the user to the little task at hand now, and also blocks tapping access to whatever UI elements are still visible below it.

Note, there no values supplied for FoldInTime and FoldOutTime so the default values of 150 and 250ms are used. But you can easily change that by adding them to the behavior in XAML.

As a final thing, I have added a TapGestureRecognizer that calls the popup closing command to the 70% gray area as well. This dismisses the popup when you tap outside it, which is exactly what one expects in a mobile app. It’s effectively the same thing as hitting Cancel (and in this case, Ok, as that does nothing but closing the popup as well).

Viewmodel

Hardly worth mentioning, but to make the picture complete:

using Xamarin.Forms;

namespace XamarinFormsDemos.ViewModels
{
  public class PopupViewModel : MenuViewModelBase
  {
    public Command CloseMenuCommand { get; private set; }

    public PopupViewModel() : base()
    {
      CloseMenuCommand = new Command(() => IsMenuVisible = false);
    }
  }
}
Command makes boolean false. So menu disappears again. Doh ;)

Usage

<?xml version="1.0" encoding="utf-8" ?>
<demoViewFramework:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
    // stuff omitted
  <Grid>
    <!-- Page content -->
    <Grid.RowDefinitions>
      <RowDefinition Height="25*"></RowDefinition>
      <RowDefinition Height="75*"></RowDefinition>
    </Grid.RowDefinitions>
    <Grid Row="0" Style="{StaticResource HeaderOutsideStyle}">
      <ContentView Style="{StaticResource ContentStyle}">
        <Label Text="Popup menu" Style="{StaticResource HeaderStyle}"  
        VerticalOptions="CenterAndExpand"></Label>
      </ContentView>
    </Grid>

    <ContentView Grid.Row="1" Style="{StaticResource ContentStyle}" 
        HorizontalOptions="Fill">
      <Label Text="Here be page content" 
        Style="{StaticResource ContentTextStyle}"></Label>

      <ContentView  VerticalOptions="Start" Style="{StaticResource ContentStyle}" >
          <Button Text="Open popup menu" Command="{Binding ToggleMenuCommand}" 
                  HorizontalOptions="Fill" VerticalOptions="Start"></Button>
      </ContentView>
    </ContentView>

    <!-- Menu -->
    <controls:PopupControl Grid.Row="0" Grid.RowSpan="2"></controls:PopupControl>

  </Grid>
</demoViewFramework:BaseContentPage;/Grid>

Once again, the control is added last and convers the whole screen, as to appear over the existing UI.

Some final words

When you are developing cross-platform apps, UI testing is crucial. For instance, while I was making this app, I noticed that when you change this

<Grid 
      //Namespaces omitted
      IsVisible="{Binding IsVisible, Source={x:Reference AnimatedGrid}}">
  <ContentView BackgroundColor="{StaticResource SeeThrough}"
               HorizontalOptions="Fill" VerticalOptions="Fill">

in this:

       
<Grid 
      //Namespaces omitted
      >
  <ContentView IsVisible="{Binding IsVisible, Source={x:Reference AnimatedGrid}}"
               BackgroundColor="{StaticResource SeeThrough}"
               HorizontalOptions="Fill" VerticalOptions="Fill">

you get a visually a identical result on all three platforms covered in the test project. On Android and Windows 10 it even works the same. But if you try to run on iOS, you will notice the “Open popup menu” is not clickable. This is because the outer grid covers the whole screen and is always visible - and although it’s empty, it apparently blocks all user input. As usual, Apple does things different ;)

The demo project – with updated code – can be found here

This article appeared earlier – in Dutch – on the Wortell company blog

13 April 2016

Behaviors for animated scroll-down and slide-in menus in Xamarin Forms

Preface

Some time ago I wrote about a proof-of-concept for viewmodel driven animations using behaviors in Xamarin Forms. In the mean time, time has moved on, so has Xamarin Forms, and the idea I had back then made it into a kind of framework solution I now used professionally. And it’s time to show how it’s done now in detail.

This article is about building animated scroll-into-view menu’s in Xamarin Forms, and to make sure we all understand what that means, I made this little video showing the code in action on an Android emulator, a Windows 10 Universal Windows Platform app, and an iPhone simulator:

Framework

The demo app contains a simple framework (project DemoViewFramework) that supports a number of things crucial to an app sporting viewmodel driven behaviors, that is:

  • facilitation of  MVVM (using MVVMLight)
  • registration of which view belongs to what viewmodel
  • navigation from viewmode to viewmodel (in stead of from page to page)
  • exposing essential events in the life cycle of the page to it’s accompanying viewmodel.

I won’t go into very much detail about the framework – it’s a pretty na├»ve implementation anyway – but there are two key things to take away. The first one is the class BaseContentPage

using Xamarin.Forms;

namespace DemoViewFramework
{
  public class BaseContentPage : ContentPage
  {
    protected override void OnAppearing()
    {
      base.OnAppearing();
      Context?.OnViewAppearing();
    }

    protected override void OnSizeAllocated(double width, double height)
    {
      base.OnSizeAllocated(width, height);
      Context?.OnViewInitialized(true);
    }

    protected override void OnDisappearing()
    {
      base.OnDisappearing();
      Context?.OnViewInitialized(false);
      Context?.OnViewDisappearing();
    }

    private IPageViewModelBase Context => (IPageViewModelBase)BindingContext;
  }
}
This class is intended to be a base class for your pages, and it's sole purpose is to channel the OnAppearing, OnSizeAllocated and OnViewDisappearing events to the object in the Page's BindingContext - that is, the view model. To this intent, every view model should implement the interface IPageViewModelBase so the corresponding methods can be called:
namespace DemoViewFramework
{
  public interface IPageViewModelBase
  {
    void OnViewInitialized(bool value);

    void OnViewAppearing(object state = null );

    void OnViewDisappearing();

    INavigationService NavigationService { get; set; }
  }
}
If you are using your own Navigation framework, you can forget about the NavigationService property. To make implementation easier, there's a base class for view models – which is the second key takeaway. In the demo project is a child class from MVVMLight's ViewModelBase, but of course you are very free to implement your own INotifyPropertyChanged base class as well.
using GalaSoft.MvvmLight;

namespace DemoViewFramework
{
  public class PageViewModelBase : ViewModelBase, IPageViewModelBase
  {
    public virtual void OnViewInitialized(bool value)
    {
      ViewIsInitialized = value;
    }

    private bool _viewIsInitialized;
    public bool ViewIsInitialized
    {
      get { return _viewIsInitialized; }
      set { Set(() => ViewIsInitialized, ref _viewIsInitialized, value); }
    }

    public virtual void OnViewAppearing(object state = null)
    {
      ViewHasAppeared = true;
    }

    public virtual void OnViewDisappearing()
    {
      ViewHasAppeared = false;
    }

    private bool _viewHasAppeared;
    public bool ViewHasAppeared
    {
      get { return _viewHasAppeared; }
      set { Set(() => ViewHasAppeared, ref _viewHasAppeared, value); }
    }

    public INavigationService NavigationService { get; set; }
  }
}
Key thing here is that there are two properties - ViewHasAppeared and ViewIsInitialized - available in every view model and they are set automatically by the framework. Behaviors handling animations need to know when they actually can start doing stuff and they can bind to one of these properties. As I explained in a previous blog post, from Xamarin 2.1 apparently you can only start after OnSizeAllocated, which translates to ViewIsInitialized in the view model, so the initialization code needs to be fired when that property is set to true. The actual activation of the behavior (that is, the animation), needs to come from another property. That may sound complicated, but I assure you it's not that bad. Just read on.

The (base) view model for driving animations

Basically, we need something to kick off the animations. To this extent, we are using this very simple view model

using DemoViewFramework;
using Xamarin.Forms;

namespace XamarinFormsDemos.ViewModels
{
  public class MenuViewModelBase : PageViewModelBase
  {
    private bool _isMenuVisible;

    public MenuViewModelBase()
    {
      ToggleMenuCommand = new Command(() => IsMenuVisible = !IsMenuVisible);
    }

    public bool IsMenuVisible
    {
      get { return _isMenuVisible; }
      set { Set(() => IsMenuVisible, ref _isMenuVisible, value); }
    }

    public Command ToggleMenuCommand { get; private set; }
  }
}

A command that toggles a simple boolean property. I mean, how hard can it be, right?

Leveling the playing field

If you are coming from Windows behaviors, like I do, you are in for a surprise. Xamarin behaviors are a prime example of something that quacks like a duck and walks like a duck, can be a goose after all. Some things are a bit different and – like a goose – can bite you pretty badly. First of all, there is no standard binding context, and there is no AssociatedObject property to easily refer to. This can make things a bit complex when 'translating' behaviors from Windows to Xamarin and back, hence this base class to make sure we have the same - or at least a more similar - base

using System;
using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors.Base
{
  public abstract class BindableBehaviorBase<T> : Behavior<T> 
    where T : VisualElement
  {
    protected T AssociatedObject { get; private set; }

    protected override void OnAttachedTo(T bindable)
    {
      AssociatedObject = bindable;
      bindable.BindingContextChanged += Bindable_BindingContextChanged;
      base.OnAttachedTo(bindable);
    }

    protected override void OnDetachingFrom(T bindable)
    {
      bindable.BindingContextChanged -= Bindable_BindingContextChanged;
      base.OnDetachingFrom(bindable);
      AssociatedObject = null;
    }

    private void Bindable_BindingContextChanged(object sender, EventArgs e)
    {
      if (AssociatedObject != null)
      {
        BindingContext = AssociatedObject.BindingContext;
      }
    }
  }
}
It's a bit of an oddball class, but it's purpose is simple - after this has run, you can bind to any of the behavior's dependency properties, and in your code you can refer to a typed AssociatedObject, just like you would in Windows XAML behaviors.

Getting a bit animated

Now earlier in this blog post I mentioned the fact that behaviors doing animations probably need to know when a view is ready initializing, so they can initialize themselves. To get to this point, there is this base class on top of  BindableBehaviorBase, with a similar ‘original name:

using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors.Base
{
  public abstract class ViewInitializedBehaviorBase<T> : BindableBehaviorBase<T> 
    where T : VisualElement
  {
    #region ViewIsInitialized Attached Dependency Property      
    public static readonly BindableProperty ViewIsInitializedProperty =
       BindableProperty.Create(nameof(ViewIsInitialized), typeof(bool), 
       typeof(ViewInitializedBehaviorBase<T>),
       default(bool), BindingMode.TwoWay,
       propertyChanged: OnViewIsInitializedChanged);

    public bool ViewIsInitialized
    {
      get { return (bool)GetValue(ViewIsInitializedProperty); }
      set { SetValue(ViewIsInitializedProperty, value); }
    }

    private static void OnViewIsInitializedChanged(BindableObject bindable, 
            object oldValue, object newValue)
    {
      var thisObj = bindable as ViewInitializedBehaviorBase<T>;
      thisObj?.Init((bool)newValue);
    }

    #endregion

    protected abstract void Init(bool viewIsInitialized);
  }
}

When you bind the behavior’s ViewIsInitialized property to the viewmodel’s ViewIsInitialized property, the behavior knows ‘when the view is ready (or not anymore). Whatever, if the property in the view model changes, the behavior calls its (now abstract) method Init.

So what happens is

BaseContentPage –> PageViewModelBase –> (property binding) ViewInitializedBehaviorBase.Init –>
  Concrete Implementation.Init

And finally - menu animations

One more base class to go. I noticed pretty soon that animations handling the folding or scrolling of things almost always consider one property to be animated. So I created a yet another base class

using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors.Base
{
  public abstract class AnimateFoldBehaviorBase : 
    ViewInitializedBehaviorBase<View>
  {
    protected double FoldInPosition;
    protected double FoldOutPosition;

    protected VisualElement GetParentView()
    {
      var parent = AssociatedObject as Element;
      VisualElement parentView = null;
      if (parent != null)
      {
        do
        {
          parent = parent.Parent;
          parentView = parent as VisualElement;
        } while (parentView?.Width <= 0 && parent.Parent != null);
      }

      return parentView;
    }

    protected override void OnAttachedTo(View bindable)
    {
      base.OnAttachedTo(bindable);
      bindable.IsVisible = false;
    }

    private void ExecuteAnimation(bool show)
    {
      if (show)
      {
        AssociatedObject.IsVisible = true;
        ExecuteAnimation(FoldInPosition, FoldOutPosition, (uint)FoldOutTime);
      }
      else
      {
        ExecuteAnimation(FoldOutPosition, FoldInPosition, (uint)FoldInTime);
      }
    }

    protected abstract void ExecuteAnimation(double start, double end, 
      uint runningTime);

    public static readonly BindableProperty IsVisibleProperty =
       BindableProperty.Create(nameof(IsVisible), typeof(bool), 
       typeof(AnimateFoldBehaviorBase),
       false, BindingMode.OneWay,
       propertyChanged: OnIsVisibleChanged);

    public bool IsVisible
    {
      get { return (bool)GetValue(IsVisibleProperty); }
      set {SetValue(IsVisibleProperty, value); }
    }

    private static void OnIsVisibleChanged(BindableObject bindable, 
       object oldValue, object newValue)
    {
      var thisObj = bindable as AnimateFoldBehaviorBase;
      thisObj?.ExecuteAnimation((bool)newValue);
    }   

    // FoldInTime Attached Dependency Property      
 
    // FoldOutTime Attached Dependency Property      
  }
}

This class handles a few important things. First of all, it gets the parent view – in a menu’s case the page. In fact, it finds the first object that has a width, thus a size. This is the behavior’s ‘playing field’. Then there is the ExecuteAnimation method, that actually starts the animation – or reverses it. Note also that the behavior actually hides the element that it’s animating – this whole setup assumes you will move something into view, while it’s not in view initially. You might notice that the Init method which is abstract in it's parent class is still not implemented. That happens only in the concrete class - that is actually pretty small, now all the ground work has been laid:

using Wortell.XamarinForms.Behaviors.Base;
using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors
{
  public class AnimateSlideDownBehavior : AnimateFoldBehaviorBase
  {
    protected override void Init(bool newValue)
    {
      if (newValue)
      {
        var parentView = GetParentView();
        if (parentView != null)
        {
          FoldInPosition = -parentView.Height;
          AssociatedObject.TranslationY = FoldInPosition;
        }
      }
    }

    protected override void ExecuteAnimation(double start, 
       double end, uint runningTime)
    {
      var animation = new Animation(
        d => AssociatedObject.TranslationY = d, start, end, Easing.SinOut);

      animation.Commit(AssociatedObject, "Unfold", length: runningTime,
        finished: (d, b) =>
        {
          if (AssociatedObject.TranslationY.Equals(FoldInPosition))
          {
            AssociatedObject.IsVisible = false;
          }
        });
    }
  }
}

So on Init, the menu is moved up exactly it’s own height and - assuming it’s on top of the page - it will appear just outside the view. The actual animation code itself is laughably simple – it just animates over TranslationY, and when it find it’s ending at the FoldInPosition, it will make the animated object invisible

All that’s left now is defining the menu in XAML and then adding the behavior to it. It’s important to add the menu after the actual page content so it will be drawn over it. Otherwise it will just slide behind it, and that’s not very useful.

<!-- Menu -->
<ContentView Grid.Row="0" Grid.RowSpan="2">
  <ContentView.Behaviors>
    <behaviors:AnimateSlideDownBehavior
      IsVisible="{Binding IsMenuVisible}"
      ViewIsInitialized="{Binding ViewIsInitialized}"
      FoldOutTime="400" FoldInTime="300"/>
  </ContentView.Behaviors>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="75*"></RowDefinition>
      <RowDefinition Height="25*"></RowDefinition>
    </Grid.RowDefinitions>
    <ContentView Style="{StaticResource MenuStyle}">
      <StackLayout Style="{StaticResource ContentStyle}" 
        Orientation="Vertical" VerticalOptions="Start">
        <Image Source="chevronup.png" HeightRequest="40" 
          VerticalOptions="Start" HorizontalOptions="Start">
          <Image.GestureRecognizers>
            <TapGestureRecognizer Command="{Binding ToggleMenuCommand}"/>
          </Image.GestureRecognizers>
        </Image>
        <Label Text="Menu" Style="{StaticResource HeaderStyle}"
          VerticalOptions="Start"/>
        <Label Text="Here be menu content" 
         Style="{StaticResource MenuTextStyle}"></Label>
      </StackLayout>
    </ContentView>

    <!-- Leftover space -->
    <ContentView Grid.Row="1"  HorizontalOptions="Fill" VerticalOptions="Fill">
      <ContentView.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding ToggleMenuCommand}"/>
      </ContentView.GestureRecognizers>
    </ContentView>
  </Grid>
</ContentView>

Note the binding of ViewIsInitialized to ViewIsInitialized for the initialization, and of IsVisible to IsMenuVisible for the actual execution of the animation. Also notice the FoldOutTime and FoldInTime – the menu will fold out in 400, and fold in in 300ms. These are optional values – if you don’t specify them, the behavior will use default values. Finally, note the fact that the menu actually covers the whole screen, but the lower part is empty. Yet, if you tap that part, the menu will move away as well, as you would expect in an app.

The AnimateSlideInBehavior is nearly the same but animates TranslateX – just have a look at the sample code, it’s yours for the taking

Some concluding remarks

Of course you can also code these animations into code behind or in a control’s code behind, but that way you will need to copy and paste code into your views time and time again, and potentially you will need to fix the same bugs on multiple places. What I usually do is indeed make a control, but use the behavior in that and then re-use the control over multiple pages. Using a behavior decouples the code from a specific control or page, and you can re-use the dynamic behavior everywhere.

One final bit – the Xamarin iOS linker is still sometimes ‘optimizing’ away code, just like .NET Native, something that already bit me last year. So in the Wortell.XamarinForms there is this extremely odd class Initializer:

namespace Wortell.XamarinForms
{
  public static class Initializer
  {
    public static void Init()
    {
    }
  }
}
that indeed does completely nothing, except for being called from the iOS project's AppDelegate
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();
    global::Wortell.XamarinForms.Initializer.Init();
    LoadApplication(new App());

    return base.FinishedLaunching(app, options);
}
Which still does nothing, but  tricks the compiler into thinking the code is actually used (which is true) so the assembly actually gets packaged with the app.

I hope this blog will inspire you to try your hand at animations with Xamarin Forms as well. It’s actually pretty easy when you get the hang of it. And there is more to come on this blog.

Parts of this text appeared earlier on the Wortell company blog, in Dutch

30 March 2016

Xamarin Forms 2.1 upgrade–some surprises (and how to get around it)

Back in June I wrote about a proof-of-concept for view model driven animations using behaviors in Xamarin Forms. This concept made it into my employer’s standard Xamarin library (more about that soon) but after an upgrade I noticed the animation behaviors did not work anymore – at least on Android. I have no idea what happened along the way, but fact is that the the behavior no longer worked after upgrading to Xamarin Forms 2.1. A prolonged debugging session learned me the following:

  1. At the ViewAppearing stage user interface elements don’t have a size allocated yet – whereas they used to have that.
  2. A parent object - especially something a grid – does not does not necessarily has to have it’s size set yet (width and height are still –1)

I will write about this soon in more detail, but what needs to be done to get this working again is:

  1. We need to initialize the behavior on the page’s OnSizeAllocated, not the OnViewAppearing method
  2. We need to go up recursively until we find an element that does not have a size of –1 in stead of blindly taking the parent.

So, in StartPage.xaml.cs:

protected override void OnAppearing()
{
  Context.OnViewAppearing();
  base.OnAppearing();
}

protected override void OnSizeAllocated(double width, double height)
{
  Context.OnViewAppearing();
  base.OnSizeAllocated(width, height);
}

It is a bit of a kludge, but it will do for now. In FoldingPaneBehavior we will first need to add a method to recursively find a parent with a size:

protected VisualElement GetParentView()
{
  var parent = associatedObject as Element;
  VisualElement parentView = null;
  if (parent != null)
  {
    do
    {
      parent = parent.Parent;
      parentView = parent as VisualElement;
    } 
    while (parentView?.Width <= 0 && parent.Parent != null);
  }

  return parentView;
}
And the first line of the Init method neededs to be changed from
var p = associatedObject.ParentView;
to
var p = GetParentView();

And then the behavior will work again.

A second surprise when upgrading to Xamarin Forms 2.1 is that the declaration of Dependency properties using generics is deprecated. It will still work, but not for long. So in stead of

public static readonly BindableProperty IsPopupVisibleProperty =
       BindableProperty.Create<FoldingPaneBehavior, bool>(t => t.IsPopupVisible,
       default(bool), BindingMode.OneWay,
       propertyChanged: OnIsPopupVisibleChanged);
You will know have to use
public static readonly BindableProperty IsPopupVisibleProperty =
       BindableProperty.Create(nameof(IsPopupVisible), typeof(bool), 
       typeof(FoldingPaneBehavior),
       default(bool), BindingMode.OneWay,
       propertyChanged: OnIsPopupVisibleChanged);
And the callback loses its type safety because that becomes
private static void OnIsPopupVisibleChanged(BindableObject bindable, object oldValue, object newValue)
in stead of
private static void OnIsPopupVisibleChanged(BindableObject bindable, bool oldValue, object bool)

and thus you have to cast oldValue and newValue to bool. This makes the code inherently more brittle and harder to read, but I assume there is a good reason for this. I have updated the xadp snippet for creating these properties accordingly.

A typical case of moved cheese, but fortunately not too far. The updated demo solution is still on GitHub