The last Visibility Converter

Posted Monday, April 08, 2013 by

The Boolean to Visibility Converter is as close you’re going to get to bread and butter in the xaml frameworks. You need it in almost every app and almost every framework has one, even the Windows 8 project templates come with one.

The trouble is that a lot of these implementations are naïve and simplistic, and soon enough you’ll be writing “Inverse Boolean to Visibility Converter”, “Int32 to Visibility Converter” etc. Ultimately these all exhibit very similar behavior, convert the default value (false, 0, null etc.) to Collapsed and anything else to Visible or do the same but inversed. Having over a dozen converters for all the implementations becomes a pain to manage and awkward.

So let’s build the last Visibility Converter you’ll need, we’ve already defined what the behavior should be, “Convert the default value to Collapsed and everything else to Visible”. The first thing we need to do is determine the default value for a type. If the type is a value type (int, boolean etc.) then we create an instance of the type otherwise the type is a reference type so the default value is null. Below is an example of this as an extension method for WinRT.

public static object GetDefaultValue(this Type type)

{

    return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null;

}

Now we can create our converter, we’ll want to have two properties to customize the behavior, the first, Inverse is pretty simplistic, the second, SupportIsNullOrEmpty is to deal with the one exception to our rules above and that’s string. The default value for string is null but we’ll typically want to treat an empty string as null, so we’ll add a second property SupportIsNullOrEmpty to be able to turn on or off dealing empty strings as null (it’ll be on by default).

public class VisibilityConverter : IValueConverter

{

    public VisibilityConverter()

    {

        SupportIsNullOrEmpty = true;

    }

 

    public bool Inverse

    {

        get;

        set;

    }

 

    public bool SupportIsNullOrEmpty

    {

        get; set;

    }

 

    public object Convert(object value, Type targetType, object parameter, string language)

    {

        bool visible;

 

        if (value is string && SupportIsNullOrEmpty)

        {

            visible = !String.IsNullOrEmpty(value.ToString());

        }

        else

        {

            var defaultValue = value != null ? value.GetType().GetDefaultValue() : null;

 

            visible = !Equals(value, defaultValue);

        }

 

        if (Inverse)

            visible = !visible;

 

        return visible ? Visibility.Visible : Visibility.Collapsed;

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, string language)

    {

        throw new NotSupportedException();

    }

}

The only downside to this approach is that ConvertBack can’t be implemented sensibly but to be honest I’ve never found a reason to have a * to Visibility converter to need it (that’s not to say there aren’t some).

Image Placeholder Control with Arbitrary Content

Posted Tuesday, February 28, 2012 by

For any application that's displaying remote images having a backup placeholder content is necessary for a polished user experience for all the times where the user has a slow internet connection, or none at all.

One possible solution from Marker Metro use Caliburn Micro and defines a behavior in order to handle failed image requests to place a placeholder image, this solution doesn't handle displaying any content before the image is loaded. David Ansom from Microsoft has created a Placeholder Image control that lets you define an image to display before the remote image is loaded and if the image is loaded.

One requirement I had was not to just display an image has a placeholder but arbitrary xaml content. What we really want to create is a marriage of Content Control and Image where we display the Content until the Image loads or if the Image fails. Since Image is a sealed control we'll create a new control based off the Content Control. This gives up the properties Content and Content Template, we'll need to add the Image properties we need Source and Stretch.

public class PlaceholderImage : ContentControl

{

    public static readonly DependencyProperty SourceProperty =

        DependencyProperty.Register("Source", typeof(ImageSource), typeof(PlaceholderImage), new PropertyMetadata(OnSourceChanged));

 

    public static readonly DependencyProperty StretchProperty =

        DependencyProperty.Register("Stretch", typeof(Stretch), typeof(PlaceholderImage), null);

 

    public PlaceholderImage()

    {

        DefaultStyleKey = typeof(PlaceholderImage);

    }

 

    public ImageSource Source

    {

        get { return (ImageSource)GetValue(SourceProperty); }

        set { SetValue(SourceProperty, value); }

    }

 

    public Stretch Stretch

