Views for types that aren't View Models

Posted Thursday, February 20, 2014 by

In most MVVM style frameworks we're used of seeing pairs of views and view models, RepositoryView and RepositoryViewModel for instance. We may sometimes have view models without views (such as GroupViewModel) but how do we deal with instances when we want views for types that aren't view models?

I'm currently redesigning and rebuilding bits of Hub Bug and making use of the Octokit.NET library from GitHub. This awesome nuget package provides 90% of what I'd call my model. Often view models wrap these model types when bound to the view. Yet some of the smaller types such as Label and User may be bound directly to the view inside list boxes or form small parts of a large view. Creating view models just so Caliburn.Micro's ViewLocator can locate the correct view feels like a waste as the view models don't add any functionality and create unnecessary ceremony.

By default Caliburn.Micro won't know what to do with these types, they don't end in "ViewModel" so it won't find any views. Even if it did it would try to locate them in the Octokit assembly which definitely isn't something we want.

What we want to do is give the ViewLocator a hint that when it tries to locate views for certain types to look somewhere where it wouldn't normally look. In this case When it locates a view for Octokit.User I want to it to use HubBug.App.Views.Octokit.UserView. In application set up (either the Bootstrapper or the Application depending on platform) I have the following:

ViewLocator.NameTransformer.AddRule(

    @"^Octokit\.(\w*)",

    @"HubBug.App.Views.Octokit.${1}View");

This uses lovely old regular expressions to transform the view model type to the view type, that's all it takes.

What's very cool about this is that I now have a consistent way to display Users, whether I'm binding to a ComboBox or it's part of a Grid. Caliburn makes sure I'm using the correct view in every instance.

For instance any where I want to display a user I can drop in the following xaml, tweak the binding appropriate to the view and I'm done.

<ContentControl micro:View.Model="{Binding Repository.Owner}" FontSize="{StaticResource ControlContentThemeFontSize}"/>

ViewLocator has quite a few customisation options, take a look and see how you can streamline your app development.

The last Visibility Converter

Posted Monday, April 8, 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.

Page 1 of 1512345>

Professional Windows App Development