Archive

Posts Tagged ‘XAML only’

Commanding: Binding Controls to Methods

May 7, 2012 Leave a comment

A Command in WPF encapsulates a method to do something, a check on whether it is possible to do it at the moment , and a notification of when that state changes. It does this in a way so that one can bind a control like a Button or MenuItem to a command, and the control will execute the command when clicked and enable and disable itself according to whether the command is available.

WPF supports this infrastructure through two APIs: CommandBindings, which can be defined in XAML, but can only call methods defined in the XAML’s code-behind file, and the ICommand-Interface, which can be implemented in ViewModel classes, and supports creating properties in the ViewModel which allow to bind a control to a command.

For the first approach, I don’t like that I have to write code in the code-behind file. Defining a command is a routine thing, so I’d like to do it in XAML.

For the second approach, I don’t like that I have to implement an interface, and create kind of standard boilerplate glue-code in order to be able to make the UI call a method in the model. If possible, I want to do all the gluing that is necessary in XAML only.

What would an ideal solution look like? I’d like to have a way to just take any normal method in the ViewModel, bind a command to it (that would be a RoutedCommand object, like with the CommandBinding approach), and assign the RoutedCommand to any Control that supports commands.

In the post about the Sticky Command Design Pattern, I have already demonstrated how one can set up CommandBindings through attached properties. Now, I want to generalize this approach into a commanding framework.

A Reusable Sticky Command

The first element is a reusable component which ties a RoutedCommand and its implementation together. Basically, it contains the same information as a CommandBinding, and in fact it is just a wrapper and adapter which is responsible for creating CommandBindings while overcoming their limitations.

So, on the one hand, the Command class defines the usual Attach and Detach methods for Sticky Components:

Protected Overrides Sub Attach(ByVal sender As Object)
    If SupportsCanExecute Or UseValidation Then
        StickyComponentManager.Attach(sender, Command, AddressOf OnExecuted, AddressOf OnCanExecute, Preview)
    Else
        StickyComponentManager.Attach(sender, Command, AddressOf OnExecuted, Nothing, Preview)
    End If
End Sub

Protected Overrides Sub Detach(ByVal sender As Object)
    StickyComponentManager.Detach(sender, Command)
End Sub

 

Command here is a property which holds a RoutedCommand and which can be configured through XAML. UseValidation and Preview are also properties which can be configured through XAML.

On the other hand, the Command class defines standard implementations for the CommandBinding’s Executed and CanExecute delegates:

Public Overridable Sub Execute(ByVal parameter As Object)

End Sub

Protected Overridable ReadOnly Property SupportsCanExecute() As Boolean
    Get
        Return False
    End Get
End Property

Public Overridable Function CanExecute(ByVal sender As Object, ByVal parameter As Object) As Boolean
    Return CanExecute(parameter)
End Function

Public Overridable Function CanExecute(ByVal parameter) As Boolean
    Return True
End Function

