Showing posts with label Silverlight. Show all posts
Showing posts with label Silverlight. Show all posts

13 April 2011

Preventing objects made invisible by data binding to show up initially

Currently I am in the process of writing a small Windows Phone 7 game . It sports a ‘game over’ panel with a ‘play again’ button and it’s visibility is controlled by a boolean value ‘IsGameOver’ in the ViewModel. My VisibilityConverter converts the true and false to Visiblity.Visible and Visibility.Collapsed.

Although this works well, data binding apparently takes place only after the complete GUI is loaded, for when the game starts, you see the panel for about half a second before it disappears again. Ugh.

I did not actually solve this problem, but found a workaround in the following little behavior:

using System.Windows;
using System.Windows.Interactivity;

namespace Wp7nl.Behaviors
{
  public class SetInitialOpacityBehavior : Behavior<FrameworkElement>
  {
    protected override void OnAttached()
    {
      AssociatedObject.Loaded += AssociatedObjectLoaded;
      base.OnAttached();
      AssociatedObject.Opacity = InitialOpacity;
    }

    void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      AssociatedObject.Loaded -= AssociatedObjectLoaded;
      AssociatedObject.Opacity = OpacityAfterLoading;
    }

    #region InitialOpacity
    public const string InitialOpacityPropertyName = "InitialOpacity";

    public int InitialOpacity
    {
      get { return (int)GetValue(InitialOpacityProperty); }
      set { SetValue(InitialOpacityProperty, value); }
    }

    public static readonly DependencyProperty InitialOpacityProperty = 
	  DependencyProperty.Register(
        InitialOpacityPropertyName,
        typeof(int),
        typeof(SetInitialOpacityBehavior),
        new PropertyMetadata(0));
    #endregion

    #region OpacityAfterLoading
    public const string OpacityAfterLoadingPropertyName = "OpacityAfterLoading";

    public int OpacityAfterLoading
    {
      get { return (int)GetValue(OpacityAfterLoadingProperty); }
      set { SetValue(OpacityAfterLoadingProperty, value); }
    }

    public static readonly DependencyProperty OpacityAfterLoadingProperty = 
	DependencyProperty.Register(
      OpacityAfterLoadingPropertyName,
      typeof(int),
      typeof(SetInitialOpacityBehavior),
      new PropertyMetadata(1));
    
    #endregion
  }
}

It basically sets the opacity to 0 when the behavior is attached to the FrameworkElement (which apparently takes place before the element is actually displayed), thereby making the element invisible. After the Loading event has fired, it sets the opacity to the value of the dependency property OpacityAfterLoading - which is default 1. Apparently this takes place after data binding. Whatever – my ‘game over’ panel does not show up at the start anymore, but only after the game ends. Like it should.

So if you notice an element showing up initially while it should not, just fire up Blend, drag this behavior on top of it and be done with it.

This behavior is by no means Windows Phone 7 specific – it would work perfectly in Silverlight and probably WPF too. Be aware, however, that although OpacityAfterLoading is a dependency property, in Windows Phone 7 you cannot data bind it, because it’s currently based on Silverlight 3 which only allow you to bind to classes that do descend from DependencyObject – which a behavior obviously does not ;-)

One final concluding note – although this behavior is in the Wp7nl namespace, it currently not in the Wp7nl library nor the NuGet package. But it will be in the next ;-)

08 March 2011

Simple Windows Phone 7 / Silverlight drag/flick behavior

In this post I describe a behavior that will essentially make anything draggable and ‘flickable’, that is, you can drag a GUI element along with your finger and it seems to have a little inertia when you let it go. The behavior mimics part of the Microsoft Surface’s ScatterView functionality and will feature in the small children’s game for Windows Phone 7 game I am currently developing. As a bonus, you will see some generally applicable extension methods for creating storyboards and translation GUI elements too.

I have not tried this in Silverlight but I am not doing anything Windows Phone 7 specific as far as I am aware, so I guess it should work in plain Silverlight too.

Setting the stage

I created a simple solution containing three assemblies: a Windows Phone application, a class library “LocalJoost” holding my utilities including this behavior, and one containing Phone.Fx.Preview, not for the BindableApplicationBar this time, but because I make extensive use of its VisualTreeHelperExtensions. You will also need System.Windows.Interactivity.dll, which I nicked from the MVVMLight toolkit.

Extensions for FrameworkElement

First of all, I created a set of extension methods on FrameworkElement that allow me to easily find and manipulate an attached CompositeTransform from code. A CompositeTransform is only one of the many possibilities to manipulate a GUI element, but it basically is just a descriptor of how far from its original place the GUI element should be drawn, if it should be rotated, scaled, etc. I only use the translation attributes – to move it around.

To be able to translated and animated, the object must have a CompositeTransform. Well, at least it must for the solution I have chosen. You will see later that the behavior checks for this transform and if it’s not present, it simply makes it. But you can also create it in XAML, of course.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Linq;
using Phone7.Fx.Preview;

namespace LocalJoost.Utilities
{
  /// <summary>
  /// Class to help animations from code using the CompositeTransform
  /// </summary>
  public static class FrameworkElementExtensions
  {
    /// <summary>
    /// Finds the composite transform either direct
    /// or as part of a TransformGroup
    public static CompositeTransform GetCompositeTransform(
       this FrameworkElement fe)
    {
      if (fe.RenderTransform != null)
      {
        var tt = fe.RenderTransform as CompositeTransform;
        if( tt != null) return tt;

        var tg = fe.RenderTransform as TransformGroup;
        if (tg != null)
        {
          return tg.Children.OfType<CompositeTransform>().FirstOrDefault();
        }
      }
      return null;
    }

    /// <summary>
    /// Gets the point to where FrameworkElement is translated
    /// </summary>
    public static Point GetTranslatePoint(this FrameworkElement fe)
    {
      var translate = fe.GetCompositeTransform();
      if (translate == null) 
         throw new ArgumentNullException("CompositeTransform");

      return new Point(
        (double) translate.GetValue(CompositeTransform.TranslateXProperty),
        (double) translate.GetValue(CompositeTransform.TranslateYProperty));

    }

    /// <summary>
    /// Translates a FrameworkElement to a new location
    /// </summary>
    public static void SetTranslatePoint(this FrameworkElement fe, Point p)
    {
      var translate = fe.GetCompositeTransform();
      if (translate == null) 
         throw new ArgumentNullException("CompositeTransform");

      translate.SetValue(CompositeTransform.TranslateXProperty, p.X);
      translate.SetValue(CompositeTransform.TranslateYProperty, p.Y);
    }

    /// <summary>
    /// Translates a FrameworkElement to a new location
    /// </summary>
    public static void SetTranslatePoint(
       this FrameworkElement fe, double x, double y )
    {
      fe.SetTranslatePoint(new Point(x, y));
    }

    public static FrameworkElement GetElementToAnimate( 
      this FrameworkElement fe)
    {
      var parent = fe.GetVisualParent();
      return parent is ContentPresenter ? parent : fe;
    }
  }
}

Note in the last method that the element to animated is either the element itself or a ContentPresenter that is its parent. I noticed that stuff created by databinding is always surrounded by a ContentPresenter, and that animating an object within its ContentPresenter does not work – logically. So then then the ContentPresenter is animated in stead – dragging its contents with it.

Extensions for StoryBoard

Next up, a set of extension methods to easily create translation animations, and add them to storyboard:

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace LocalJoost.Utilities
{
  public static class StoryboardExtensions
  {
    /// <summary>
    /// Create a animation with a default easing function
    /// </summary>
    public static Timeline CreateDoubleAnimation(this Storyboard storyboard,
      Duration duration, double from, double to)
    {
      return storyboard.CreateDoubleAnimation(duration, from, to,
        new SineEase
        {
          EasingMode = EasingMode.EaseInOut
        });
    }

    /// <summary>
    /// Create a animation with a custom easing function
    /// </summary>
    public static Timeline CreateDoubleAnimation(this Storyboard storyboard, 
      Duration duration, double from, double to, IEasingFunction easingFunction)
    {
      var animation = new DoubleAnimation
      {
        From = from,
        To = to,
        Duration = duration,
        EasingFunction = easingFunction
      };
      return animation;
    }

    /// <summary>
    /// Add an animation to an existing storyboard
    /// </summary>
    public static void AddAnimation(this Storyboard storyboard, 
      DependencyObject item, Timeline t, DependencyProperty p)
    {
      if (p == null) throw new ArgumentNullException("p");
      Storyboard.SetTarget(t, item);
      Storyboard.SetTargetProperty(t, new PropertyPath(p));
      storyboard.Children.Add(t);
    }

    /// <summary>
    /// Add a translation animation to an existing storyboard with a 
    /// default easing function
    /// </summary>
    public static void AddTranslationAnimation(this Storyboard storyboard, 
      FrameworkElement fe, Point from, Point to, Duration duration)
    {
      storyboard.AddTranslationAnimation(fe, from, to, duration, null);
    }

    /// <summary>
    /// Add a translation animation to an existing storyboard with a 
    /// custom easing function
    /// </summary>
    public static void AddTranslationAnimation(this Storyboard storyboard, 
      FrameworkElement fe, Point from, Point to, Duration duration,
      IEasingFunction easingFunction)
    {
      storyboard.AddAnimation(fe.RenderTransform,
             storyboard.CreateDoubleAnimation(duration, from.X, to.X, 
                                              easingFunction),
              CompositeTransform.TranslateXProperty);
      storyboard.AddAnimation(fe.RenderTransform,
            storyboard.CreateDoubleAnimation(duration, from.Y, to.Y, 
                                             easingFunction),
              CompositeTransform.TranslateYProperty);
    }
  }
}

