Storyboards in Caliburn Micro

Posted Monday, November 14, 2011 by

In my previous post I talked about the benefits of using co-routines in Caliburn Micro to ease any interactions with the View from the View Model. In that case it was the use of the Visual State Manager; in this post we’ll cover managing storyboards and animation.

We’ll use code from an older post around how to create one off event handlers. What I’ve done is encapsulate that logic into an extension method ToObservable.

public static IObservable<IEvent<EventArgs>> ToObservable(this Storyboard storyboard)

{

    if(storyboard == null)

        throw new ArgumentNullException("storyboard");

 

    return Observable.FromEvent((EventHandler<EventArgs> e) => new EventHandler(e),

                                e => storyboard.Completed += e,

                                e => storyboard.Completed -= e);

}

In the BeginStoryboardResult we verify the view is a FrameworkElement (and therefore can contain Resources). We then load the Storyboard from the Resources collection. Using the extension method we wire the Completed event of the Storyboard to the completion of the IResult.

public class BeginStoryboardResult : ResultBase

{

    private readonly string storyboardName;

 

    public BeginStoryboardResult(string storyboardName)

    {

        this.storyboardName = storyboardName;

    }

 

    public string StoryboardName

    {

        get { return storyboardName; }

    }

 

    public override void Execute(ActionExecutionContext context)

    {

        if(!(context.View is FrameworkElement))

            throw new InvalidOperationException("View must be a framework element to use BeginStoryboardResult");

 

        var view = (FrameworkElement)context.View;

 

        if(!view.Resources.Contains(StoryboardName) || !(view.Resources[StoryboardName] is Storyboard))

            throw new InvalidOperationException(String.Format("View doesn't the contain a Storyboard with the key {0} as a resource", StoryboardName));

 

        var storyboard = (Storyboard)view.Resources[StoryboardName];

 

        storyboard.ToObservable().Take(1)

            .Subscribe(e => OnCompleted());

 

        storyboard.Begin();

    }

}

After that it’s pretty much just starting the actual storyboard.

Again one of the main benefits of result an IResult like this is we still maintain separation between the view model and the view, we can now create unit tests that test how the view model plays storyboards without requiring the actual storyboard.

Useful WP7 App for Developers

Posted Wednesday, March 30, 2011 by

As a developer for Windows Phone 7 one app I've found incredibly useful is AppTracker from Very Software. It quickly brings together all reviews for an app from the different marketplaces.

It quickly gives me an idea of how new versions of To Do Today are performing and also when there are new reviews.

I highly recommend you check it out on the Marketplace

Common Problem with Notifications from a View Model

Posted Monday, March 28, 2011 by

In the MVVM (Model, View, ViewModel) pattern in xaml based applications (Windows Phone 7, Silverlight and WPF) there are two different ways a view model will notify the view of changes. If you're unaware of the differences you can end up wondering why your changes aren't being reflected in your view.

The most common is implementing the INotifyPropertyChanged interface, the other is through the INotifyCollectionChanged interface. Most developers really won't use second interface but its useful to know about and how it affects your view model.

When you bind to a property on your view model the binding infrastructure will listen for INotifyPropertyChanged events from your view model for that property.

If the type of the property implements INotifyCollectionChanged (such as ObservableCollection) it will also listen for events off that property.

The distinction is important because a common pattern with developers is implement collection properties as automatic properties. This however limits the way you can interact with the collection such that changes will be reflected in the view.

public class CorrectViewModel : ViewModelBase

{

    public CorrectViewModel()

    {

        Items = new ObservableCollection<Item>();??

    }

 

    public ObservableCollection<Item> Items

    {

        get; set;

    }

 

    public void Add()

    {

        var items = GetItems();

 

        Items.Clear(); // Collection changed raised

 

        foreach(var item in items)

        {

            Items.Add(item); // Collection changed raised

        }

    }

 

    private IEnumerable<Item> GetItems()

    {

        return from i in Enumerable.Range(0, 10)

                select new Item

                {

                    Name = "Item: " + i

                };

    }

}

 

public class IncorrectViewModel : ViewModelBase

{

    public IncorrectViewModel()

    {

        Items = new ObservableCollection<Item>();

    }

 

    public ObservableCollection<Item> Items

    {

        get;

        set;

    }

 

    public void Add()

