If you've done any amount of development with MVVM, one of the things you'll hate most is creating Value Converters and Commands for the smallest of things - especially for things like formatting, placement, visibility etc. And a lot of the times, these converters and commands are pertinent only to the View itself, but have to be declared on a module-wide basis. So as a solution, I've created "relays" for exposing IValueConverter and ICommand, which allow the logic to be defined in the code-behind (or ViewModel if you prefer), without having to create extraneous/individual classes.
Simple Example : Value Converter Relay
In one of my apps, I have a play/pause type of functionality which required a button to display either the play or pause sign, which is controlled by a "IsPlaying" property in the ViewModel. Now, the IsPlaying property is bool type, and we display the play/pause sign using the Webdings font in text. Normally, for this smallest of functionality, one would have to create an IValueConverter implementation and declare it in xaml, then consume it with binding. But with the relays we just declare a relay in xaml and then define the functionality in the code-behind, without having to create any specific class - have a look:
In XAML (with cmpnts being the xmlns declaration)
1: <cmpnts:ValueConverterRelay x:Key="PlayPauseTextConverter" />
and in the code behind we write this
1: this.SetResourceConverter<bool, string>("PlayPauseTextConverter", (b) => b ? ";" : "4");
and the button uses the converter, as usual
1: <Button FontFamily="Webdings" Content="{Binding IsPlaying,
2: Converter={StaticResource PlayPauseTextConverter}, Mode=OneWay}" />
Compared to the normal parade, this is much more immediate and compact - though for MVVM purists it might be polluting the code-behind. For my tastes, this works perfectly well and has a couple of advantages too - for one, it is strongly typed, allows lambdas/delegate based conversion, it enables referencing to and use of locally-scoped elements/variables and keeps the UI-related trickery close-by.
As far as the markup is concerned you declare every converter as ValueConverterRelay type, just with different keys. And then in the code-behind, using an extension method (on any FrameworkElement type, to which the resource belongs) or alternatively by directly setting the converter we specify the implementation logic. Note we can specify both "convert" and "convert back" IValueConverter calls equivalents using the SetResourseConverter extension method or equally specify a non-generic implementation.
Another Example : Command Relay
The companion to the converter relay, is the command relay type - and it is basically similar in declaration and use.
In XAML (with cmpnts being the xmlns declaration) we define a CommandRelay resource
1: <cmpnts:CommandRelay x:Key="SelectContactCommand" />
and then in the code-behind something like
1: this.SetResourceCommand<ContactInfo>("SelectContactCommand", (c) => OnSelectContact(c));
To consume the commands, as shown below, I'm making use of nRoute's attached behaviours to hook-in the commands to the user-control's MouseLeftButtonDown event
1: <DataTemplate x:Key="ContactTemplate">
2: <view:ContactView cmd:MouseDown.Command="{StaticResource SelectContactCommand}"
3: cmd:MouseDown.CommandParameter="{Binding }"/>
4: </DataTemplate>
Like before with the converter relay, we declare the command relay in the resources with a uniquely key which we then use to set the implementation in the code-behind - in this case using the SetResourceCommand extension method. Again note the command declaration is strongly typed, and makes use of the generic/delegate-based command implementations I had blogged about earlier.
As a side note, here I've purposely shown the command linking within a data template, because if you were to use a event handler hooked to the code-behind it could potentially leak memory - specially if the host control's life span outlasts that of the templated view (which is often the case, like when you are paging). And the difference in using attached behaviours, if designed properly, is that they hold a weak reference and should be GC'able.
One Complication
But then there is a twist; now, depending on how early in the control's lifecycle you make use of the converter/command, the relay might not be available immediately - particularly when the xaml is just parsed/initialized. And this is a big problem, because it means things would not work initially or worse they would "break" the databinding. In these cases we'll need to handle an "Initialize" event on the command or converter relay, during which we specify the implementation through the event argument - have a look:
In XAML, we hook up the Initialize event (and this shouldn't leak memory as resources normally last the entire lifetime of their hosts, unless you decide otherwise in which case you'll need to clear up any handlers)
1: <cmpnts:ValueConverterRelay x:Key="CountToIndexConverter"
2: Initalize="CountToIndexConverter_Initialize"/>
In the code-behind, we create and pass-back the converter through the event argument
1: void CountToIndexConverter_Intitalize(object sender, ValueConverterInitializeEventArgs e)
2: {
3: e.Converter = new ValueConverter<int, double>((i) => i == 0 ? 0d : i - 1);
4: }
Obviously, this isn't ideal but as far as I know there is no precise way to interject between the resources creation and it's immediate use post-parsing of xaml - but resolving through an event handler works, and for the most part is more convenient than creating individual classes for each specific conversion or command you need.
Summary
So the basic idea here is to enable easy and compact defining and consumption of commands and value converters. However, it must be said, this is not a substitute for ViewModel based commands or general converters, rather my recommendation is to use it for consuming or manipulating visuals-related logic (as opposed to the business-related logic).
The code is attached below, and both these relays form part of the next drop of nRoute (which BTW has many new exciting ideas, stay tuned).
Download Code
Posted by Rishi on 25-Aug-09 9:06 AM, 9 Comments