You will probably only use the last two methods. Create an empty StoryBoard, pop in a FrameworkElement, the “from” and “to” point, the time it should take and optionally an easing function, then start your StoryBoard and off your GUI element goes.

An easing function, by the way, describes a bit about the way an objects starts, stops and moves. No easing function means the object moves from start to end with a constant speed. If you take a SineEase with an EaseInOut mode, it will first move slowly, than increase speed, and at the end it more or less slowly comes to a halt. This makes for a more fluid behavior. There are about a gazillion easing functions, and I encourage you to explore them with Expression Blend.

The DragFlickBehavior itself

Then finally the behavior itself, which is surprisingly simple now all the groundwork has been laid by the extension methods:

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
using LocalJoost.Utilities;

namespace LocalJoost.Behaviors
{
  public class DragFlickBehavior: Behavior<FrameworkElement>
  {
    private FrameworkElement _elementToAnimate;
    
    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.Loaded += AssociatedObjectLoaded;
      AssociatedObject.ManipulationDelta += 
         AssociatedObjectManipulationDelta;
      AssociatedObject.ManipulationCompleted += 
         AssociatedObjectManipulationCompleted;
    }
    
    void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      _elementToAnimate = AssociatedObject.GetElementToAnimate();
      if (! (_elementToAnimate.RenderTransform is CompositeTransform))
      {
        _elementToAnimate.RenderTransform = new CompositeTransform();
        _elementToAnimate.RenderTransformOrigin = new Point(0.5, 0.5);
      }
    }

    void AssociatedObjectManipulationDelta(object sender, 
        ManipulationDeltaEventArgs e)
    {
      var dx = e.DeltaManipulation.Translation.X;
      var dy = e.DeltaManipulation.Translation.Y;
      var currentPosition = _elementToAnimate.GetTranslatePoint();
      _elementToAnimate.SetTranslatePoint(currentPosition.X + dx, 
         currentPosition.Y + dy);
    }

    private void AssociatedObjectManipulationCompleted(object sender, 
      ManipulationCompletedEventArgs e)
    {
      // Create a storyboard that will emulate a 'flick'
      var currentPosition = _elementToAnimate.GetTranslatePoint();
      var velocity = e.FinalVelocities.LinearVelocity;
      var storyboard = new Storyboard { FillBehavior = FillBehavior.HoldEnd };

      var to = new Point(currentPosition.X + (velocity.X / BrakeSpeed),
        currentPosition.Y + (velocity.Y / BrakeSpeed));
      storyboard.AddTranslationAnimation(
        _elementToAnimate, currentPosition, to, 
        new Duration(TimeSpan.FromMilliseconds(500)), 
        new CubicEase {EasingMode = EasingMode.EaseOut});
      storyboard.Begin();
    }


    protected override void OnDetaching()
    {
      AssociatedObject.Loaded -= AssociatedObjectLoaded;
      AssociatedObject.ManipulationCompleted -= 
          AssociatedObjectManipulationCompleted;
      AssociatedObject.ManipulationDelta-= 
          AssociatedObjectManipulationDelta;

      base.OnDetaching();
    }

    #region BrakeSpeed
    public const string BrakeSpeedPropertyName = "BrakeSpeed";

    /// <summary>
    /// Describes how fast the element should brake, i.e. come to rest,
    /// after a flick. Higher = apply more brake ;-)
    /// </summary>
    public int BrakeSpeed
    {
      get { return (int)GetValue(BrakeSpeedProperty); }
      set { SetValue(BrakeSpeedProperty, value); }
    }

    public static readonly DependencyProperty BrakeSpeedProperty = 
      DependencyProperty.Register(
      BrakeSpeedPropertyName,
      typeof(int),
      typeof(DragFlickBehavior),
      new PropertyMetadata(10));

    #endregion
  }
}

And half of it is a dependency property, too ;-). What this behavior does, is:

  • When attached, it checks which element to animate, checks for an CompositeTransform to be present and if not, simply create it.
  • While the element is being manipulated, change the translate point by the delta x and y of the drag event, therefore the element will follow your finger – and causing the element apparently to be ‘dragged’
  • When you stop dragging, it creates a simple storyboard that will move the element a bit further in the direction you last dragged it, seemingly causing a bit on inertia. It does that by looking at the FinalVelocities’s LinearVelocity property values. If you move it fast enough, it will move right out of the screen, never to return ;-). That’s more or less what I mean by ‘flick’. The BrakeSpeed property of the behavior determines how fast the element comes to a half. I find the default value 10 to have quite a natural feeling to it – on my phone, that is.

Demo

For the sample solution I fired up Expression Blend, dragged some elements on a Windows Phone 7 page and attached the behavior to everything - up to the page name and application name, as you can see in the demo video. That is not particularly useful in itself, but it drives home the point. Your designer can now have fun with this behavior ;-), and I hope I have showed you some little things on how to manipulate storyboard and elements from code in the process.

The DragFlickBehavior in action

19 February 2011

Converter to convert a single item to a list to enable Bing Maps binding

Some things are so simple that I wonder if I am missing something. I tried to bind a single GeoPosition to Bing Maps’ MapItemsControl ItemsSource on Windows Phone 7 and it did not work. Apparently Bing Maps want a list. So I wrote the following converter:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Data;

namespace LocalJoost.Convertors
{
  public class ToListConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, 
                          CultureInfo culture)
    {
      var l = new List<object> ();
      if (value != null) l.Add(value);
      return l;
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
                              CultureInfo culture)
    {
      return null;
    }
  }
}
A masterpiece of complicated coding, right :-/ ? Anyway, in stead of
<Microsoft_Phone_Controls_Maps:MapItemsControl 
  ItemsSource="{Binding GpsLocation}">
I used
<Microsoft_Phone_Controls_Maps:MapItemsControl 
  ItemsSource="{Binding GpsLocation, Converter={StaticResource ToListConverter}}">

and Lo and Behold – now I did get my single icon on the map.

I have no idea if this is a quirk of the Bing Maps control or that ItemsSource attributes in general only accept lists – but either way, this works.

11 February 2011

A Blendable Windows Phone 7 / Silverlight clipping behavior

If you are moving images around on a Canvas, you don’t want to show them when they get, for example, a negative Y-coordinate – for else they move outside of the Canvas and over what is above that. I’ve seen numerous examples of clipping ‘utilities’ that basically all ape this article on The Code Project. For some reason no-one seems to have thought about making it a real behavior that can be used from Blend. So after a short discussion over Twitter and DM with Thomas Woo (aka @codechinchilla) I decide to do my own take.

It’s not exactly rocket science:

using System.Windows;
using System.Windows.Media;
using System.Windows.Interactivity;

namespace LocalJoost.Behavior
{
  public class ClipToBoundsBehavior: Behavior<FrameworkElement>
  {
    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.SizeChanged += AssociatedObjectSizeChanged;
      AssociatedObject.Loaded += AssociatedObjectLoaded;
    }

    protected override void OnDetaching()
    {
      AssociatedObject.SizeChanged -= AssociatedObjectSizeChanged;
      AssociatedObject.Loaded -= AssociatedObjectLoaded;
      base.OnDetaching();
    }

    void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      SetClip();
    }

    void AssociatedObjectSizeChanged(object sender, SizeChangedEventArgs e)
    {
      SetClip();
    }

    private void SetClip()
    {
      AssociatedObject.Clip = new RectangleGeometry
      {
        Rect = new Rect(0, 0, 
          AssociatedObject.ActualWidth, AssociatedObject.ActualHeight)
      };
    }
  }
}

…and it’s even less code than the original sample. Drag this thingie in Blend on your Canvas - or whatever other framework element whose children you want to display only within its bounds - and you’re done. Or your designer is done.

I made this for a little Windows Phone 7 App I am currently working on, but it will pretty much work in plain Silverlight, and I am pretty sure it will work on WPF (and thus Microsoft Surface) as well.

30 January 2011

ViewModel driven animations using the Visual State Manager, DataStateBehavior and Expression Blend

Leon Zandman, fellow member of the Dutch Windows Phone 7 developer community, told me that my MapMania app used too much screen real estate for secondary controls and too little for the actual map, especially in landscape mode. I got the same feedback within 24 hours of app certification from one NY Chua from Singapore, who apparently had bought MapMania (which shows, by the way, that something like the Standard About Page does work very well for user/customer feedback). So it was time to do something.