    {

        get { return (Stretch)GetValue(StretchProperty); }

        set { SetValue(StretchProperty, value); }

    }

 

    ...

}

I want to use the Visual State Manager in this control to be able to have an animated transition from the content to the image; our initial state will be "Content" so we'll transition to that state in the override of OnApplyTemplate.

public override void OnApplyTemplate()

{

    base.OnApplyTemplate();

 

    VisualStateManager.GoToState(this, "Content", false);

}

In our handler for the Source property changing I follow what seems to be a standard pattern that the static method calls into a similar non static method on the control. Given this callback will be called when the image changes we’ll first make sure we’re in the Content state, we then remove our image opened handler from the previous image and add it to the new image. Once the image is loaded we’ll shift to the Image state making sure we use transitions.

private void OnSourceChanged(ImageSource oldValue, ImageSource newValue)

{

    VisualStateManager.GoToState(this, "Content", false);

 

    var oldBitmapSource = oldValue as BitmapImage;

    var newBitmapSource = newValue as BitmapImage;

 

    if(oldBitmapSource != null)

    {

        oldBitmapSource.ImageOpened -= OnImageOpened;

    }

 

    if(newBitmapSource != null)

    {

        newBitmapSource.ImageOpened += OnImageOpened;

    }

}

 

private void OnImageOpened(object sender, EventArgs e)

{

    VisualStateManager.GoToState(this, "Image", true);

}

 

private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

    var placeholderImage = (PlaceholderImage)d;

 

    placeholderImage.OnSourceChanged((ImageSource)e.OldValue, (ImageSource)e.NewValue);

}

That’s it for code, very simple; however some of the magic lives the Style for the control in Generic.xaml. This defines the Visual States and the animation between them, in this case we’ll fade the image in.

<Style TargetType="controls:PlaceholderImage">

    <Setter Property="Template">

        <Setter.Value>

            <ControlTemplate TargetType="controls:PlaceholderImage">

                <Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Margin="{TemplateBinding Margin}">

                    <VisualStateManager.VisualStateGroups>

                        <VisualStateGroup x:Name="ContentStates">

                            <VisualStateGroup.Transitions>

                                <VisualTransition GeneratedDuration="0:0:1.0">

                                    <VisualTransition.GeneratedEasingFunction>

                                        <ExponentialEase EasingMode="EaseInOut" Exponent="6"/>

                                    </VisualTransition.GeneratedEasingFunction>

                                </VisualTransition>

                            </VisualStateGroup.Transitions>

                            <VisualState x:Name="Content" />

                            <VisualState x:Name="Image">

                                <Storyboard>

                                    <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ImageContent" d:IsOptimized="True"/>

                                </Storyboard>

                            </VisualState>

                        </VisualStateGroup>

                    </VisualStateManager.VisualStateGroups>

                    <ContentControl Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />

                    <Image x:Name="ImageContent" Source="{TemplateBinding Source}" Stretch="{TemplateBinding Stretch}" Opacity="0" />

                </Grid>

            </ControlTemplate>

        </Setter.Value>

    </Setter>

</Style>

The usage of the control is pretty simple, any content declared inside the control is used as placeholder content.

<controls:PlaceholderImage Source="{Binding Product.ThumbUrl, Converter={StaticResource BitmapImage}}" HorizontalAlignment="Center">

    <TextBlock Text="Loading"/>

</controls:PlaceholderImage>

I've included the code for this in the Compiled Experience Phone Toolkit I'm putting together so you can download it and use within your apps. Take a look and let me know how it can be improved.

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.

Visual State Manager and Coroutines

Posted Monday, September 26, 2011 by

Well it's been quite a while since I've posted anything having spent the last five weeks in Canada getting married and St Lucia on our honeymoon. As usual a lot happens while you're away, with Mango getting closer to release (and the marketplace opening for Mango apps), the unveiling of Windows 8 at Build and other smaller news. With all this there's been a lot to catch up but after five weeks away from development I've been itching to get back to it. I’m currently working on Mango updates for Left to Spend and To Do Today, I’m taking the opportunity to rebuild bits of To Do Today with some of the techniques I’ve learnt in the last year so I’ll hopefully be discussing some of those as I go.