    {

        var items = GetItems();

 

        Items = new ObservableCollection<Item>(); // No notification raised

 

        foreach(var item in items)

        {

            // Collection changed raised but view unaware of new collection

            Items.Add(item);

        }

    }

 

    private IEnumerable<Item> GetItems()

    {

        return from i in Enumerable.Range(0, 10)

                select new Item

                {

                    Name = "Item: " + i

                };

    }

}

Notice the difference between the correct view model and the incorrect one is that the correct one alters the already existing collection and the view is notified with INotifyCollectionChanged events. The incorrect view model replaces the collection with an entirely new one, but because the automatic property doesn't fire an INotifyPropertyChanged event the changes will not be reflected in the view.

The incorrect view model can be fixed by either modifying the Add method to not replace the collection but modify it in place, or to modify the Items collection to fire property changed events.

Useful Value Converters

Posted Saturday, January 29, 2011 by

Value converters are a really useful part of the xaml binding infrastructure, they work in Windows Phone 7, Silverlight and WPF. As we work on more and more projects in this space we build up a library of useful value converters. I'd like to illustrate some of the ones I use here.

Bitmap Image Converter

public class BitmapImageConverter : IValueConverter

{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {

        if(value is string)

            return new BitmapImage(new Uri((string)value, UriKind.RelativeOrAbsolute));

 

        if(value is Uri)

            return new BitmapImage((Uri)value);

 

        throw new NotSupportedException();

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

        throw new NotSupportedException();

    }

}

Boolean To Visibility Converter

public class BooleanToVisibilityConverter : IValueConverter

{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {

        if(value == null)

            return Visibility.Collapsed;

 

        var isVisible = (bool)value;

 

        return isVisible ? Visibility.Visible : Visibility.Collapsed;

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

        var visiblity = (Visibility)value;

 

        return visiblity == Visibility.Visible;

    }

}

Colour To Brush Converter

public class ColorToBrushConverter : IValueConverter

{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {

        if(value == null)

            return new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));

 

        if(value is Color)

            return new SolidColorBrush((Color)value);

 

        if(value is string)

            return new SolidColorBrush(Parse((string)value));

 

        throw new NotSupportedException("ColorToBurshConverter only supports converting from Color and String");

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

        throw new NotSupportedException();

    }

 

    private static Color Parse(string color)

    {

        var offset = color.StartsWith("#") ? 1 : 0;

 

        var a = Byte.Parse(color.Substring(0 + offset, 2), NumberStyles.HexNumber);

        var r = Byte.Parse(color.Substring(2 + offset, 2), NumberStyles.HexNumber);

        var g = Byte.Parse(color.Substring(4 + offset, 2), NumberStyles.HexNumber);

        var b = Byte.Parse(color.Substring(6 + offset, 2), NumberStyles.HexNumber);

 

        return Color.FromArgb(a, r, g, b);

    }

}

Simple Type Converter

public class SimpleTypeConverter : IValueConverter

{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {

        return System.Convert.ChangeType(value, targetType, CultureInfo.CurrentCulture);

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

        return System.Convert.ChangeType(value, targetType, CultureInfo.CurrentCulture);

    }

}

String Format Converter

public class StringFormatConverter : IValueConverter

{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {

        if(parameter == null && value == null)

            return String.Empty;

 

        if(parameter == null)

            return value.ToString();

 

        return String.Format(CultureInfo.CurrentCulture, parameter.ToString(), value);

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

        throw new NotSupportedException();

    }

}

One nice thing about value converters is because they're simple pluggable pieces of code they've easy to unit test. The unit tests for our Boolean To Visibility Converter may look like.

[TestClass]

public class BooleanToVisibilityConverterFixture

{

    [TestMethod]

    public void ConvertWithNullValueReturnsCollapsed()

    {

        var converter = new BooleanToVisibilityConverter();

        var value = converter.Convert(null, typeof(Visibility), null, CultureInfo.CurrentCulture);

 

        Assert.AreEqual(Visibility.Collapsed, value);

    }

 

    [TestMethod]

    public void ConvertWithFalseReturnsCollapsed()

    {

        var converter = new BooleanToVisibilityConverter();

        var value = converter.Convert(false, typeof(Visibility), null, CultureInfo.CurrentCulture);

 

        Assert.AreEqual(Visibility.Collapsed, value);

    }

 