I decided to make the page title collapsible by the user, using a nice animation. And of course I wanted this to be a setting that could be tombstoned, and I should be able to drive the animation from my ViewModel. Since  Windows Phone 7 does not support DataTriggers, there are some challenges, but they are easy to overcome. I will demonstrate my solution and the technique I used by making a small application from scratch, for I like to give complete examples – so you won’t have to hunt for the missing clues. You will pick up some Blend knowledge long the way as well. I hope.

Start out the usual way:

  • Create a new Windows Phone 7 application – this one I called “ModelDrivenAnimations”
  • Add a Solution Folder “Binaries”
  • Put “GalaSoft.MvvmLight.WP7.dll”, “GalaSoft.MvvmLight.Extras.WP7.dll” and “System.Windows.Interactivity.dll” in it.

And this time add a fourth assembly: Expression.Samples.Interactivity.dll. Get it from CodePlex (or just nick it out of my sample solution). Put it in the Binaries Solution folder as well, make a reference to all four assemblies from the project and we are ready to go.

First of all, muck up the page by putting something in the ContentPanel grid, like this:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  <TextBlock TextWrapping="Wrap" FontSize="85"> 
   This is content that could do with some more space
  </TextBlock>
</Grid>

ModelDrivenAnimations1

Which will yield a very pretty *cough* looking application screen similar to the image to the right.

Next is a very simple view model:

using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;

namespace ModelDrivenAnimations
{
  public class SampleViewModel : ViewModelBase
  {
    private string _appName = "ModelDrivenAnimations";
    public string AppName
    {
      get { return _appName; }
      set
      {
        _appName = value;
        RaisePropertyChanged("AppName");
      }
    }

    private bool _isPageTitleVisible = true;
    public bool IsPageTitleVisible
    {
      get { return _isPageTitleVisible; }
      set
      {
        _isPageTitleVisible = value;
        RaisePropertyChanged("IsPageTitleVisible");
      }
    }

    public ICommand TogglePageTitleCollapsed
    {
      get
      {
        return new RelayCommand(() =>
        {
          IsPageTitleVisible = !IsPageTitleVisible;
        });
      }
    }
  }
}

Very basic: a property “IsPageTitleVisible” to control the actual display of the Page Title, a command to fire the actual toggle, and a property with the app name to see if the binding actually works ;-) - normally this would come from a resource file, eh? If you’ve read my blog before, you should be starting to recognize the next part. First, add this to the namespace declarations of MainPage.xaml by adding

xmlns:ModelDrivenAnimations="clr-namespace:ModelDrivenAnimations"

to the namespaces, then declare the SampleViewModel just above the LayoutRoot grid:

<phone:PhoneApplicationPage.Resources>
  <ModelDrivenAnimations:SampleViewModel x:Key="SampleViewModel"/>
</phone:PhoneApplicationPage.Resources>
set the DataContext of your layout root grid to be the SampleViewModel:
<Grid x:Name="LayoutRoot" Background="Transparent" 
  DataContext="{StaticResource SampleViewModel}">

and then change Text="MY APPLICATON" into Text="{Binding AppName}" on the ApplicationTitle TextBlock. Rebuild the application, and even in design time the application name changes to “ModelDrivenAnimations”. Well done. The stage is set. Now proceed to start Expression Blend and open your project. Go on! Contrary to what most developers think, it won’t bite ;-)

First of all, we are going to bind the command. I decided to let the PageTitle disappear as the user taps on the ApplicationTitle, so select the ApplicationTitle on the design pane screen. Then proceed as follows:

  • Top left, select the “Assets” tab, and start to type “EventToCommand” in the search box. Long before you are even halfway done, the “EventToCommand” behavior appears.
  • Drag the EventToCommand command on top of the ApplicationTitle in the “Objects and Timeline” panel about halfway left on the screen.
  • Go to the right top side of the screen. Select the “Properties” tab. In the panel “Trigger” is a dropdown “EventName” which is currently set to “Loaded”. Change that to “MouseLeftButtonDown” – we want the effect appear when the user taps the title, not when it is loaded ;-).
  • Select the tab “Data” at the top right of the screen. Way down below at the bottom right a panel “Data Context” with “SampleViewModel” in it will appear. Expand this, find the “TogglePageTitleCollapsed" (last entry) and drag this onto the EventToCommand.
  • Hit “Ok” on the popup that appears.

You have just created a command binding without even typing a line of code. Expression Blend even added all the namespace prefixes for you.

One little piece of preparation – once again, select the PageTitle and select the “Properties” tab. In the panel “Layout” there is a property “Height” and that is set to “Auto (95.765625)”. This needs to be a fixed value, or else the Visual State Manager cannot infer how to change states. So change this value to “95”.

Now we are going to have some fun with the Visual State Manager itself:

  • Looking back at the top left of the screen you will notice a “States” tab. Select that, and two very small icons will appear.
  • Clicking the first one will create a new Visual State Group and three more icons.
  • Click on the middle one (“Add state”) and add a state “Expanded”. The top of the design pane in the middle will now show a little red button with the text “Expanded state recording is on”.
  • Click the red button to turn off the recording.
  • Add another Visual State called “Collapsed” but don’t turn off the recording now. In stead, select the PageTitle again (either in design view or in the Objects and Timeline panel).
  • Click tab “Properties” on the right top side of the screen.
  • Change the Height property again – this time to 0 (zero). The PageTitle will collapse to a thin blue indicator line.
  • Look down, and find the panel “Transform”. It’s likely to be collapsed at this point, so expand it first. Select the third tab, called “Scale”.
  • Change the Y value to 0 .

Click on visual state “Expanded” in the tab “States” on the left top of the screen and the PageTitle will reappear again. Now turn of the recording for visual states by clicking the red button on top of the design pane, and commence to the last step: connecting the states to the ViewModel by using DataStateBehavior.

  • Select “Assets” again in the top left panel, type “DataState” in the search box and “DataStateBehavior” and “DataSwitchBehavior” will appear.
  • Drag DataStateBehavior on top of “ApplicationTitle” in the “Objects and Timeline” panel.
  • On the right side of the screen, select the Properties tab again, and the properties of the behavior will appear.
  • Click the little grey square behind “Binding”, select “Data binding” in the popup menu, and then “IsPageTitleVisible” in the popup.
  • Click the down pointing arrow on the bottom of the dialog for more options, and select TwoWay.
  • Click OK.
  • Enter “Collapsed” for “FalseState”,
  • Enter “Expanded” for TrueState,
  • Enter “True” for Value.

The odd thing is – sometimes Blend does not let you enter “True” for Value. I am not sure if this is a bug. Please switch to XAML in that case and enter the value manually (use the small button with “< >” on the top right of the design pane to switch to XAML and find the DataStateBehavior; the button above (with the circle) will take you back to design view).

Then hit F5. Yes, hit F5. Even designers need to test stuff runtime, and they can do so by hitting F5, just like a developer. If you’ve done everything right, the Page Title will disappear and reappear when you click on the application title. But that’s hardly an animation. So switch back to Blend one more time:

  • Select the “States” tab once more,
  • Select “Default Transition”
  • Change the “0 s” in the little textbox to the right into “1”.

Then hit F5 again. The Visual State Manager now takes 1 second to change from one state to another, and infers an animation from that. Your PageTitle now appears to fold away in itself. The net effect, as I implemented it in MapMania, looks like this:

Animated header as implemented on MapMania

Notice the little visual cue next to the app name ;-). And this is about the simplest of animations. There are lots of other possibilities – you can even make your own transitions, let things rotate in or out of existence, whatever. I really encourage you to explore the possibilities of Transform tab – I just started doing that myself ;-).

 

 

 

The Visual State Manager is, when all is said and done, nothing more than a State Machine in which you can define states and transitions, that can be easily controlled from your View Model – i.e. code, the developer’s comfort zone  – by using DataStateBehavior. And DataSwitchBehavior – this can handle more states than just true or false, as far as I understood.

For those who lost the way in text, a little “Blend Road Map” showing everything we have used to get to this result:Blend

A little rant to conclude this post: some of you may have noticed that since we’ve hooked up the ViewModel to the View we did not type a single line of code at all. I can hear some of you think “maybe this dolt should rename his blog “Blend by Example” and leave us alone” ;-)

I am convinced that, as a WPF, Silverlight or Windows Phone developer, you should have at least a basic understanding of what you can do with Blend. First of all, even if you work without a separate designer, it saves you a lot of XAML typing, which I can assure you is not very much fun, and every good developer is a lazy developer. Second, creating things like animations and pretty user interfaces is pretty hard, and Blend helps you a lot with it. Maybe you as a developer like bare-bones GUI’s, but your users don’t, so better use a tool that can help with it. Third, as you do work with a separate designer, you need to know what he or she needs to have to make the application tick. Developer and designer need to communicate and understand each other. Now what do you think is more likely – that a designer understands what you as a developer are doing, or the other way around? And who will be calling the shots in a development process – the one that only understands his own side of the designer/development fence, or the one that can look over the fence and stomp around at the other side, if only a little? So get going and get to grips with Blend. Or else – heaven forbid - have designers commandeering you around ;-)

If something went wrong along the way, please refer to the sample solution.

28 November 2010

Converter for showing/hiding Silverlight/WP7 GUI elements with boolean model values