One of the cool and often over looked features in Caliburn Micro is the IResult interface and its use in Coroutines, there's some excellent documentation on the Caliburn Micro website so I won't go into a lot of detail regarding how they work. One of the aspects I really like (besides helping to simplify asynchronous code) is that they better enable View, View Model separation even when you need to interact directly with the View.

A great example of this is the Visual State Manager, the visual state of a screen is something that there's a definite use case to control from the View Model as it’s a smooth way to have animated states within the page. Previously my approach has been to expose a string "State" property on the View Model and bind this to a custom attached dependency property. This works but I feel it has a few weaknesses, a page can be in multiple states at any one time due to Visual State Groups (the page will be in a state for each group). This isn't represented well by the State property and in reality really hides that fact. It also doesn’t control the need to turn on and off state transitions.

Because the ActionExecutionContext passed to the IResult has a reference to the View we can manipulate the Visual State Manager directly in the IResult without worrying about bindings. We’ve also preserved the distinction between the View and View Model so we can unit test our View Model changes the state without execution of the IResult. So what does the code look like, pretty simple really, I have a ResultBase class to handle some of the plumbing in IResult. From this class I create my VisualStateResult that takes the appropriate parameters. On Execute we verify the View is a Control and then invoke GoToState.

public abstract class ResultBase : IResult

{

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

 

    protected virtual void OnCompleted()

    {

        OnCompleted(new ResultCompletionEventArgs());

    }

 

    protected virtual void OnError(Exception error)

    {

        OnCompleted(new ResultCompletionEventArgs

        {

            Error = error

        });

    }

 

    protected virtual void OnCompleted(ResultCompletionEventArgs e)

    {

        Caliburn.Micro.Execute.OnUIThread(() => Completed(this, e));

    }

 

    public abstract void Execute(ActionExecutionContext context);

}

public class VisualStateResult : ResultBase

{

    private readonly string stateName;

    private readonly bool useTransitions;

 

    public VisualStateResult(string stateName, bool useTransitions = true)

    {

        this.stateName = stateName;

        this.useTransitions = useTransitions;

    }

 

    public string StateName

    {

        get { return stateName; }

    }

 

    public bool UseTransitions

    {

        get { return useTransitions; }

    }

 

    public override void Execute(ActionExecutionContext context)

    {

        if(!(context.View is Control))

            throw new InvalidOperationException("View must be a Control to use VisualStateResult");

 

        var view = (Control)context.View;

 

        VisualStateManager.GoToState(view, StateName, UseTransitions);

 

        OnCompleted();

    }

}

yield return new VisualStateResult("Complete", useTransitions: false);

Using Caliburn Micro as a Data Template Selector

Posted Tuesday, July 19, 2011 by

WPF has a really nice feature in the DataTemplateSelector that helps us use different DataTemplate's within the same ItemsControl depending on the data being bound. Out of the box this functionality isn't included with Windows Phone, but it can be implemented in a number of different ways. In this post I'm going to show how we can use Caliburn Micro to achieve the same functionality.

Caliburn Micro is really optimised and designed for a "View Model first" approach, by this we pass the framework the view model we wish to display, it then uses conventions to determine the view for that view model and binds them together.

Windows Phone 7 and it's requirements around the navigation frame and pages enforces a "View first" approach, this doesn't mean we can't use the "View Model" first within our pages.

When Caliburn applies it's conventions to a ItemsControl (the base class of controls such as ListBox and Pivot) checks to see if the ItemTemplate has been set. If one hasn't Caliburn will set a very simple but powerful DataTemplate that looks like:

<DataTemplate>

    <ContentControl cal:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />

</DataTemplate>

By binding the View Model to the Content Control tells Caliburn to locate the appropriate View and instantiate it within the Control Control. The important thing to think about here is that if the view model type is different for items in the ListBox then the views created by Caliburn will be different.

For our example we'll have an activity stream with three different sorts of activities, announcements, status updates and new image galleries.