    [TestMethod]

    public void ConvertWithTrueReturnsVisible()

    {

        var converter = new BooleanToVisibilityConverter();

        var value = converter.Convert(true, typeof(Visibility), null, CultureInfo.CurrentCulture);

 

        Assert.AreEqual(Visibility.Visible, value);

    }

 

    [TestMethod]

    public void ConvertBackWithVisibleReturnsTrue()

    {

        var converter = new BooleanToVisibilityConverter();

        var value = converter.ConvertBack(Visibility.Visible, typeof(bool), null, CultureInfo.CurrentCulture);

 

        Assert.AreEqual(true, value);

    }

 

    [TestMethod]

    public void ConvertBackWithCollapsedReturnsFalse()

    {

        var converter = new BooleanToVisibilityConverter();

        var value = converter.ConvertBack(Visibility.Collapsed, typeof(bool), null, CultureInfo.CurrentCulture);

 

        Assert.AreEqual(false, value);

    }

I tend to create a resource dictionary named Converters.xaml to hold references to all the converters I need.

<ResourceDictionary

   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:c="clr-namespace:CompiledExperience.Kore.Client.Converters"

   mc:Ignorable="d">

 

    <c:BitmapImageConverter x:Key="BitmapImage"/>

    <c:BooleanToVisibilityConverter x:Key="BooleanToVisiblity"/>

    <c:ColorToBrushConverter x:Key="ColorToBrush"/>

    <c:SimpleTypeConverter x:Key="SimpleType"/>

    <c:StringFormatConverter x:Key="StringFormat"/>

 

</ResourceDictionary>

Once that's created then you modify your Binding statements to use the Converter.

<Rectangle Fill="{Binding Colour, Converter={StaticResource ColourToBrushConverter}}" Width="12" Height="72" />

If you're using Expression Blend to wire up your bindings then you can select the Converter from the provided drop down.

Relative Date Time Converter

Posted Saturday, September 11, 2010 by

Value converters are really what help make good applications become great, they plug into the binding infrastructure and help convert values appropriate to the model into values appropriate for the view. Being able to convert say a temperature from a simple integer into something like a SolidColourBrush gives a very easy representation of heat.

These work in all three of the xaml based frameworks, Windows Phone 7, Silverlight and WPF and because value converters are so compact they're also very testable.

A lot of sites represent days in a more interesting "5 minutes ago" format, so I set about throwing together a quick value converter for this, converting from a date to a string. I've become a big fan of using dictionaries of functions in this way for times when many if statements become too messy and a strategy style pattern is overkill.

public class RelativeDateTimeConverter : IValueConverter

{

    private const int Minute = 60;

    private const int Hour = Minute * 60;

    private const int Day = Hour * 24;

    private const int Year = Day * 365;

 

    private readonly Dictionary<long, Func<TimeSpan, string>> thresholds = new Dictionary<long, Func<TimeSpan, string>>

    {

        {2, t => "a second ago"},

        {Minute,  t => String.Format("{0} seconds ago", (int)t.TotalSeconds)},

        {Minute * 2,  t => "a minute ago"},

        {Hour,  t => String.Format("{0} minutes ago", (int)t.TotalMinutes)},

        {Hour * 2,  t => "an hour ago"},

        {Day,  t => String.Format("{0} hours ago", (int)t.TotalHours)},

        {Day * 2,  t => "yesterday"},

        {Day * 30,  t => String.Format("{0} days ago", (int)t.TotalDays)},

        {Day * 60,  t => "last month"},

        {Year,  t => String.Format("{0} months ago", (int)t.TotalDays / 30)},

        {Year * 2,  t => "last year"},

        {Int64.MaxValue,  t => String.Format("{0} years ago", (int)t.TotalDays / 365)}

    };

 

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {

        var dateTime = (DateTime)value;

        var difference = DateTime.UtcNow - dateTime.ToUniversalTime();

 

        return thresholds.First(t => difference.TotalSeconds < t.Key).Value(difference);

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

        throw new NotSupportedException();

    }

}

<TextBlock Style="{StaticResource PhoneTextSubtleStyle}" Text="{Binding Created, Converter={StaticResource RelativeDateTimeConverter}}" />

Page 1 of 212>

Professional Windows App Development