April 19, 2009

XAML markup extension with automatic type conversion

I recently had the following problem: I am using a custom XAML markup extension in my WPF application. The markup extension returns strings. While I normally use it for assigning values to string properties I this time wanted to apply it to a different type of property.

The following code illustrates what I wanted to do:


<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:XamlExtAutoConvert="clr-namespace:XamlExtAutoConvert"
x:Class="XamlExtAutoConvert.TestWindow"
Title="TestWindow"
Height="300"
Width="300"
>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">

<Button Width="{XamlExtAutoConvert:Test}" Content="{XamlExtAutoConvert:Test}" />

</Grid>
</Window>


This is of course a pretty stupid example but I hope it gets across what I want to do. Let's assume that the markup extension XamlExtAutoConvert:Test returns the string "200". This string should end up as the button content and at the same time define the width of the button.

As in XML the width of the button is specified as a string too (after all XML is nothing more than strings) and converted to double (which is the type of the Width property) I was hoping the same was happening with the value returned from the markup extension.

I was wrong.

You will get the following exception:


System.Windows.Markup.XamlParseException occurred
Message="'200' value cannot be assigned to property 'Width' of object
'System.Windows.Controls.Button'.
'200' is not a valid value for property 'Width'.


Fortunately there's a way to rewrite the markup extension to cover this use case. You can determine the type of the target property. And with that you can try to convert the value to that type.

A sample markup extension looks like this:


using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;

namespace XamlExtAutoConvert
{
class TestExtension: MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = (IProvideValueTarget)serviceProvider.GetService(
typeof(IProvideValueTarget));
if (target != null)
{
// get the actual value to return
string value = GetValue();

// determine the type we need to convert to
object convertedValue = null;
Type targetType = null;
if (target.TargetProperty is DependencyProperty)
{
var property = target.TargetProperty as DependencyProperty;
targetType = property.PropertyType;
}
else
{
// if needed we could also support other types
}

// check if conversion is needed
if (targetType != null &&
targetType != typeof(string) &&
targetType != typeof(object))
{
// try a conversion
try
{
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
object retValue = converter.ConvertFromString(value);
convertedValue = retValue;
}
catch (NotSupportedException)
{
//fall through
}
}

// if we have a converted value, return it
if (convertedValue != null)
return convertedValue;

// else just return the string value
return value;
}
return null;
}

private string GetValue()
{
// replace with whatever the extension is supposed to do
return "200";
}
}
}
blog comments powered by Disqus