Typically, when you are writing Silverlight or Windows Phone 7 applications using the MVVM pattern, your viewmodels expose boolean values indicating something is going on – for instance, your background thread is loading data and thus you want to display a progress bar. For some reason the designers of XAML choose the Visibility property of a GUI element to be of type “Visibility”, an enum with values “Visible” and “Collapsed”.

Anyway, you could of course make your viewmodel expose properties of type Visibility but as an architect I would call that “bad form”. It’s so much easier to create a simple converter for this:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace LocalJoost.Converters
{
  public class VisibilityConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, 
                   CultureInfo culture)
    {
      if (value != null && value is bool && parameter != null)
      {
        var bValue = (bool) value;
        var visibility = (Visibility)Enum.Parse(
             typeof (Visibility), parameter.ToString(),true);
        if (bValue) return visibility;
        return visibility == 
           Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
      }
      
      return null;
    }

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

This convertor assumes a boolean input value and a parameter of type Visibility. If the input value is true, it returns the value in the parameter, if it is false, it returns the opposite. So it’s basically a converter that converts one type of boolean in another, with the possibility to specify what is true and what is false ;-)

Typical usage snippet:

Visibility="{Binding IsDownloading, Converter={StaticResource VisibilityConverter}, ConverterParameter=Visible}"

which says that if the “IsDownloading” property of the model is true, the GUI object in case must be visible. Since I actually used this snippet on a Windows Phone 7 ProgressBar, this makes perfect sense.

12 November 2010

Silverlight and Windows Phone 7 do not like DTDs

My current Windows Phone 7 project requires the possibility of reading configurations from various servers. For the GIS lovers among my audience: I am trying to read a GetCapabilities document from a WMS server. I was reading this one and this one, and the first one succeeded, while the second one failed. It became even more odd when I tried to write unit tests running from the full framework – then both links processed flawlessly. The error was “NotSupportedException” on XDocument.Load().

The second links contains a DTD from ye goode olde days and it turns out Silverlight and Windows Phone 7 by default are not very fond of DTD’s in documents. After some searching around I found the DtdProcessing Enumeration and I changed this code

var doc = XDocument.Load(stream)

into

using (var reader = XmlReader.Create(stream, 
  new XmlReaderSettings {DtdProcessing = DtdProcessing.Ignore}))
{
  var doc = XDocument.Load(reader)
}

And indeed, problem solved

02 November 2010

Swapping two elements in a bound ObservableCollection IMPROVED VERSION

Today I noticed something odd. I wanted to swap two elements in an ObservableCollection that was bound to a list box – so I could move objects up and down in the list box. I noticed that moving up always worked, and moving down led to a crash in my application with the message ‘The parameter is incorrect’ in the Application_UnhandledException method of App.Xaml. Fat lot of help that is, I can tell ya ;-)

Maybe this is a Windows Phone 7 only thing, but after a bit of experimenting I found out that you always have to change the value of the item that is last in the collection – i.e., the item with the highest index – first. This worked perfectly and I blogged about it. Later I found one exception, when I tried to swap the first and the second entry. Swapping second and first works, but the other way around gave once again ‘The parameter is incorrect’

This makes no sense at all, but I guess it has something to do with the fact that at one point in time the collection contains one object twice, and whatever is bound to it does not seem to like that very much. One of my household sayings is that were’s a will, there’s a workaround. So, second time around:

using System.Collections.Generic;
using System.Collections.ObjectModel;

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace LocalJoost.Utilities
{
  public static class ObservableCollectionExtensions
  {
    public static void Swap<T>(
       this ObservableCollection<T> collection, 
       T obj1, T obj2)
    {  
      if (!(collection.Contains(obj1) && collection.Contains(obj2))) return;
      var indexes = new List<int>
         {collection.IndexOf(obj1), collection.IndexOf(obj2)};
      if(indexes[0] == indexes[1]) return;
      indexes.Sort();
      var values = new List<T> {collection[indexes[0]], collection[indexes[1]]};
      collection.RemoveAt(indexes[1]);
      collection.RemoveAt(indexes[0]);
      collection.Insert(indexes[0], values[1]);
      collection.Insert(indexes[1], values[0]);
    }
  }
}
and now I am really done with it. It's not always high science what I blog, just things I hope that save someone else a lot of time :-)

21 October 2010

Showing Open Source maps on Windows Phone 7 with the Bing Maps Control

My previous post, which caused a bit of a stirrup and got me over 1000 page hits on that one page alone in less than 46 hours, led to the question if I could provide an example of how to use data from Open Source map servers on the web in Bing Maps. This is more useful than Google Maps, since Google’s TOS most likely does not allow accessing their data they way I showed, and an application doing so violates section 3.1 of the Windows Phone 7 Application Certification Requirements, which clearly state that allowed content includes “Copyrighted content that is used with permission”, which I obviously do not have and most likely never will. Unless Google wants to code me a Windows Phone 7 app for them. Hello? Anyone reading along there at Mountain View? ;-)

So anyway, as long as my personal Google Maps for Windows Phone 7 is not going to hit the Marketplace, the Bing Maps MercatorMode can very well be used to show other tile maps, and by popular request I show you how to use data from Mapnik and Osmarender:

MapnikOsmarender

The possibility should come as no surprise, since I showed something like this earlier, only then with a plain ole’ MultiScaleImage. All you need to do is, once again, write a class again that descends from Microsoft.Phone.Controls.Maps.TileSource and overrides

Uri GetUri(int x, int y, int zoomLevel)

like I showed in my previous post. And these are even simpler than the Google Maps tilesource. First, Mapnik:

using System;

namespace LocalJoost.TileSource
{
  public class MapnikTileSource : Microsoft.Phone.Controls.Maps.TileSource
  {
    public MapnikTileSource()
    {
      UriFormat = "http://{0}.tile.openstreetmap.org/{1}/{2}/{3}.png";
      _rand = new Random();
    }

    private readonly Random _rand;
    private readonly static string[] TilePathPrefixes =
        new[] { "a", "b", "c" };

    private string Server
    {
      get
      {
        return TilePathPrefixes[_rand.Next(3)];
      }
    }
   
    public override Uri GetUri(int x, int y, int zoomLevel)
    {
      if (zoomLevel > 0)
      {
        var url = string.Format(UriFormat, Server, zoomLevel, x, y);
        return new Uri(url);
      }
      return null;
    }
  }
}

and second, Osmarender

using System;

namespace LocalJoost.TileSource
{
  public class OsmaRenderTileSource : Microsoft.Phone.Controls.Maps.TileSource
  {
    public OsmaRenderTileSource()
    {
      UriFormat = "http://{0}.tah.openstreetmap.org/Tiles/tile/{1}/{2}/{3}.png";
      _rand = new Random();
    }

    private readonly Random _rand;
    private readonly static string[] TilePathPrefixes =
        new[] { "a", "b", "c", "d", "e", "f" };

    private string Server
    {
      get
      {
        return TilePathPrefixes[_rand.Next(6)];
       }
    }

    public override Uri GetUri(int x, int y, int zoomLevel)
    {
      if (zoomLevel > 0)
      {
        var url = string.Format(UriFormat, Server, zoomLevel, x, y);
        return new Uri(url);
      }
      return null;
    }
  }
}

As you see, no rocket science. How to use this classes in XAML is explained in my previous post, and I am not going to repeat that here..

The odd thing is that the Bing Maps logo stays visible, although no Bing Maps data is being used. Notice that the open source maps are displayed in design mode too. So, use any tile server out there that you can legally access and have fun mapping…. although I must admit that is most likely interesting for hardcore GIS fans only, since both servers are considerably slower than Bing – especially OsmaRender, which does some odd things cutting the Netherlands in half, as you see. But still, if not anything else, it shows off the power and versatility of the Bing Maps control pretty well.

For the lazy people, a complete solution – including the Google stuff – can be downloaded here.

19 October 2010

Google Maps for Windows Phone 7 using the Bing Maps Control

GoogleMapsAs I write this, we are less than 25 hours away from the moment the first Windows Phone 7 devices are released into the wild – in the Netherlands, that is. The marketplace is filling up nicely already. Some things are pretty obviously missing. And you wonder why, because they would be quite easy to make.

I hereby dare Google to publish a Google Maps application in the Windows Phone 7 Marketplace. Why? Because if they don’t do it, someone else probably will, and probably pretty fast, too. See the screenshot to the left. I assure you – this is no fake. What you see here is the Google Maps satellite layer with the Street layer on top of it, with a 40% opacity, showing a piece of the city center of Amersfoort, Netherlands. It took me about 90 lines of code and 18 lines of XAML.

Setting up a basic Bing Map control is easy as cake. You find the grid “Contentpanel”, and you plonk a Map in it, preferably using Blend:

 

 

 

 

 

 

 

<Microsoft_Phone_Controls_Maps:Map 
 x:Name="map"  
 CredentialsProvider="your_credentials" Mode="Road">
</Microsoft_Phone_Controls_Maps:Map>

You can change “Road” into “Aerial” and then you have Bing Satellite imagery. What’s less known is that you can actually attach your own tile layers to it. A tile is an image of 256x256 defined by it’s X and Y position in the grid forming the map of the world on a specific zoom level. All you have to to is write a little class that descends from Microsoft.Phone.Controls.Maps.TileSource in which you only have to override the following method:

Uri GetUri(int x, int y, int zoomLevel)

in which you tell which x/y/zoomlevel combination translates to which URI. For Google Maps, this turns out to be pretty easy. All information needed for can be found in the Deep Earth source code – a wee bit adapted for for the Bing Map Control. First, I made an enum that defines all the layers that Google provides

namespace LocalJoost.TileSource
{
  public enum GoogleTileSourceType
  {
    Street,
    Hybrid,
    Satellite,
    Physical,
    PhysicalHybrid,
    StreetOverlay,
    WaterOverlay
  }
}

Then, the actual tile calculating class:

using System;

namespace LocalJoost.TileSource
{
  public class GoogleTileSource : Microsoft.Phone.Controls.Maps.TileSource
  {
    public GoogleTileSource()
    {
      UriFormat = @"http://mt{0}.google.com/vt/lyrs={1}&z={2}&x={3}&y={4}";
      TileSourceType = GoogleTileSourceType.Street;
    }
    private int _servernr;
    private char _mapMode;

    private int Server
    {
      get
      {
        return _servernr = (_servernr + 1) % 4;
      }
    }

    private GoogleTileSourceType _tileSourceType;
    public GoogleTileSourceType TileSourceType
    {
      get { return _tileSourceType; }
      set
      {
        _tileSourceType = value;
        _mapMode = TypeToMapMode(value);
      }
    }

    public override Uri GetUri(int x, int y, int zoomLevel)
    {
      {
         if (zoomLevel > 0)
        {
          var url = string.Format(UriFormat, Server, _mapMode, zoomLevel, x, y);
          return new Uri(url);
        }
      }
      return null;
    }

    private static char TypeToMapMode(GoogleTileSourceType tileSourceType)
    {
      switch (tileSourceType)
      {
        case GoogleTileSourceType.Hybrid:
          return 'y';
        case GoogleTileSourceType.Satellite:
          return 's';
        case GoogleTileSourceType.Street:
          return 'm';
        case GoogleTileSourceType.Physical:
          return 't';
        case GoogleTileSourceType.PhysicalHybrid:
          return 'p';
        case GoogleTileSourceType.StreetOverlay:
          return 'h';
        case GoogleTileSourceType.WaterOverlay:
          return 'r';
      } return ' ';
    }
  }
}

As you can see, this is not quite rocket science. To make the Bing Maps control use this, you need to expand your XAML a little. First, you have to add two namespaces to your MainPage.Xaml:

xmlns:MSPCMCore="clr-namespace:Microsoft.Phone.Controls.Maps.Core;assembly=Microsoft.Phone.Controls.Maps" 
xmlns:LJTileSources="clr-namespace:LocalJoost.TileSource;assembly=LocalJoost.TileSource" 

Then, you have to use a third mode for the Bing Map – Mercator. This basically only tells the map to operate in Mercator mode, but not to attach any default imagery. And here we go:

<Microsoft_Phone_Controls_Maps:Map x:Name="map" 
   CredentialsProvider="your credentials" >
  <Microsoft_Phone_Controls_Maps:Map.Mode>
    <MSPCMCore:MercatorMode></MSPCMCore:MercatorMode>
  </Microsoft_Phone_Controls_Maps:Map.Mode>
  <Microsoft_Phone_Controls_Maps:Map.Children>
    <Microsoft_Phone_Controls_Maps:MapTileLayer>
      <Microsoft_Phone_Controls_Maps:MapTileLayer.TileSources>
        <LJTileSources:GoogleTileSource TileSourceType="Satellite"/>  
      </Microsoft_Phone_Controls_Maps:MapTileLayer.TileSources>
    </Microsoft_Phone_Controls_Maps:MapTileLayer>
  </Microsoft_Phone_Controls_Maps:Map.Children>
</Microsoft_Phone_Controls_Maps:Map>
Presto. Google Maps for Windows Phone 7. And oh, if you want to project another layer on top of it, for instance Google Streets, like I did, you just add more tilelayers:
<Microsoft_Phone_Controls_Maps:Map x:Name="map" 
   CredentialsProvider="your credentials" >
<Microsoft_Phone_Controls_Maps:Map.Mode>
  <MSPCMCore:MercatorMode></MSPCMCore:MercatorMode>
</Microsoft_Phone_Controls_Maps:Map.Mode>
<Microsoft_Phone_Controls_Maps:Map.Children>
  <Microsoft_Phone_Controls_Maps:MapTileLayer>
    <Microsoft_Phone_Controls_Maps:MapTileLayer.TileSources>
      <LJTileSources:GoogleTileSource TileSourceType="Satellite"/>
    </Microsoft_Phone_Controls_Maps:MapTileLayer.TileSources>
  </Microsoft_Phone_Controls_Maps:MapTileLayer>
  <Microsoft_Phone_Controls_Maps:MapTileLayer>
    <Microsoft_Phone_Controls_Maps:MapTileLayer.TileSources>
      <LJTileSources:GoogleTileSource TileSourceType="Street"/>
    </Microsoft_Phone_Controls_Maps:MapTileLayer.TileSources>
  </Microsoft_Phone_Controls_Maps:MapTileLayer>
</Microsoft_Phone_Controls_Maps:Map.Children>
</Microsoft_Phone_Controls_Maps:Map>

The Bing Maps control makes you able to zoom and pan trough all this. Okay, Google Maps does a little more than this, but this shows pretty well how darn easy it is to make a good looking, pretty fully functional mapping application showing totally different imagery.

Conclusion: using the Bing Maps Control for showing raster maps is so bloody easy that I almost start to wonder if it’s still justified to feel proud ‘being a GIS guy’. Almost ;-)

06 October 2010

Using MVVMLight Messenger and behaviors to open files from local disk in Silverlight

The very first Silverlight application I had to make professionally (i.e. not as a hobby/research project at home) required the possibility to open a file from (local) disk. Of course I wanted to use Laurent Bugnion’s MVVMLight and the strict separation between logic that I talked and blogged about so much – ‘practice what thou preach’, eh?

This proved to be an interesting challenge. The most logical way forward seemed to be: make a button, attach a command to it via the usual EventToCommand route and then use OpenFileDialog from the model – which landed me a “Dialogs must be user-initiated” error. Googling Binging around I found out that Dennis Vroegop had run into this thingy quite recently as well.

Things looked ugly. Getting around it took me some code in the code behind. This kept nagging me, and only recently I found a better way to deal with this. Apart from the EventToCommand binding MVVMLight contains another gem, called the Messenger. This is an implementation of the Mediator pattern that - as far as I understand – is designed to broadcast property changes between different models in the application. But this mechanism can also be used as an alternative way to shuttle data from the GUI to the model in a designer-friendly – Blendable -way. The way to go for this particular problem is like this:

  • Define a message that holds the data from an OpenFileDialog
  • Make a behavior that can attach to a button that launches an OpenFileDialog on click, and then sends a message on the Messenger
  • Register a listener on the Messenger in the model for that particular message

True to my blog’s title, I will demonstrate this principle with an example. First, a message containing the result of an OpenFileDialog:

using System.Collections.Generic;
using System.IO;
using GalaSoft.MvvmLight.Messaging;

namespace LocalJoost.Behaviours
{
  public class FilesOpenedMessage : GenericMessage<IEnumerable<FileInfo>>
  {
    public FilesOpenedMessage(IEnumerable<FileInfo> parm)
      : base(parm)
    {
      Identifier = string.Empty;
    }

    public string Identifier{get;set;}
  }
}

Notice that the message also contains a property Identifier, acting like an optional additional message identifier. This is necessary, since the MVVMLight messenger seems to identify messages by type. Therefore, every method listening to a FilesOpenedMessage gets a message, and the various models would have no way to know if it was actually for them to act on it. Identifier makes coupling a particular listener to a particular sender possible. I’m sure there are other ways to get this done, I simply chose the simplest.

Getting on with the behavior:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using GalaSoft.MvvmLight.Messaging;

namespace LocalJoost.Behaviours
{
  /// <summary>
  /// A behavior attaching to a button
  /// </summary>
  public class FileOpenBehavior : Behavior<Button>
  {
    // Properties - can be set from XAML
    public string MessageIdentifier { get; set; }
    public string Filter { get; set; }
    public bool MultiSelect { get; set; }

    protected override void OnAttached()
    {
      base.OnAttached();
      Filter = "All files (*.*)|*.*";
      AssociatedObject.Click += AssociatedObject_Click;
    }

    void AssociatedObject_Click(object sender, RoutedEventArgs e)
    {
      // Open the dialog and send the message
      var dialog = 
        new OpenFileDialog {Filter = Filter, Multiselect = MultiSelect};
      if (dialog.ShowDialog() == true)
      {
        Messenger.Default.Send(
          new FilesOpenedMessage(dialog.Files) 
          { Identifier = MessageIdentifier });
      }      
    }

    protected override void OnDetaching()
    {
      AssociatedObject.Click -= AssociatedObject_Click;
      base.OnDetaching();
    }
  }
}