Private Sub OnExecuted(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
    Exception = Nothing
    Status = WorkingMessage
    Dim a As Func(Of Object, Object, Exception) = AddressOf Me.DoExecute
    Dim op As System.Windows.Threading.DispatcherOperation = Me.Dispatcher.BeginInvoke(a, Windows.Threading.DispatcherPriority.Background, sender, e.Parameter)
    op.Wait()
    Dim success As Boolean = (op.Result Is Nothing)
    If success Then
        Status = ReadyMessage
    Else
        Dim ex As Exception = op.Result
        Status = ex.Message
        Exception = ex
    End If
End Sub

Private Function DoExecute(ByVal sender As Object, ByVal parameter As Object) As Exception
    Dim result As Exception = Nothing
    Try
        Execute(sender, parameter)
    Catch ex As Exception
        result = ex
    End Try
    Return result
End Function

Private Sub OnCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
    Try
        e.CanExecute = True
        If SupportsCanExecute Then
            e.CanExecute = CanExecute(sender, e.Parameter)
        End If
        If e.CanExecute And UseValidation Then
            e.CanExecute = Not System.Windows.Controls.Validation.GetHasError(sender)
        End If
        e.Handled = Handled
    Catch
        e.Handled = False
    End Try
End Sub

 

This code contains some details which I am going to cover in later posts. The important thing for now is: there are standard event handlers which can be attached to a CommandBinding, and their real work is done in the overridable Execute and CanExecute methods. These methods are supposed to be implemented by derived classes.

Binding to Methods

So far, we have an object which can create a CommandBinding by attaching itself to a Control. The next step is to bind a method of our ViewModel to this object.

For this purpose, I created a subclass of Command, ActionCommand, which defines a property of type Action, i.e. a Delegate for a method that doesn’t take parameters and doesn’t return a result.

Public Shared ReadOnly ExecutedActionProperty As DependencyProperty = DependencyProperty.Register("ExecutedAction", GetType(Action), GetType(ActionCommand))
Public Property ExecutedAction As Action
    Get
        Return GetValue(ExecutedActionProperty)
    End Get
    Set(ByVal value As Action)
        SetValue(ExecutedActionProperty, value)
    End Set
End Property

Public Overrides Sub Execute(ByVal sender As Object, ByVal parameter As Object)
    If ExecutedAction IsNot Nothing Then
        ExecutedAction.Invoke()
    End If
End Sub

 

The class overrides the Execute method with an implementation that invokes the method the ExecutedAction delegate is bound to.

In order to be able to bind a value to the ExecutedAction delegate, I created the property as a DependencyProperty. Also, I derived the base class for my Command from Freezable. Freezables are special objects that are optimized for use as resources. What makes them extremely useful in our case is that they inherit the DataContext of the FrameworkElement in whose Resources section they are defined. The DataContext will be our ViewModel object. So, the only thing lacking is a way to create a Delegate for one of its methods through a Binding.

The solution here is a converter. The special converter we use in this case will accept the name of the method as ConverterParameter. Then it will inspect the type of the bound property and the type of the bound object in order to create a Delegate with a matching signature.  This is its code:

Public Class ActionConverter
    Implements IValueConverter

    Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
        If value Is Nothing OrElse parameter Is Nothing OrElse Not TypeOf parameter Is String OrElse Not GetType([Delegate]).IsAssignableFrom(targetType) Then
            Return Nothing
        Else
            Return WPFGlue.Framework.StickyComponentManager.CreateActionDelegate(value, parameter, targetType)
        End If
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
        Throw New NotSupportedException()
    End Function
End Class

 

And the method which creates the Delegate:

Public Shared Function CreateActionDelegate(ByVal target As Object, ByVal methodName As String, ByVal actionType As Type) As [Delegate]
    Dim result As [Delegate] = Nothing

    If target IsNot Nothing AndAlso Not String.IsNullOrEmpty(methodName) AndAlso GetType([Delegate]).IsAssignableFrom(actionType) Then
        Dim targetType As System.Type = target.GetType
        Dim methodInfo As System.Reflection.MethodInfo
        methodInfo = targetType.GetMethod(methodName, actionType.GetGenericArguments)
        If methodInfo IsNot Nothing Then
            result = [Delegate].CreateDelegate(actionType, target, methodInfo, False)
        End If
    End If

    Return result

End Function

 

Gluing it Together in XAML

While this looks quite complex, the good news is that one needs to create this kind of class only once, and from then on can use it to bind to method in XAML only. This is how one would define a Command which binds to a method in the ViewModel:

<c:ActionCommand Command="Open" ExecutedAction="{Binding Converter={StaticResource actionConverter}, ConverterParameter=Open}"/>

 

In this case, the we use the predefined Open RoutedCommand, and bind it to a method called Open (as specified by the ConverterParameter in the Binding) on the current DataContext. Instead of the predefined Open command, we could use any RoutedCommand, even those defined in our own application.

We connect the command to a Control using a Sticky Property which the commanding framework defines:

<StackPanel c:Commanding.CommandSet="{StaticResource commands}">
    <Button Command="Open">Open</Button>

 

As you can see, the Button which triggers the command is completely unaware of its implementation: it just knows that it should work with the Open predefined command, and the rest works through the WPF RoutedCommand infrastructure. This means that you can also take advantage of command routing, like having a MenuItem triggering the same command on different controls depending on which control has keyboard focus. This is more than one gets using the traditional MVVM RelayCommand approach.

Conclusion

Even though it is a bit of work, it is possible to create reusable components which allow it to bind a RoutedCommand to a method of a ViewModel using XAML only. With this technique, it is possible to take advantage of WPF command routing, while at the same time the amount of glue code in the ViewModel is reduced.

You can find the full code and an example project, CommandingExample, in the WPFGluePublished download through the Downloads page.

In posts to come I will cover more details of the WPFGlue Commanding framework, like executing commands on background threads, displaying status and exception messages, or defining CommandSets in order to attach several commands in one line of XAML.

Advertisements

Navigating from Object to Object

December 8, 2009 1 comment

WPF offers a whole application paradigm based on navigation: The Navigation Application, hosted in a NavigationWindow, Frame or WebBrowser; consisting of XAML pages which are independent of each other; keeping track of the navigation history and restoring pages and their values while going forwards and backwards are supported out of the box.

However, some things are lacking. Suppose you have a data object model. In your application, you want to be able to select an object from a list, then click on a link to open a new page and edit the details of the object. When you are done, you want to select another object, and  when you have repeated this a couple of times, you want to be able to go backwards using the back button, returning to the objects you edited on each page.

What is the problem here? WPF offers no built-in way to pass an object from one page to the other using XAML only. This has consequences. If you use the approach suggested in the WPF documentation, you will have to instantiate a new page object, pass the object you want to edit to its constructor, and navigate to it using the NavigationService.Navigate(Object) overload. However, pages which are navigated to this way are not disposed when they are no longer displayed, but retained in memory as a whole, and this has some side effects which I already talked about in this post.

So, what can we do about it?

The GoToPage Sticky Command

A sticky command is a command binding that is attachable to any XAML element and adds a certain functionality to this element without changing its code.

The GoToPage navigation command was one of my first experiments with the WPFGlue programming style. Since then, I have learned a few things and changed it considerably. One of the changes being that now the command which is the navigation command is no longer hardcoded, but could be any routed command which is configured to use the Navigation command implementation. This is how you use it:

<NavigationWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:n="https://wpfglue.wordpress.com/navigation&quot;       
    Title="NavigationExample" Height="300" Width="300"
    n:Navigation.Command="GoToPage" Source="ListPage.xaml"/>

 

By setting the Navigation.Command attached property on the NavigationWindow to the WPF GoToPage command, you enable all pages that the NavigationWindow hosts to use this command in order to navigate to other pages and pass along objects as DataContext for the new page.

In the page, you would use the command like this:

<Button x:Name="EditButton"
   DataContext="{Binding ElementName=ItemListView, Path=SelectedItem}"
   Command="GoToPage" CommandParameter="{Binding}"
   n:Navigation.Uri="DetailPage.xaml">

This button uses the selected item of a ListView as its DataContext. If clicked, it invokes the GoToPage command (set to its Command property) The selected item is bound to the CommandParameter property, which means that it will be passed as parameter to the GoToPage command. Finally, the URL of the page that should be navigated to is configured through the Navigation.Uri attached property.

How Does It Work?

The Navigation.Command property is a sticky property: in its change handler, it attaches a CommandBinding with the code that calls the Navigation command to the element it is set on:

Public Shared ReadOnly CommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("Command", GetType(RoutedCommand), GetType(Navigation), New PropertyMetadata(AddressOf OnCommandChanged))
Public Shared Function GetCommand(ByVal d As DependencyObject) As RoutedCommand
    Return d.GetValue(CommandProperty)
End Function
Public Shared Sub SetCommand(ByVal d As DependencyObject, ByVal value As RoutedCommand)
    d.SetValue(CommandProperty, value)
End Sub
Private Shared Sub OnCommandChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    If e.OldValue IsNot Nothing Then
        Detach(d, e.OldValue)
    End If
    If e.NewValue IsNot Nothing Then
        Attach(d, e.NewValue)
    End If
End Sub
Private Shared Sub OnUnloaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
    SetCommand(sender, Nothing)
End Sub

This code follows a pattern that is typical for Sticky Components; I want to introduce them in more detail later, but we are going to encounter their patterns continuously here, so I want to point it out:

A Sticky Component needs to be able to clean up after itself. So, there are two procedures, Attach and Detach. The Attach method connects the sticky component by setting up CommandBindings or event handlers, the Detach method removes all these references between the Sticky Component and its hosting element. So, the Detach procedure needs to be called in two cases: either if the Sticky Component is removed from the element, or if the element is unloaded. So, the common pattern for Sticky Components is to call Detach when a Sticky Component is replaced on an element, and to reset the attached property that controls the Sticky Component in the element’s Unloaded event.

Navigation Flow Control

This is the method that finally gets called when the GoToPage command is invoked:

Public Shared Function Navigate(ByVal sender As Object, ByVal target As Uri, ByVal data As Object) As Boolean
    Dim result As Boolean = False
    Dim service As NavigationService = GetNavigationService(sender)
    If service IsNot Nothing Then
        AddHandler service.LoadCompleted, AddressOf SetDataContextHandler
        result = service.Navigate(target, data)
    End If
    Return result
End Function

It uses the NavigationService.Navigate(Uri,Object) overloaded method, which allows to pass additional data into the navigation process. This additional data is the object that the user selected, and that was passed to the GoToPage command as parameter. The NavigationService will hold on to this object while the pages are changed. When the new page is loaded, we want to set the pages DataContext to the object, so we register a handler for the NavigationService’s LoadCompleted event.

The handler looks like this:

Private Shared Sub SetDataContextHandler(ByVal sender As Object, ByVal e As System.Windows.Navigation.NavigationEventArgs)
    Dim service As NavigationService = GetNavigationService(e.Navigator)
    If service IsNot Nothing Then
        RemoveHandler service.LoadCompleted, AddressOf SetDataContextHandler
        Dim data As Object = e.ExtraData
        If data IsNot Nothing Then
            If TypeOf e.Content Is DependencyObject Then
                SetDataContext(e.Content, data)
                AddHandler service.Navigating, AddressOf SaveDataContextHandler
            End If
        End If
    End If
End Sub

 

Notice that the first thing this procedure does is to unregister itself from the LoadCompleted event. This is because there is no guarantee that all navigation in the application will use our command. This handler makes sense only if the ExtraData of the NavigationEventArgs really contains an object which should be set to the DataContext of the new page. Thus, we register it specifically for this case, and unregister it immediately after use. I call this design pattern a “One Shot Event”.

Then, we set the DataContext of the new page. By the time the event occurs, this page can be found in the Content property of the NavigationEventArgs. However, we cannot set its DataContext directly: WPF is quite particular about when exactly during the lifetime of a page the DataContext is set; some validation features will not work properly if it is set too early, so we define a special attached behaviour that waits until the new page’s Loaded event occurs and then sets the DataContext:

Public Shared ReadOnly DataContextProperty As DependencyProperty = _
    DependencyProperty.RegisterAttached("DataContext", GetType(Object), _
    GetType(Navigation), New PropertyMetadata(Nothing, AddressOf OnDataContextChanged))
Public Shared Function GetDataContext(ByVal d As DependencyObject) As Object
    Return d.GetValue(DataContextProperty)
End Function
Public Shared Sub SetDataContext(ByVal d As DependencyObject, ByVal value As Object)
    d.SetValue(DataContextProperty, value)
End Sub
Private Shared Sub OnDataContextChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    If e.NewValue IsNot Nothing Then
        If StickyComponentManager.GetIsLoaded(d) Then
            d.Dispatcher.Invoke(System.Delegate.CreateDelegate(GetType(Navigation), Nothing, "OnDataContextLoaded"), d, Nothing)
        Else
            StickyComponentManager.AttachEvent(d, FrameworkElement.LoadedEvent, AddressOf OnDataContextLoaded)
        End If
    End If
End Sub
Private Shared Sub OnDataContextLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim d As DependencyObject = TryCast(sender, DependencyObject)
    If d IsNot Nothing Then
        StickyComponentManager.DetachEvent(d, FrameworkElement.LoadedEvent, AddressOf OnDataContextLoaded)
        Dim data As Object = GetDataContext(d)
        d.SetValue(FrameworkElement.DataContextProperty, data)
        d.ClearValue(DataContextProperty)
    End If
End Sub

Notice that the handler for the Loaded event not only removes itself from the page, but also resets the attached property that held on to the DataContext so as to leave no references to this object in places where they are not expected.

We want to be able to return to the page and still see the same object as DataContext. So, we need to save the DataContext to the Journal when the page is about to be left. This is where it gets tricky (again). The method to add custom information to the Journal is to implement a class that inherits from CustomContentState and set it to the CustomContentState property of the NavigatingCancelEventArgs that are passed into the NavigationService.Navigating event when the user tries to navigate away from the current page. However, CustomContentState objects are not stored as object references, but in serialized format, being deserialized as they are needed. This means that our DataContext would be serialized as well, making it impossible to return to the same instance. In order to work around this, we save the DataContext to a shared Session object in the background, and retain only an integer index, which can be serialized easily, while allowing us to retrieve the object later:

Private Shared Sub SaveDataContextHandler(ByVal sender As Object, ByVal e As System.Windows.Navigation.NavigatingCancelEventArgs)
    Dim service As NavigationService = GetNavigationService(e.Navigator)
    If service IsNot Nothing Then
        RemoveHandler service.Navigating, AddressOf SaveDataContextHandler
        If Not e.Cancel AndAlso TypeOf service.Content Is DependencyObject Then
            Dim content As DependencyObject = service.Content
            Dim data As Object = content.GetValue(FrameworkElement.DataContextProperty)
            If data IsNot Nothing Then
                Dim id As Integer = GetDataContextId(content)
                id = Session.StoreReference(id, data)
                Dim state As NavigationContentState = New NavigationContentState(id, e.ContentStateToSave)
                e.ContentStateToSave = state
            End If
        End If
    End If
End Sub

 

Since we don’t want to keep the DataContext object alive longer than its object model supposes it is alive, the Session object uses WeakReferences for storing the DataContext.

When the user navigates back to the page, the CustomContentState’s Replay method is called. In this method, the DataContext is retrieved from the Session object and put into the new page’s DataContext property, using the same method as before. Since we cannot use a CustomContentState more than once, we also have to set up the handling of the Navigating event again, so that the DataContext is saved again when the page is left.

Public Overrides Sub Replay(ByVal navigationService As System.Windows.Navigation.NavigationService, ByVal mode As System.Windows.Navigation.NavigationMode)
    If _OriginalState IsNot Nothing Then
        _OriginalState.Replay(navigationService, mode)
    End If
    If _DataContextId <> -1 Then
        Dim content As DependencyObject = TryCast(navigationService.Content, DependencyObject)
        If content IsNot Nothing Then
            Dim data As Object = Session.RetrieveReference(_DataContextId)
            If data Is Nothing Then
                SetIsExpired(content, True)
                WPFGlue.Validation.Validation.SetSuppressErrorTemplate(content, True)
            Else
                SetDataContext(content, data)
                SetDataContextId(content, _DataContextId)
                AddHandler navigationService.Navigating, AddressOf SaveDataContextHandler
            End If
        End If
    End If
End Sub

 

Handling Expired Content

But what if the object that was the DataContext has be freed since the user last visited the page?

Handling this case gracefully almost drove me crazy. What I wanted to do was to navigate backwards one step and to clear the forward navigation stack so that the user couldn’t navigate to the page again. But I found no way of doing this that would work with all cases I wanted to cover: the API of the Journal is just too narrow. So, what I ended up doing was simply disabling the page and displaying a big fat Adorner on top of it, telling the user to go away and not come back… The Adorner can be styled using a ControlTemplate, a little bit like the Validation.ErrorTemplate, so you can make it less obnoxious; if anyone can tell me how to achieve what I originally wanted to do, I’ll be forever grateful.

Disabling the GoToPage Command

In the example, it makes no sense to try to go to the details page if no object is selected in the list. In cases like this, it is possible to disable the GoToPage command by setting the Navigation.CanNavigate property on the element that invokes the command. This can be done through a trigger, like in the example:

<Style TargetType="Button">
    <Style.Triggers>
        <Trigger Property="DataContext" Value="{x:Null}">
            <Setter Property="n:Navigation.CanNavigate" Value="False"/>
        </Trigger>
    </Style.Triggers>
</Style>

Or it could be done by binding this property to a property in a ViewModel.

Blocking Navigation Completely

Sometimes, it might be necessary to block navigation completely. On the details page, there is a ValidationRule that demands that the Name property contains a value. By binding the Navigation.BlockNavigation property to Validation.HasError on the Page element, one can disable all navigation away from the page until this property is filled. In order to support this behaviour, the following event handler is attached to the NavigationService.Navigating event:

Private Shared Sub BlockNavigationHandler(ByVal sender As Object, ByVal e As System.Windows.Navigation.NavigatingCancelEventArgs)
    Dim service As NavigationService = GetNavigationService(sender)
    If service IsNot Nothing Then
        If service.Content IsNot Nothing Then
            e.Cancel = GetBlockNavigation(service.Content)
            'Allow Fragment navigation
            If e.Cancel And Uri.Compare(service.CurrentSource, service.Source, UriComponents.PathAndQuery, UriFormat.UriEscaped, StringComparison.InvariantCulture) = 0 Then
                e.Cancel = False
            End If
        End If
    End If
End Sub

 

Testing the Component

You will find the example application NavigationExample in the WPFGluePublished solution on the Downloads page. In this example, each page contains a “GC” button which forces the .Net garbage collection. By running the example in the debugger and following the debug output, you can see the lifetime events of the pages, and by forcing the garbage collection you can verify that the page objects get finalized when they are no longer needed, thus proving that the Navigation.Command didn’t leave behind any dangling references and doesn’t cause any memory leaks.

By adding some customers, editing their details, removing them, and going back using the NavigationWindow’s Back button, you can test the behaviour for pages whose DataContext has expired.

Conclusion

This example shows how it is possible to pass objects from page to page using XAML only, and how one can handle forwards and backwards navigation using object references. However, there are still some limitations: I don’t really like displaying expired pages, and since the Session object uses weak references, it might not be suitable for partial trust scenarios where there is no permission to execute unmanaged code.

In posts to come, I want to explore the possibilities of defining a navigation topology as a central resource. However, for this I will need the complete Sticky Component Framework, about which I will write as soon as possible…

Using .Net Localized String Resources in WPF

December 5, 2009 1 comment

When localizing an application, the most obvious feature is that labels, texts and pictures in the UI are displayed in another language. This is done through localized string resources, and .Net has a whole framework for handling these. In Windows Forms, this framework is quite well supported by Visual Studio. However, in WPF this support is considerably lacking. This is about how you can use them anyway.

Creating Localized Resources

You can edit the localized resources of your application if you choose the Resources tab in the My Project editor. However, these are the resources for the neutral or invariant culture, the .Net culture description that is not connected to any natural language. These resources are embedded into the executable of your application. If you want culture specific resources which are compiled into .Net satellite DLLs which you can distribute with your application in order to let it support multiple languages, you have to create them manually. Unfortunately, the documentation on this is not very clear. In my tests, I did the following:

  • Close Visual Studio
  • Go to the My Project / MyExtensions folder in the project directory
  • Copy the file Resources.resx and rename the copy to the correct name for the culture, e.g. Resources.de-DE.resx for German or Resources.en-US.resx for US English
  • Open the Project again in Visual Studio
  • Select Add Existing Item and add the file to the project.
    After you have done that, the Resource designer will display if you open the file from Solution Explorer. Just make sure that the Access Modifier combo box on top says “No Code Generation”, otherwise you might get error messages about conflicting class names on compilation.

Since you copied the original resource file, all resources will already be there in the original language; so, if you do this towards the end of the project, you will already have the correct resource keys and their original translations to help you…

The Visual Studio Resource designer was originally intended for Windows Forms. As a result, the image resources it produces (at least at the time of writing, Visual Studio 2008 SP1) are tied to the Windows Forms image types, which are not working with WPF. For this reason, I decided to not support image resources. Instead, one can create bitmap images from URIs which are stored as resource strings, so that they can point to different sources for different languages. These URIs should be pack URIs if you provide the images with your program; pack URIs are explained very nicely here.

Using Localized Resources in Your WPF Pages

WPFGlue offers four different MarkupExtensions for integrating localized resources in XAML. Their names are StringExtension, URIExtension, BitmapImageExtension, and FlowDirectionExtension. StringExtension is the base class. Its ProvideValue method, which is called whenever the XAML that contains the extension is instantiated, looks like this:

Public Overrides Function ProvideValue(ByVal serviceProvider As System.IServiceProvider) As Object
    Dim result As Object = "[" & Key & "]"
    If _ResourceManager Is Nothing Then
        Try
            If Assembly IsNot Nothing AndAlso Not Assembly.FullName.StartsWith("PresentationFramework,") Then
                _ResourceManager = New System.Resources.ResourceManager(BaseName, Assembly)
            End If
        Catch x As Exception
            result = Assembly.FullName
        Finally
        End Try
    End If
    If _ResourceManager IsNot Nothing Then
        Try
            result = _ResourceManager.GetString(Key)
        Finally
        End Try
        If result Is Nothing Then
            result = "[" & Key & "]"
        End If
    End If
    Return result

End Function

It reads strings from the application’s resources; URIExtension converts them into URIs on top of that, and BitmapImageExtension creates a BitmapImage from the URI it looked up in the resource. FlowDirectionExtension checks whether the current UI language is written from right to left, and returns the correct FlowDirection value. All these markup extensions behave like StaticResource. This means that they return their value at the time the XAML is instantiated, and don’t create a binding to the properties they are applied to. It also means that you can use them everywhere in your XAML, not only with DependencyProperties.

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" >
    <TextBlock Text="{l:String TestString}" />
    <TextBlock Text="{Binding Source={StaticResource Value}}"/>
    <TextBlock Text="{l:Binding Source={StaticResource Value}}"/>
    <Image Stretch="None" Source="{l:BitmapImage FlagURI}"/>
</StackPanel>

So far so good. Using these extensions, you make sure that the users see the application in their system’s language, if you provided the correct resources. But what if you want to change the language at runtime?

This is where it gets sticky. The UILanguage Sticky Property is used to read and set the current UI language, which is the same language the resource manager uses in order to find the resources it wants to display. However, changing this property will not automatically change all the labels. I didn’t think this necessary; in this solution, changes of the UI language will take effect only after you navigate to a different page.

Public Shared ReadOnly UILanguageProperty As DependencyProperty = DependencyProperty.RegisterAttached("UILanguage", GetType(String), GetType(Localization), New PropertyMetadata(Nothing, AddressOf OnUILanguageChanged, AddressOf CoerceUILanguage))
Public Shared Function GetUILanguage(ByVal d As DependencyObject) As String
    Return d.GetValue(UILanguageProperty)
End Function
Public Shared Sub SetUILanguage(ByVal d As DependencyObject, ByVal value As String)
    d.SetValue(UILanguageProperty, value)
End Sub
Private Shared Sub OnUILanguageChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    If e.NewValue IsNot Nothing Then
        System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(CStr(e.NewValue))
    End If
End Sub
Private Shared Function CoerceUILanguage(ByVal d As DependencyObject, ByVal baseValue As Object) As Object
    Dim result As String = baseValue
    If baseValue Is Nothing Then
        result = System.Globalization.CultureInfo.CurrentUICulture.IetfLanguageTag
    End If
    Return result
End Function

Notice that setting this property to {x:Null} explicitly will coerce its value to the current UI language. You can use this to initialize a language selection box to the correct start value.

Setting UILanguage on any element in XAML will change the UI language for the whole application. However, if you want a common and consistent access point for this, consider setting it on the topmost XAML element in your application, i.e. the main Window or NavigationWindow.

Using Resources from Different Assemblies

You can use l:String etc. just out of the box, without having to tell the system where it would find the resources. This is possible because the MarkupExtension uses the type of the current Application object in order to find the assembly of the main program.

In the designer, obviously the main program is Visual Studio, so the markup extension doesn’t know where to look. If it isn’t able to discover the ResourceManager it needs to provide the string it is looking for, or if the key it receives does not have a corresponding resource, it just returns the value of the key itself enclosed in brackets.

However, there is a way to tell it where to look. This has to be there in order to be able to read resources from assemblies other than the main program’s assembly. For example, you could have put all your resources into a special resource assembly, or you could use a library which brings along its own resources.

The most convenient way to make a different assembly the resource lookup source is to assign a type from that assembly to the ResourceReferenceType sticky property. This assumes that the basename for resources in this type’s assembly is “Resources”, as is the default with VB.Net. You can also have finer control setting the ResourceAssembly and ResourceBasename properties, respectively. (n.b. I’m not sure whether the automatic detection of the assembly and basename works the same way in VB and C#).

These settings influence the whole application. This means that as long as they point to a certain place, all resource strings in the application are taken from there. That looks like quite a serious limitation; however, if using a Navigation application, every page would be independent and could easily have its own setting, and if you’d divide your page into regions using containing elements, the behaviour would be consistent as long as every containing element and every data template sets the source for its localized resources explicitly. This is so because the markup extensions access the resource DLL only while the XAML is instantiated, and the set properties retain their values even if the resource assembly changes later on (*).

If you want to preview your resource strings in the designer, you can just set the ResourceReferenceType and UILanguage on the window you are working on to your program’s application class and language, respectively.

<Page x:Class="LanguageSelectionPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:l="https://wpfglue.wordpress.com/localization&quot;
    xmlns:ex="clr-namespace:LocalizedResourcesExample"
    xmlns:r="clr-namespace:LocalizedResourceAssembly;assembly=LocalizedResourceAssembly"
    Title="LanguageSelectionPage" FlowDirection="{l:FlowDirection}"
      l:Localization.ResourceReferenceType="{x:Type ex:Application}"
      l:Localization.UILanguage="de-DE">

     However, if you want to be able to change the resource assembly and language at runtime, don’t forget to remove these attributes before you build the application.

The LocalizedResourceExample

The example solution you can download from the Downloads page demonstrates how to select the UI language at runtime:

<ComboBox SelectedValue="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
    AncestorType={x:Type NavigationWindow}}, Path=(l:Localization.UILanguage)}"
    ItemsSource="{StaticResource languageList}" DisplayMemberPath="Key"
    SelectedValuePath="Value"/>
