Jeff Prosise's Blog

Using Custom Markup Extensions in Silverlight 5

The first beta of Silverlight 5 was announced at MIX this week and is available for downloading. As such, I’ll be blogging about the new features in weeks to come. I’ll also be delivering sessions on Silverlight 5 at several upcoming conferences, including Microsoft TechDays in Belgium, Microsoft DevDays in the Netherlands, Devscovery in Redmond, WA, and Microsoft TechEd in Atlanta. If you plan to attend any of those conferences, I’d love to see you in my sessions!

One of the most exciting features that Silverlight 5 introduces – and one that has been a long time in coming to the platform – is custom markup extensions. My fellow coconspirator and Silverlight MVP Jeremy Likness presented a custom markup extension that uses MEF to satisfy imports on object instances declared in XAML. I’d like to present a custom markup extension of my own – one that retrieves RESX localization resources and simplifies the task of adding localization support to Silverlight applications.

In the past, RESX-based localization was usually performed in Silverlight XAML with the help of the built-in {Binding} markup extension. To demonstrate, the following example declares an instance of the ResourceManager wrapper class named Resources (which is generated by Visual Studio from Resources.resx), assigns the Resources instance to the DataContext property of a TextBlock, and uses a data-binding expression to set the TextBlock’s Text property equal to the Greeting property of the Resources instance:

 

<Grid>

  <Grid.Resources>

    <local:Resources x:Key="Localize" />

  </Grid.Resources>

  <TextBlock Text="{Binding Greeting}" DataContext="{StaticResource Localize}" />

</Grid>

 

It works, but it makes you wonder why you have to resort to data binding to make localization work when localization is such a common task in Silverlight applications.

You can make this work a little more cleanly by writing a custom markup extension. Such an extension might be applied this way:

 

<Grid>

  <TextBlock

    Text="{local:Resx ResxKey=Greeting, ResxType=Resources, Default=Welcome}" />

</Grid>

 

In this example, Resx is the custom markup extension, ResxKey identifies the localization resource to be loaded, ResxType identifies the ResourceManager wrapper class that provides access to that resource, and Default is an optional default value that’s used if the specified localization resource doesn’t exist or can’t be retrieved. Better, is it not? And it’s just one of a million different applications for custom markup extensions.

Implementing a custom markup extension is, in most cases, relatively straightforward. You begin by deriving from Silverlight 5’s new System.Windows.Markup.MarkupExtension class. Then you override ProvideValue in the derived class and return the value generated by the markup extension. My ResxExtension class is implemented this way:

 

public class ResxExtension : MarkupExtension

{

    public string ResxType { get; set; }

    public string ResxKey { get; set; }

    public object Default { get; set; }

 

    public override object ProvideValue(IServiceProvider serviceProvider)

    {

        if (!String.IsNullOrEmpty(ResxType) && !String.IsNullOrEmpty(ResxKey))

        {

            try

            {

                // Create a strongly typed resource manager instance

                object resman = Activator.CreateInstance(Type.GetType(ResxType));

 

                // Get the value of the specified property

                PropertyInfo pi = resman.GetType().GetProperty(ResxKey);

                return pi.GetValue(resman, null);

            }

            catch (Exception)

            {

                // Purposely do nothing here to allow the call to fall through

            }

        }

 

        // If we make it to here, return the default value (if specified) or,

        // as a last resort, the key name

        if (Default != null)

            return Default;

        else

            return ResxKey;

    }

}

 

The three public properties – ResxType, ResxKey, and Default – define the named parameters that the markup extension accepts. The XAML parser automatically initializes these properties with the values provided in the markup. My ProvideValue override uses reflection to create an instance of the ResourceManager wrapper class identified by ResxType, and then uses reflection again to retrieve the value of the property whose name is stored in the markup extension’s ResxKey property. If anything goes wrong, ProvideValue returns the default value specified with the Default parameter, or the value of ResxKey if there is no Default parameter.

If you’d like to see ResxExtension in action, you can download a zip file containing the Visual Studio solution. When you run the app for the first time, you’ll see this:

ResxExtension1

But if you open App.xaml.cs and uncomment line of code that sets the culture to French, you’ll see this instead:

ResxExtension2

The welcome text, the URI of the flag image, and the width of the flag image come from RESX files named Resources.resx, Resources.fr.resx, Resources.es.resx, and Resources.de.resx. Open these RESX files in Visual Studio and you’ll see exactly how the individual resources are defined. As for applying the localization resources, that happens in MainPage.xaml:

 

<TextBlock Text="{local:Resx ResxKey=Greeting,

  ResxType=CustomMarkupExtensionDemo.Localization.Resources, Default='Nice Try!'}"

  Foreground="LightYellow" FontSize="72" FontWeight="Bold"

  HorizontalAlignment="Center" VerticalAlignment="Center">

  <TextBlock.Effect>

    <DropShadowEffect BlurRadius="12" ShadowDepth="12" Opacity="0.5" />

  </TextBlock.Effect>

</TextBlock>

<Image Source="{local:Resx ResxKey=FlagUri,

  ResxType=CustomMarkupExtensionDemo.Localization.Resources}"

  Width="{local:Resx ResxKey=FlagWidth,

  ResxType=CustomMarkupExtensionDemo.Localization.Resources}" />

 

Examine the source and see what kinds of cool custom markup extensions you can come up with. And don’t forget that you’ll need to download and install the Silverlight 5 Beta to run the example.

On Apr 15 2011 12:54 PMBy jprosise With 13 Comments

Comments (13)

  1. Jeff great sample, just something to enhance, Visual Studio Design Time + Blendability.

    In Visual Studio Design mode nothing is shown (nor text and flag).

    In Blend the flag is missing.

  2. A bit old now but, is there anyway to trigger the MarkupExtensions to re-evaluate, it looks like they only evaluate once at initial startup. So in your above example, if dynamically the properties were update to provide new locale settings (like the user picked a new language from a drop down) is it possible to trigger the UI to re-evaluate and thus get the new language values.

  3. There is one thing missing here and that's not at all obvious from the code: the project must be edited to specify the supported cultures so that the satellite assemblies get built. I realize this has nothing to do with custom markup extensions but since the topic is applied localization (and I spent an hour figuring it out), I thought it worth mentioning!

Leave a Comment