Which works pretty simple: it attaches itself to the click event, composes a message and fires it away on the Messenger. In your model you register a listener like this:

public class DemoModel : ViewModelBase
{
  public DemoModel()
  {
    if (IsInDesignMode)
    {
      // Code runs in Blend --> create design time data.
    }
    else
    {
      Messenger.Default.Register<FilesOpenedMessage>(
        this,
        DoOpenFileCallback);
    }
  }

  private void DoOpenFileCallback(FilesOpenedMessage msg)
  {
    if (msg.Identifier != "1234") return;
    // Store result in SelectedFiles
    SelectedFiles = msg.Content.Select(f => f.Name).ToList();
  }
  // Rest of model omitted - see demo solution
}

The “Content” property of the message always contains the payload, i.e. whatever you had to pass to the message constructor – in this case, an IEnumerable<FileInfo>. In XAML you then bind the whole thing together like this, like any ordinary behavior:

<UserControl x:Class="DemoMessageBehaviour.MainPage"
  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:model="clr-namespace:DemoMessageBehaviour.Model;assembly=DemoMessageBehaviour.Model"
  xmlns:LJBehaviours="clr-namespace:LocalJoost.Behaviours;assembly=LocalJoost.Behaviours"
  xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"        
  mc:Ignorable="d"
  d:DesignHeight="300" d:DesignWidth="400">
  <UserControl.Resources>
    <model:DemoModel x:Key="MyDemoModel"/>
  </UserControl.Resources>
  <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource MyDemoModel}">
    <Grid.RowDefinitions>
      <RowDefinition Height="0.9*"/>
      <RowDefinition Height="0.1*"/>
    </Grid.RowDefinitions>
    <Button Content="Open" Grid.Row="1" IsEnabled="{Binding CanOpen}" >
      <i:Interaction.Behaviors>
        <LJBehaviours:FileOpenBehavior MessageIdentifier="1234" MultiSelect="True"/>
      </i:Interaction.Behaviors>
    </Button>
    <ListBox Grid.Row="0" ItemsSource="{Binding SelectedFiles}"/>
  </Grid>
</UserControl>

The Listbox is used to display the names of the selected files. I did leave this out, there’s enough code in this sample already. For those who want to have the full picture: a complete solution demonstrating the behavior can be found here.

Notice there’s an important difference using this technique in stead of the EventToCommand: when the user presses the button, the model cannot control the actual display of the OpenFileDialog: the click is handled by the behavior, not the model itself (via EventToCommand) and the OpenFileDialog is always displayed once the button is clicked. Therefore, the model should control if the user can press the button at all, which is done by binding the IsEnabled property to a property of the model (CanOpen – sorry, lame pun). Which is good UX practice anyway – controls that cannot be used should be disabled in stead of giving a ‘sorry, you can’t do that’ message whenever possible, but in this case it’s simply necessary.

I only showed how to do open a file from disk, but a pattern like this can be used to save files to disk as well, or do other things that might seem to require coding in the code behind file. That is no sin in itself – nobody from the MVVM police will show up at your doorstep and take your family away if you need to do that. At least that is what Laurent himself promised at his MIX10 talk ;-). But by coding a behavior, you enable designers to add fairly easily relatively complex actions using Blend without having to dive too deep into XAML. Using behaviors like this FileOpenBehavior makes it even easier to add interactivity than the EventToCommand (which was closed off anyway during security restrictions).

It can’t hurt to be nice to your designers – after all, they make the pretty stuff up front that makes your application sell ;-)

Oh, and one final thing: in all my previous posts I was talking about ‘behaviour’ in stead of ‘behavior’. I hope you all will excuse me for being educated in the Queen’s English, even tough I am just Dutch. ;-).

30 September 2010

Notifying the viewmodel of input errors using MVVMLight in Silverlight

I was creating a little application that had some input fields and a submit button. I wanted the submit button only to be enabled when the user input was valid. No a very unusual use case. So I set out to create a viewmodel the usual MVVMLight way, extending ViewModelBase and making properties using the snippets Laurent Bugnion provides with the MVVMLight toolkit. To validate them, I decorated them with attributes from System.ComponentModel.DataAnnotations, and validated them with using the Validator. A typical property looks like this:

/// 
/// The  property's name.
/// 
public const string SomecodePropertyName = "Somecode";

private int _somecode = 0;

[Range(0, 2000)]
public int Somecode
{
  get
  {
    return _someCode;
  }

  set
  {
    if (_someCode == value)
    {
      return;
    }
    Validator.ValidateProperty(value,
      new ValidationContext(this, null, null) 
      { MemberName = SomecodePropertyName });

    _someCode = value;

    // Update bindings, no broadcast
    RaisePropertyChanged(SomecodePropertyName);
  }
}

Then I thought I was smart, so I added the following code:

private void RaisePropertyChangedAndRevalidate(string propertyName)
{
  RaisePropertyChanged(propertyName);
  RaisePropertyChanged("IsValid");
}

public bool IsValid
{
  get
  {
    return Validator.TryValidateObject(this, 
                       new ValidationContext(this, null, null),
                       new List<ValidationResult>());
  }
}

and changed every other call to RaisePropertyChanged to RaisePropertyChangedAndRevalidate. Then I bound my button’s IsEnabled property to IsValid

<Button Content="subMit" x:Name="btnSave" 
  IsEnabled="{Binding IsValid,ValidatesOnExceptions=True,NotifyOnValidationError=True}">

and I turned out not be so very smart after all, for it did not work. When the user entered a value that was outside the range of 0-2000 or even plain nonsense – like “thisisnonumber”, the UI gave an error message, I even got the message in my ValidationSummary, but for some reason the viewmodel still found itself valid.

This puzzled me for a while, but it turned out to be perfectly logical. If the user inputs nonsense this throws an exception, and so does a call to Validator.ValidateProperty. So the invalid input never makes to the viewmodel property, and the viewmodel, if it was initially valid, stays valid en thus my button stays enabled. How to get around this?

Actually, it’s pretty simple: notify your viewmodel of input errors, so it can keep track of whatever nonsense the user has tried to cram down its throat. The way I chose to do it was like this:

  • Add a field int _fieldErrors;
  • Bind the event BindingValidationError to a command in the viewmodel, passing it’s parameters along
  • Add to or subtract 1 from_fieldErrors for every time the command was called depending on the parameter
  • Modify the IsValid property so it checks the _fieldErrors field to.

In code:

private void RaisePropertyChangedAndRevalidate(string propertyName)
{
  RaisePropertyChanged(propertyName);
  if (_fieldBindingErrors == 0)
  {
    RaisePropertyChanged("IsValid");
  }           
}
private int _fieldBindingErrors = 0;

public ICommand RegisterFieldBindingErrors
{
  get
  {
    return new RelayCommand<ValidationErrorEventArgs>(e =>
    {
      if (e.Action == ValidationErrorEventAction.Added)
      {
        _fieldBindingErrors++;
      }
      else
      {
        _fieldBindingErrors--;
      }
      RaisePropertyChanged("IsValid");
    });
  }
}

public bool IsValid
{
  get
  {
    return Validator.TryValidateObject(this, 
                       new ValidationContext(this, null, null),
                       new List<ValidationResult>()) 
                       && _fieldBindingErrors == 0;
  }
}

Binding the stuff in XAML goes like this:

<StackPanel
  DataContext="{Binding MyModel,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="BindingValidationError">
          <cmd:EventToCommand Command="{Binding RegisterFieldBindingErrors}" 
            PassEventArgsToCommand="true"/>
        </i:EventTrigger>
      </i:Interaction.Triggers>
</StackPanel>

And thus the viewmodel maintains its own integrity, is notified by the UI of all problems playing around there, and can decide based upon the sum of this information whether or not the user can actually save.

Thanks to Mike Taulty for helping me out on the Validator in real-time on Twitter.

10 September 2010

An animated swipe-enabled title control for Windows Phone 7

Updated for the RTM tools, included sample application to show how it’s used.

For my ongoing ‘research project’ on making the user experience for my MVVM-driven map viewer for Windows Phone 7 a bit better I wanted to make some changes to the header. Initially you could swipe the header which showed which map you show, but it gave no indication that you actually could make the swipe, or that other maps were available. I set out to create a behaviour, and ended up building a control. In my map viewer, it currently looks and works like this:

And, of course, it needed to play along nicely with MVVM. And therefore it has three bindable properties:

  • Items: an IList of basically anything
  • SelectedItem: the currently selected item in Items
  • DisplayText: (optional): the name property of the objects in Items which value should be used in de SwipeTitle. If skipped, the ToString() value of the objects is used as a display value.

It does not sport item or data templates (yet) – it’s just meant to be plug and play. For now.

Usage

You only have to do six things to use this thing:

  • Create an empty Windows Phone 7 application
  • Add the LocalJoost.Controls project included in the demo solution to your project.
  • Create some kind of model with a property containing a list of objects and a property containing a selected object and bind it to the layout root
  • Drag the SwipeTitle control on the design surface at the place of the TextBox called “PageTitle” (and remove PageTitle)
  • Bind property Items, SelectedItem and DisplayText as needed
  • Make sure the Width property of the StackPanel “TitlePanel” is some large width, e.g. 2000.

