Using value converters by convention

Posted Thursday, March 27, 2014 by

The application of a convention in Caliburn.Micro is a bit of a pipeline with multiple steps, almost all of them we can modify, let's look at bringing value converters into the equation.

Most of the time the default conventions tend to bring like-minded types together. That's natural in that conventions should be reasonably intuitive, like a string property on the view model being bound to the Text property of the TextBox.

Sometimes these types don't always line up, which is where value converters typically come in. The most common of these being converting between boolean and visibility properties.

Caliburn.Micro does a little bit of work here already, if a convention calls for properties of the two above types (boolean and visibility) then it automatically adds a BooleanToVisibilityConverter to the Binding.

We can extend this through the modification of the ConventionManager.ApplyValueConverters method, but why would we do this? In an app I'm currently working on I want to use relative dates through the app, I'm using a value converter that leverages the Humanizer library (check it out if you haven't already) to do the conversion. This means everywhere I want to out put the date I need to dispense with the normal convention using x:Name and set up the binding with the value converter. A bit of a pain and there's always a chance I could miss one.

What we need to do is modify Caliburn such that when it applies a convention between a DateTime (or DateTimeOffset) property and a TextBlock's Text when add the converter to the process.

The code is pretty simple, the only weird part is taking a reference to the default implementation first so that we can keep the default behavior as well.

var baseApplyValueConverter = ConventionManager.ApplyValueConverter;

 

ConventionManager.ApplyValueConverter = (binding, bindableProperty, property) =>

{

    baseApplyValueConverter(binding, bindableProperty, property);

 

    if (bindableProperty == TextBlock.TextProperty && typeof(DateTime).IsAssignableFrom(property.PropertyType))

        binding.Converter = new RelativeDateTimeConverter();

 

    if (bindableProperty == TextBlock.TextProperty && typeof(DateTimeOffset).IsAssignableFrom(property.PropertyType))

        binding.Converter = new RelativeDateTimeConverter();

};

Now whenever a convention on a TextBlock is wired to a DateTime it will display a relative date time. We can remove all our bindings and converters from code and go back to simple x:Name conventions.

Custom Special Values in Caliburn.Micro

Posted Sunday, March 2, 2014 by

Caliburn.Micro has features to allow you bind methods to actions with parameters. Check out the Action Parameters section of the documentation. Part of this set of features includes "special values" such as $eventArgs or $dataContext, these allow an easy to access information contextual to the action.

I use these special values in a number of different ways. When binding a list of values the item template may contain a button with the following:

<Button Style="{StaticResource ContentButton}" cm:Message.Attach="ViewSection($dataContext)">

This action bubbles up to the root model passing the item in the list the button was pressed for.

public void ViewSection(RepositorySection group)

Another is making use of the ItemClick functionality on GridView and ListView in Windows 8. This provides similar functionality without requiring buttons in your item templates. The difference is that clicked item is in the event args so I'd use syntax something similar to:

<ListView cm:Message.Attach="[Event ItemClick] = [SelectItem($eventArgs)]" IsItemClickEnabled="True">

public void SelectItem(ItemClickEventArgs e)

Another example is around pinning tiles in Windows 8, for better user experience we want to pass an invocation point to SecondaryTile.RequestCreateAsync to ensure the popup is near the element that invoked it. You can get access to element using $source or the $executionContext.

public async void ToggleTile(ActionExecutionContext context)

{

    var transform = context.Source.TransformToVisual(null);

 

    var invocationPoint = transform.TransformPoint(new Point(0, 0));

While all these solutions solve the problem at hand they do make the code messier than I'd ideally like. The ItemClick example forces you view model to depend on a xaml event args object with an internal constructor. While the invocation point sample forces the view model to start dealing with UI elements and their layout on the screen.

So how do we solve these problems? We can actually create custom special values, this removes the weird dependencies on the view model and increases code reuse as the special values are only dealt with once. For the above examples we'd have the following code in our bootstrapper (or application) depending platform.

MessageBinder.SpecialValues.Add("$invocationpoint", c => c.Source.TransformToVisual(null).TransformPoint(new Point()));

MessageBinder.SpecialValues.Add("$clickeditem", c => ((ItemClickEventArgs)c.EventArgs).ClickedItem);

Important points to note is that the key's must start with $ and be lowercase (they don't need to be in the xaml though). We can now change our xaml for our examples to the following.

<ListView cm:Message.Attach="[Event ItemClick] = [SelectIssue($clickedItem)]"

<AppBarButton cm:Message.Attach="TogglePin($invocationPoint)"

There's a myriad of ways this trick could possibly be put to use. I'm going back through old projects and looking where I'm depending on special values and seeing if they can be simplified.

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.

Have you registered it with the container?

Posted Tuesday, January 14, 2014 by

These days there’s a plethora of Inversion of Control (IoC) containers in the .NET ecosystem, from classics such as Castle Windsor and Autofac to the smaller ones that seem to ship with every MVVM framework. They all have their own idiosyncrasies in their behavior that really let you find one that best suits the way you code.

One behavior that’s a clear separator is how each container deals with a request that can’t be completed. This could be for a number of reasons, the type or interface isn't registered with the container, lack of public constructor etc. Some containers will throw an exception, others return null, and others like MicroIoC do their best to deal with it.

The SimpleContainer / WinRT Container in Caliburn.Micro is one that returns null if a request for a type can’t be completed and will often the failing silently. Usually this manifests when you've just created a new view / view model pair, haven’t registered the view model into the container and you’re left wondering why your view model isn't working. For a while at the Marker Metro offices we had a scoreboard the amount of times we’d forgotten and wasted time hunting for the problem (I can't reveal the leader).

One way to solve this is to use a container like MicroIoC that when a request for a concrete type (not abstract or an interface) that hasn’t been registered it will register that type automatically (under a Per Request lifecycle).

Depending on how long it’s been since I've run into the issue I’ll have different opinions on this behavior, this will certainly solve the aforementioned problem, but I often find it can lead to laziness in the registering of things other than view models and if any are statefull (such as a caching layer) you can run into some transparent bugs.

Another way to at least help you catch these errors during development time by modifying the GetInstance overload in Caliburn.Micro’s Bootstrapper / CaliburnApplication to something like as follows:

protected override object GetInstance(Type service, string key)

{

    var instance = container.GetInstance(service, key);

 

    if (instance == null)

        throw new InvalidOperationException(String.Format("Could not resolve type {0}", service));

 

    return instance;

}

While this will still fail silently as the exception is caught and handled within Caliburn.Micro, if you have a debugger attached you catch the exception and save yourself some precious time.

Interactive Extensions as a PCL

Posted Tuesday, October 29, 2013 by

Interactive Extensions is a fantastic library from the team that brought us Reactive Extensions, in fact it supports a lot same operations on IEnumerable that Rx brought to IObservable.

The best way to think about it is that it's a library that takes Linq to the next level, there's a nice summary at Interactive Extensions for LINQ to Objects.

However while Ix has been available as a Portable Class Library for a while now it didn't include the appropriate flags for WinRT development. But now it does! Checked in last month to the source was an update to allow it to be used in WinRT, it's not yet published to nuget but I'd highly recommend checking it out.

Page 1 of 2112345>

Professional Windows App Development