Getting Rid of XAML Boilerplate

Source Code

So Much Boilerplate Code

Let me explain the problem…

You’re writing some XAML, all is good in the world, when you realise that you want to hide a control (or change it’s font, or opacity, or any other property for that matter) when [some condition] is true.


<TextBlock Text="Everthing's Fine" /> <!-- hide when ErrorCount==0 -->
<TextBlock Text="Oh no! Errors!" /> <!-- hide when ErrorCount!=0 -->

Sounds simple, right? And it is! At this point we could easily use Styles and Triggers to set the Visibility like this:


<TextBlock Text="Everthing's Fine">
	<TextBlock.Style>
		<Style TargetType="TextBlock">
			<Style.Triggers>
				<DataTrigger Binding={Binding ErrorCount} Value="0">
					<Setter Property="Visibility" Value="Collapsed" />
				</DataTrigger>
			</Style.Triggers>
		</Style>
	</TextBlock.Style>
</TextBlock>
<TextBlock Text="Oh no!  Errors!">
	<!-- similar style here -->
</TextBlock>

Unfortunately this approach, whilst functional, can quickly make even simple XAML files grow to be huge and unreadable – 10 extra lines and 5 extra indentations just to say “sometimes hide this control”! If you use the same condition in lots of places then you could move this style out into the resources, but that doesn’t help the many one-off styles that I keep creating.

So how to get rid of all this boilerplate?

An Ideal Solution… and Why We Can’t Have It

In a perfect world I would like to write all of this on a single line in a nice readable format. Something like…

<TextBlock Text="Everthing's Fine" 
    Visibility="{l:When {Binding ErrorCount}, IsEqualTo=0, Return=Visible, Else=Collapsed}" />

Sadly this doesn’t seem to be possible, partly due to a known bug with custom markup extensions that Microsoft are not going to fix, and partly because we can’t put dependency properties on a markup extension (so no bindings).

Making The Best Of It

If we can’t get to the ideal scenario then how close can we get? After trying out what felt like hundreds of different ways to work against WPF, it turns out that we can get pretty close without too much pain.

The implementation below will…

  • Work with a binding (for the source value, at least)
  • Work inline as a markup extension
  • Automatically convert from strings to other types using TypeConverter (e.g. "Visible" to Visibility.Visible)

For example:

<TextBlock Visibility="{Binding ErrorCount, 
	Converter={l:When IsEqualTo=0, Return=Visible, Else=Collapsed}" />

so how did we get there?

Working with Bindings

In order to work with Binding values, I’ve implemented this as an IValueConverter. This allows it to operate on the result of a binding, as well as getting change notifications, but unfortunately doesn’t mean that we can bind to the IsEqualTo, Return or Else properties. As they can’t do much, these properties are implemented as simple get/setters on the converter itself.

public class When : IValueConverter
{
	public object IsEqualTo { get; set; }
	public object Return { get; set; }
	public object Else { get; set; }
	
	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
		//...
	}
}

The binding result will be passed to the Convert method, so from there we can do a simple comparison to return the desired result.

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
	if (this.IsEqualTo.Equals(value))
		return this.Return;
	else
		return this.Else;
}

Note that we are using the Equals method for the comparison here – this is because the value for both IsEqualTo and the binding result are passed in as objects, and using == will incorrectly perform a reference comparison.

Making a Markup Extension

To create a markup extension (and allow inline declarations) we can extend the value converter from MarkupExtension

This abstract class only contains one method – ProvideValue – that needs to be overridden, and as we only ever want to return the converter itself we can just return this:

public class When : MarkupExtension IValueConverter
{
	public override object ProvideValue(IServiceProvider serviceProvider)
	{
		return this;
	}
}

Enabling Automatic Conversions

Thankfully, WPF takes care of some of the conversion process for us. Whatever type that an IValueConverter returns, the binding engine will always use type converters to attempt a conversion to the correct property type.

You can see this if you create a value converter that just returns the string "Red" and set it against a Brush property such as TextBlock.Foreground: WPF will automatically convert the string into a valid brush.

<TextBlock Foreground="{Binding 
	Converter={StaticResource JustReturnRedConverter}}" />

What this means for our markup extension is that there is no need for us to attempt to convert the Return or Else values – we get that for free.

The IsEqualTo property is a different matter, however. When we declare the converter inline, the type of any property value we set will default to String:

<TextBlock Foreground="{Binding ErrorCount,
	Converter={l:When IsEqualTo=0, Return=Black, Else=Red}}" />
	<!-- IsEqualTo will be set to the string "0" -->

Obviously this causes a problem when we try to compare it to the value for our binding – in this example, an integer – so we need to somehow convert that string to the appropriate type.

Type converters can help us out again here, but we need to locate and invoke them manually. We do this using TypeDescriptor.GetConverter:

private object GetValueForComparison(object value)
{
	var comparisonValue = this.IsEqualTo;
	var converter		= TypeDescriptor.GetConverter(value.GetType());

	try
	{
		if (converter.CanConvertFrom(this.IsEqualTo.GetType()))
			comparisonValue = converter.ConvertFrom(this.IsEqualTo);
	}
	catch (Exception) {} //ignore failures to convert

	return comparisonValue;
}

Here we are locating a converter for the type of the binding result (e.g. int) and then checking to see if the converter can convert from the type of the IsEqualTo property.
In the case of conversion exceptions we want to fall back to the original value of IsEqualTo.

Now we can plug this method in prior to comparing our binding result and finally use our comparison converter as intended!

Source

The final source code for this is available here. Let me know if you find it useful!

PostScript

This is written for use in WPF. I am aware that things are slightly different in Silverlight but I haven’t looked into it so if someone has a better SL implementation then stick it in the comments!

Advertisements

One thought on “Getting Rid of XAML Boilerplate

  1. dd spectra says:

    I’m always looking for ways to reduce XAML boilerplate. After every failure I realize that XAML is a hack (stuff gradually added on trying to plug up the holes in conception) even though it has some crucial features that imporove dramatically on previous layout technologies such as HTML or WinForms. Congratulations on getting this solution to work. I’ll try to benefit from it the next time I have a WPF project. Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s