Below is a short walkthrough of the source. Those who just want to use it, can just grab the source. If you want a simple sample application showing the usage of the control, just grab this solution and see how it works. I sincerely apologize for the sheer silliness of the demo solution, it was all I could think of on short notice.

Walkthrough

The XAML is pretty simple: three TextBlocks in a horizontal StackPanel:

<UserControl x:Class="WP7Viewer.SwipeTitle"
  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"
  mc:Ignorable="d"
  FontFamily="{StaticResource PhoneFontFamilyNormal}"
  FontSize="{StaticResource PhoneFontSizeNormal}"
  Foreground="{StaticResource PhoneForegroundBrush}"
  d:DesignHeight="480" d:DesignWidth="480">
    <StackPanel Grid.Row="1" Orientation="Horizontal" x:Name="pnlSwipe">
      <TextBlock x:Name="tbPrevious"
          Foreground="Gray"
           Text="Previous" 
           Margin="0,0,0,0"
           Style="{StaticResource PhoneTextTitle1Style}">
      </TextBlock>
    <TextBlock x:Name="tbCurrent"
           Text="Current" 
           Margin="20,0,0,0"
           Style="{StaticResource PhoneTextTitle1Style}">
       </TextBlock>
    <TextBlock x:Name="tbNext"
          Foreground="Gray"
           Text="Next" 
           Margin="20,0,0,0"
           Style="{StaticResource PhoneTextTitle1Style}">
      </TextBlock>
    </StackPanel>
</UserControl>

You can swipe endlessly left or right, when it comes to the end or the start of the list it just adds the items from the start or the end again. The middle text is the selected object, and is always shown (in white). When you swipe the text to the left, it animates the swipe to completion: the text previously on the right, which you swiped into view, is now displayed in the middle TextBlock. The text that used to be in the middle block, is now in the left, and in the right TextBlock a new text from Items is displayed, as shown in the video.

Now how does this work? If you know how to do it, it is – as everything – pretty simple. First, some boring stuff, the class definition with some members and dependency properties:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace WP7Viewer
{
    public partial class SwipeTitle
    {
       /// <summary>
       /// An internal linked list to make searching easier
       /// </summary>
       private LinkedList<object> _displayItems;
       private TranslateTransform _transform;
       private int _currentDisplayItem = 1; 
     
      #region Items
      /// <summary>
      /// Dependency property holding the items selectable in this control
      /// </summary>
      public static readonly DependencyProperty ItemsProperty =
         DependencyProperty.Register("Items", typeof(IList),
         typeof(SwipeTitle), new PropertyMetadata(ItemsChanged));

      private static void ItemsChanged(object sender, 
        DependencyPropertyChangedEventArgs args)
      {
        var c = sender as SwipeTitle;
        if (c != null) {  c.ProcessItemsChanged(); }
      }

      public IList Items
      {
        get { return (IList)GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
      }

      public void ProcessItemsChanged()
      {
        _displayItems = new LinkedList<object>();
        foreach (var obj in Items) _displayItems.AddLast(obj);
        SelectedItem = Items[0];
      }
      #endregion

      #region SelectedItem
      /// <summary>
      /// Dependency property holding the currently selected object
      /// </summary>
      public static readonly DependencyProperty SelectedItemProperty =
         DependencyProperty.Register("SelectedItem", typeof(object),
         typeof(SwipeTitle), new PropertyMetadata(SelectedItemChanged));

      private static void SelectedItemChanged(object sender, 
        DependencyPropertyChangedEventArgs args)
      {
        var c = sender as SwipeTitle;
        if (c != null) { c.ProcessSelectedItemChanged(); }
      }

      // .NET Property wrapper
      public object SelectedItem
      {
        get { return (object)GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
      }

      public void ProcessSelectedItemChanged()
      {
        UpdateDisplayTexts();
        ScrollToDisplayItem(1, false);
      }
      #endregion
      
      #region DisplayField
      public string DisplayField
      {
        get { return (string)GetValue(DisplayFieldProperty); }
        set { SetValue(DisplayFieldProperty, value); }
      }

      public static readonly DependencyProperty DisplayFieldProperty =
        DependencyProperty.Register("DisplayField", typeof(string), 
          typeof(SwipeTitle), null);
      #endregion
    }
  }
}

The transformation is used in the animation and to make sure the middle TextBlock is displayed when idle. I tend to tuck dependency properties in regions to keep stuff a bit organized, but feel free to do otherwise. Note, however, that the objects in Items are immediately copied in a LinkedList _displayItems – I use this list to make it easier to find previous and next objects.

Then we need a little method to get the display text from the objects according to the value of “DisplayField”:

/// <summary>
/// Retrieve the display value of an object
/// </summary>
/// <param name="displayObject"></param>
/// <returns></returns>
private string GetDisplayValue( object displayObject)
{
  if (DisplayField != null)
  {
    var pinfo = displayObject.GetType().GetProperty(DisplayField);
    if (pinfo != null)
    {
      return pinfo.GetValue(displayObject, null).ToString();
    }
  }
  return displayObject.ToString();
}
To show or update the displayed texts, according the object that is selected (in SelectedItem), the UpdateDisplayTexts method is needed. This is fired when Items or SelectedItem is changed. It finds SelectedItem and puts it in the middle TextBlock, and finds the texts that need to be before and after it, using the LinkedList _displayItems.
/// <summary>
/// Shows (possible) new texts in the three text boxes
/// </summary>
private void UpdateDisplayTexts()
{
  if (_displayItems == null) return;
  if (SelectedItem == null)
  {
    SelectedItem = _displayItems.First.Value;
  }
  tbCurrent.Text = GetDisplayValue(SelectedItem);
  var currentNode = _displayItems.Find(SelectedItem);
  if (currentNode == null) return;
  tbNext.Text =
    GetDisplayValue(currentNode.Next != null ? 
     currentNode.Next.Value : _displayItems.First.Value);
  tbPrevious.Text =
    GetDisplayValue(currentNode.Previous != null ? 
     currentNode.Previous.Value : _displayItems.Last.Value);
}

Now I need to set up and implement some events in the control:

  • The items must be initially displayed, after the control is loaded
  • When the user swipes the title, it must follow his finger
  • When the user stops swiping, it must decide whether the user has swiped far enough to select the next item (and if not, revert the swipe), and if the swipe was far enough, select the next item
  • When the next item is selected, show the new selected text in the middle again.
public SwipeTitle()
{
    InitializeComponent();
    _transform = new TranslateTransform();

    pnlSwipe.Loaded += (sender, args) => ProcessSelectedItemChanged();
    pnlSwipe.ManipulationDelta += pnlSwipe_ManipulationDelta;
    pnlSwipe.ManipulationCompleted += pnlSwipe_ManipulationCompleted;
    pnlSwipe.RenderTransform = _transform;
    tbPrevious.SizeChanged += tbPrevious_SizeChanged;
}

void pnlSwipe_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
    _transform.X += e.DeltaManipulation.Translation.X;
}

/// <summary>
/// Caculates the screen width
/// </summary>
private static double ScreenWidth
{
    get
    {
        var appFrame = Application.Current.RootVisual as Frame;
        return null == appFrame ? 0.0 : appFrame.RenderSize.Width;
    }
}

/// <summary>
/// Fired after manipulation is completed. When the title has been moved over 25% of the
/// screen, the next or previous item is selected
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void pnlSwipe_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
    if (e.TotalManipulation.Translation.X > .25 * ScreenWidth)
    {
        ScrollToDisplayItem(_currentDisplayItem - 1, true);
    }
    else if (e.TotalManipulation.Translation.X < -.25 * ScreenWidth)
    {
        ScrollToDisplayItem(_currentDisplayItem + 1, true);
    }
    ScrollToDisplayItem(_currentDisplayItem, true);
}

/// <summary>
/// Fired when new data is put into the last object. 
/// Rendering is then finished - the middle text is showed in the middle again
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void tbPrevious_SizeChanged(object sender, SizeChangedEventArgs e)
{
    ScrollToDisplayItem(1, false);
}

In the constructor the members are initialized – particularly the transform – which is set to the StackPanel surrounding the three TextBlocks. When the user now swipes, the texts appear to follow him. When, in pnlSwipe_ManipulationCompleted, the user has dragged the text over more than 25% of the screen, it scrolls the text further until the newly selected items is fully into view. If the text value of the last box is changed, we need to reset the middle TextBlock in the center again – but now without animation, and so it will seem if the scrolling text suddenly changes in color and appears to be selected.

Notice a little gem in here – the ScreenWidth property. It turns out you can determine the actual screensize by trying to cast the RootVisual to a Frame, and then check it’s RenderSize.

All that is left now, basically, is the implementation of ScrollToDisplayItem – and a companion method, as shown below:

/// <summary>
/// Scrolls to one of the 3 display items
/// </summary>
/// <param name="item">0,1 or 2</param>
/// <param name="animate">Animate the transition</param>
void ScrollToDisplayItem(int item, bool animate)
{
  _currentDisplayItem = item;
  if (_currentDisplayItem < 0) _currentDisplayItem = 0;
  if (_currentDisplayItem >= pnlSwipe.Children.Count)
  {
    _currentDisplayItem = pnlSwipe.Children.Count - 1;
  }
  var totalTransform = 0.0;
  for (var counter = 0; counter < _currentDisplayItem; counter++)
  {
    double leftMargin = 0;
    if (counter + 1 < pnlSwipe.Children.Count)
    {
      leftMargin = 
        ((Thickness)(pnlSwipe.Children[counter + 1].GetValue(MarginProperty))).Left;
    }
    totalTransform += 
      pnlSwipe.Children[counter].RenderSize.Width + leftMargin;
  }
  var whereDoWeGo = -1 * totalTransform;

  if (animate)
  {
    //Set up the storyboard and animate the transition
    var sb = new Storyboard();
    var anim = 
      new DoubleAnimation
      {
        From = _transform.X,
        To = whereDoWeGo,
        Duration = new Duration(
          TimeSpan.FromMilliseconds(Math.Abs(_transform.X - whereDoWeGo)))
      };
    Storyboard.SetTarget(anim, _transform);
    Storyboard.SetTargetProperty(anim, new PropertyPath(TranslateTransform.XProperty));
    sb.Children.Add(anim);
    sb.Completed += sb_Completed;
    sb.Begin();
  }
  else
  {
    _transform.X = whereDoWeGo;
  }
}

/// <summary>
/// Fired when an animation is completed. Then a new items must be selected
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void sb_Completed(object sender, EventArgs e)
{
  if (SelectedItem != null)
  {
    var currentNode = _displayItems.Find(SelectedItem);
    if (_currentDisplayItem == 0)
    {
      SelectedItem = (currentNode.Previous != null ? 
                       currentNode.Previous.Value : _displayItems.Last.Value);
    }
    if (_currentDisplayItem == 2)
    {
      SelectedItem = (currentNode.Next != null ? 
                       currentNode.Next.Value : _displayItems.First.Value);
    }
    UpdateDisplayTexts();
  }
}

In ScrollToDisplayItem the text is either moved or animated in the right direction, and when the animation is finished, the TextBlocks are updated with values according to the swipe direction. If no animation is needed – which occurs initially, or after the texts are updated – the transform is simply applied in stead of animated.

Credits

Important parts of the code came into being during a joint hacking evening at Sevensteps in Amersfoort, Netherlands, at August 31 2010. My sincere thanks to Bart Rozendaal, Kees Kleimeer and Pieter-Bas IJdens, all of Sevensteps, who basically coded the method pnlSwipe_ManipulationCompleted and the property ScreenWidth, almost all of the ScrollToDisplayItem method, and in addition supported me with wine, snacks and enjoyable company during that memorable evening.

08 August 2010

A Windows Phone 7 multi touch pan/zoom behaviour for Multi Scale Images

Some may have read my foray into using Windows Phone 7 to view maps, utilizing a Multi Scale Image (msi), MVVM Light and some extension properties. This application works quite well, but being mainly a study in applied architecture, the user experience leaves much to be desired. Studying Laurent Bugnion’s Multi Touch Behaviour got me on the right track. Although Laurent’s behaviour is very good, it basically works by translating, resizing (and optionally rotating) the control(s) inside the FrameworkElement is is attached to. For various reasons this is not an ideal solution for a map viewer.

So I set out to make my own behaviour, the first one I ever made by the way, and it turned out to remarkably easy – less than 90 lines of code, including whitespace and comments:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace LocalJoost.Behaviours
{
  /// <summary>
  /// A behaviour for zooming and panning around on a MultiScaleImage
  /// using manipulation events
  /// </summary>
  public class PanZoomBehaviour : Behavior<MultiScaleImage>
  {
    /// <summary>
    /// Initialize the behavior
    /// </summary>
    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.ManipulationStarted += AssociatedObject_ManipulationStarted;
      AssociatedObject.ManipulationDelta += AssociatedObject_ManipulationDelta;
    }
    /// <summary>
    /// Shortcut for the Multiscale image
    /// </summary>
    public MultiScaleImage Msi { get { return AssociatedObject; } }

    /// <summary>
    /// Screen point where the manipulation started
    /// </summary>
    private Point ManipulationOrigin { get; set; }

    /// <summary>
    /// Multiscale view point origin on the moment the manipulation started
    /// </summary>
    private Point MsiOrigin { get; set; }

    void AssociatedObject_ManipulationStarted(object sender,
ManipulationStartedEventArgs e) { // Save the current manipulation origin and MSI view point origin MsiOrigin = new Point(Msi.ViewportOrigin.X, Msi.ViewportOrigin.Y); ManipulationOrigin = e.ManipulationOrigin; } void AssociatedObject_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { if (e.DeltaManipulation.Scale.X == 0 && e.DeltaManipulation.Scale.Y == 0) { // No scaling took place (i.e. no multi touch) // 'Simply' calculate a new view point origin Msi.ViewportOrigin = new Point { X = MsiOrigin.X - (e.CumulativeManipulation.Translation.X / Msi.ActualWidth * Msi.ViewportWidth), Y = MsiOrigin.Y - (e.CumulativeManipulation.Translation.Y / Msi.ActualHeight * Msi.ViewportWidth), }; } else { // Multi touch - choose to interpretet this either as zoom or pinch var zoomscale = (e.DeltaManipulation.Scale.X + e.DeltaManipulation.Scale.Y) / 2; // Calculate a new 'logical point' - the MSI has its own 'coordinate system' var logicalPoint = Msi.ElementToLogicalPoint( new Point { X = ManipulationOrigin.X - e.CumulativeManipulation.Translation.X, Y = ManipulationOrigin.Y - e.CumulativeManipulation.Translation.Y } ); Msi.ZoomAboutLogicalPoint(zoomscale, logicalPoint.X, logicalPoint.Y); if (Msi.ViewportWidth > 1) Msi.ViewportWidth = 1; } } /// <summary> /// Occurs when detaching the behavior /// </summary> protected override void OnDetaching() { AssociatedObject.ManipulationStarted -= AssociatedObject_ManipulationStarted; AssociatedObject.ManipulationDelta -= AssociatedObject_ManipulationDelta; base.OnDetaching(); } } }

Method AssociatedObject_ManipulationStarted just records where the user started the manipulation, as well as what the MSI ViewportOrigin was on that moment. Method AssociatedObject_ManipulationDelta then simply checks if the delta event sports a scaling in x or y direction – if it does, it calculates the properties for a new ‘logical point’ to be fed into the ZoomAboutLogicalPoint method of the MultiScaleImage. If there is no scaling, the user just panned, and a new ViewportOrigin is being calculated in the MSI’s own coordinate system which runs from 0,0 to 1,1. And that’s all there is to it.

If you download my sample mapviewer application it’s actually quite simple to test drive this

  • Add a Windows Phone 7 class library LocalJoost.Behaviours to the projects
  • Reference this project from WP7viewer
  • Create the behaviour described above
  • Open MainPage.xaml in WP7Viewer
  • Find the MultiScaleImage called “msi” and remove all bindings except MapTileSource, so that only this remains:
<MultiScaleImage x:Name="msi" 
   MapMvvm:BindingHelpers.MapTileSource="{Binding CurrentTileSource.TileSource}">
</MultiScaleImage>
  • Add the behaviour to the MultiScaleImage using Blend or follow the manual procedure below:
  • Declare the namespace and the assembly in the phone:PhoneApplicationPage tag like this
xmlns:LJBehaviours="clr-namespace:LocalJoost.Behaviours;assembly=LocalJoost.Behaviours"
  • Add the behaviour to the MSI like this
<MultiScaleImage x:Name="msi" 
   MapMvvm:BindingHelpers.MapTileSource="{Binding CurrentTileSource.TileSource}">
  <i:Interaction.Behaviors>
      <LJBehaviours:PanZoomBehaviour/>
  </i:Interaction.Behaviors>
</MultiScaleImage>

And there you go. You can now zoom in and out using two or more fingers. That is, if you have a touch screen. If you don’t have a touch screen and you were not deemed important enough to be assigned a preview Windows Phone 7 device then you are in good company, for neither have I, and neither was I ;-). But fortunately there is Multi Touch Vista on CodePlex. It needs two mice (or more, but I don’t see how you can operate those), and it’s a bit cumbersome to set up, but at least I was able to test things properly. So, don’t let the lack of hardware deter you getting on the Windows Phone 7 bandwagon!

What I learned from this: behaviours will give an interesting twist to design decisions. When is it proper to solve things into behaviours, and when in a model by binding? For me, it’s clear that in this case the behaviour wins from the model – it’s far easier to make, understand and – above all – apply! For now, just drag and drop the behaviour on top of a MultiScaleImage and bang – zoom and pan. No complex binding expressions. 

Incidentally, although this behaviour was created with maps in mind, in can be applied to any MultiScaleImage of course, showing ‘ordinary’ image data.

For those who are not very fond of typing: the sample map application with the behaviour  already added and configured can be downloaded here. For those lucky b******s in possession of a real live device: you will find the XAP here. I would very much appreciate feedback on how the thing works in real life.