First we'll create Caliburn View Models for each of our three activities (AnnouncementViewModel, StatusViewModel, GalleryViewModel), we'll have a shared base View Model for common data (ActivityViewModel). On our activity page the view model will expose an observable collection of our base activity view model, on view model initialisation we'll populate the collection with a variety of activities.

public class GalleryViewModel : ActivityViewModel

{

    private string title;

    private Uri image;

 

    public string Title

    {

        get { return title; }

        set

        {

            title = value;

            NotifyOfPropertyChange(() => Title);

        }

    }

 

    public Uri Image

    {

        get { return image; }

        set

        {

            image = value;

            NotifyOfPropertyChange(() => Image);

        }

    }

}

 

public class AnnouncementViewModel : ActivityViewModel

{

    private string headline;

 

    public string Headline

    {

        get { return headline; }

        set

        {

            headline = value;

            NotifyOfPropertyChange(() => Headline);

        }

    }

}

 

public class StatusViewModel : ActivityViewModel

{

    private string username;

    private string text;

 

    public string Username

    {

        get { return username; }

        set

        {

            username = value;

            NotifyOfPropertyChange(() => Username);

        }

    }

 

    public string Text

    {

        get { return text; }

        set

        {

            text = value;

            NotifyOfPropertyChange(() => Text);

        }

    }

}

 

public class ActivityListViewModel : Screen

{

    public ActivityListViewModel()

    {

        Activities = new BindableCollection<ActivityViewModel>();

    }

 

    protected override void OnInitialize()

    {

        Activities.Add(new AnnouncementViewModel

        {

            Headline = "To Do Today 1.5 Released",

            OccuredOn = DateTime.Now

        });

 

        Activities.Add(new GalleryViewModel

        {

            Title = "Lorem Pixum",

            Image = new Uri("http://lorempixum.com/g/140/140/technics/", UriKind.Absolute),

            OccuredOn = DateTime.Today.AddDays(-1)

        });

 

        Activities.Add(new StatusViewModel

        {

            Username = "@nigel-sampson",

            Text = "Up late working on client apps.",

            OccuredOn = new DateTime(2011, 07, 11, 11, 00, 00)

        });

    }

 

    public IObservableCollection<ActivityViewModel> Activities

    {

        get;

        set;

    }

}

Now to create the views for our various activity, rather than the standard Windows Phone view of a PhoneApplicationPage our views should be a UserControl. Remember to follow the conventions Caliburn has so that the view can be located correctly (AnnouncementView, StatusView, GalleryView).

Here's an example of one of the the templates, the AnnouncementView.

<UserControl x:Class="CompiledExperience.Phone.Demo.Examples.Views.AnnouncementView"

   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="140" d:DesignWidth="456">

 

    <StackPanel x:Name="LayoutRoot" Margin="0,6">

        <TextBlock x:Name="Headline" Style="{StaticResource PhoneTextLargeStyle}"/>

        <StackPanel Orientation="Horizontal">

            <TextBlock Text="occurred on" Style="{StaticResource PhoneTextSubtleStyle}"/>

            <TextBlock x:Name="OccuredOn" Style="{StaticResource PhoneTextSubtleStyle}" Margin="0"/>

        </StackPanel>

    </StackPanel>

</UserControl>

 

Once the views have been created we're pretty much done, by convention Caliburn will bind our Activities list to the ListBox and creates instances of our views based on the view models and we're done.

<Grid x:Name="LayoutRoot" Background="Transparent">

    <Grid.RowDefinitions>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="*"/>

    </Grid.RowDefinitions>

 

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">

        <TextBlock x:Name="ApplicationTitle" Text="COMPILED EXPERIENCE" Style="{StaticResource PhoneTextNormalStyle}"/>

        <TextBlock x:Name="PageTitle" Text="templates" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>

    </StackPanel>

 

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

        <ListBox x:Name="Activities"/>

    </Grid>

</Grid>

What's really good about this approach is that you don't need to try and build a single data template that needs to deal with all types of content we need to display. Each view can be completely independent of the other views. On a higher level a similar approach can be used to separate the different sections of a Pivot or Panorama.

Page 1 of 1512345>

Professional Windows App Development