</StackPanel>

 

Notice that the ComboBox sets the value of the UILanguage attached property directly on the NavigationWindow that hosts the page. However, you have to navigate away from the page in order to see the changes applied. If you go back to the language selection page, you will see that this page also re-reads its resources and displays differently dependent on what is selected in the language selection box.

The second combobox will select the assembly LocalizedResourceAssembly in order to access its resources. Apart from the resources themselves, this assembly contains only an empty class called “Dummy” which serves as reference class to find the resources. Since the LocalizedResourceAssembly doesn’t contain all the resources defined in the main assembly, you can watch the fallback behaviour for the resource lookup: If the resource isn’t there in the specified language, it is taken from the main culture of the application (which is de-DE German in the example), and if it is not found there, the key of the resource will be displayed. Since this fallback mechanism only works within the same resource assembly, you will see different fallback behaviour if you choose the external resources.

Look out for..

The methods described here help in localizing the descriptive elements of your user interface, and given the resources are already there, everything is controlled completely through XAML. In order to localize value formats, you need some other techniques. They are already contained in the example, but I will explain them in a later post.

Credits

I learned how to write a MarkupExtension from this article by Christian Moser. Even though I changed his approach considerably, I found it extremely helpful, and especially if you want to take changes in the UI Language to take effect immediately, you should look at his example.

 

 

(*) Footnote: In order to make this fool proof, one could make the ResourceReferenceType property inheritable, so that it is copied to all child elements. However, I try to avoid FrameworkPropertyMetadataOptions.Inherit if possible, even if I don’t think that looking around for the correct ResourceManager on every localized string is very expensive in terms of performance.