Commanding II: Beyond Calling Methods

June 30, 2012 Leave a comment

In the last post about Commanding I described a basic technique for binding RoutedCommands to method calls in the ViewModel. Today I want to introduce some refinements which make the user experience more complete: Status messages for running commands, localizing exception messages, and how to have one status bar display the status messages of multiple commands.

The complete example code for this post is contained in the CommandingExample project which is part of the WPFGluePublished download available through the downloads page.

Status Messages

If users click a button or a menu item in order to execute a command, they expect instant feedback on whether the command is executing. If the result is not visible immediately, they will appreciate a status message somewhere in the UI which tells them that the command has been executed, and also a message that tells them when it has finished.

In order to accommodate this scenario, we will add three more properties to our Command base class:

Public Shared ReadOnly ReadyMessageProperty As DependencyProperty = DependencyProperty.Register("ReadyMessage", GetType(String), GetType(Command))
Public Property ReadyMessage As String
    Get
        Return GetValue(ReadyMessageProperty)
    End Get
    Set(ByVal value As String)
        SetValue(ReadyMessageProperty, value)
    End Set
End Property

Public Shared ReadOnly WorkingMessageProperty As DependencyProperty = DependencyProperty.Register("WorkingMessage", GetType(String), GetType(Command))
Public Property WorkingMessage As String
    Get
        Return GetValue(WorkingMessageProperty)
    End Get
    Set(ByVal value As String)
        SetValue(WorkingMessageProperty, value)
    End Set
End Property

Private Shared ReadOnly StatusPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("Status", GetType(String), GetType(Command), New PropertyMetadata())
Public Shared ReadOnly StatusProperty As DependencyProperty = StatusPropertyKey.DependencyProperty
Public Property Status As String
    Get
        Return GetValue(StatusProperty)
    End Get
    Private Set(ByVal value As String)
        SetValue(StatusPropertyKey, value)
    End Set
End Property

 

WorkingMessage and ReadyMessage are supposed to be configured by the developer. They are the messages which are shown to the user when the command starts executing and when it is finished. Status is a readonly property. This is where the messages arrive. The idea is to set Status to the WorkingMessage before one calls the ViewModel, and to the ReadyMessage when the ViewModel method has returned.

However, simply changing the property won’t get the message displayed: data binding, notifying of changes that require a new rendering, and executing the ViewModel method all happen on the same thread, so the changed status messages will not be processed until when the ViewModel method has returned. In order to give the UI time to render the WorkingMessage, we need to use an asynchronous pattern including the Dispatcher:

Protected Overridable Sub OnExecuted(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
    Exception = Nothing
    IsRunning = True
    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
    IsRunning = False
End Sub

Protected Overridable 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

 

The OnExecuted method begins with setting some properties that indicate that the command is about to execute. The status message is one of them. Then, it calls Dispatcher.Invoke with the address of a wrapper function, DoExecute, which calls the ViewModel method. The Dispatcher.Invoke call includes a DispatcherPriority.Background parameter. This tells the Dispatcher that it should finish all its usual housekeeping chores, among others displaying our status message, before it calls the DoExecute method. Dispatcher.Invoke returns a DispatcherOperation object. Using this object, we first call Wait in order to yield control to the Dispatcher. The Dispatcher will then take care of everything that is more important (again including displaying our status message), and finally call our method. When the method has returned, the Wait method will return, and the DispatcherOperation’s Result property will contain the return value of DoExecute.

Localizing Exception Messages

The main purpose of DoExecute is to catch any exceptions that might be thrown by the ViewModel method before they reach the Dispatcher, and to expose them through the Command’s Exception property. By default, in case of an exception, the Status property will return the Exception’s Message.

However, this message might not be suitable for display to the users: it might be in a language different form the UI language, or it might be formulated in a way that makes sense to developers, but not to end users who lack the necessary technical background.

So, the UI should be able to receive Exceptions and to translate them into localized messages. Since there might be important information embedded in the original exception message, there should also be a way to extract this information and to insert it into the translated message.

The idea here is to create a converter which receives an Exception, and can be configured in order to create a String message.

The converter looks like this:

Public Class ErrorMessageConverter
    Inherits Freezable
    Implements IValueConverter

    Public Sub New()
        MyBase.New()
        ErrorMessages = New ErrorMessageCollection
    End Sub

    Protected Overrides Function CreateInstanceCore() As System.Windows.Freezable
        Return New ErrorMessageConverter
    End Function

    Private Shared ReadOnly ErrorMessagesPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("ErrorMessages", GetType(ErrorMessageCollection), GetType(ErrorMessageConverter), New PropertyMetadata())
    Public Shared ReadOnly ErrorMessagesProperty As DependencyProperty = ErrorMessagesPropertyKey.DependencyProperty
    Public Property ErrorMessages As ErrorMessageCollection
        Get
            Return GetValue(ErrorMessagesProperty)
        End Get
        Private Set(ByVal value As ErrorMessageCollection)
            SetValue(ErrorMessagesPropertyKey, value)
        End Set
    End Property

    Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
        Dim result As String = Nothing
        If value IsNot Nothing Then
            For Each em As ErrorMessage In ErrorMessages
                result = em.GetMessage(value)
                If result IsNot Nothing Then
                    Exit For
                End If
            Next
        End If

        Return result
    End Function

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

Public Class ErrorMessage
    Inherits Freezable

    Protected Overrides Function CreateInstanceCore() As System.Windows.Freezable
        Return New ErrorMessage
    End Function

    Public Shared ReadOnly ExceptionProperty As DependencyProperty = DependencyProperty.Register("Exception", GetType(String), GetType(ErrorMessage), New PropertyMetadata("Exception"))
    Public Property Exception As String
        Get
            Return GetValue(ExceptionProperty)
        End Get
        Set(ByVal value As String)
            SetValue(ExceptionProperty, value)
        End Set
    End Property

    Private Shared ReadOnly MessageParserProperty As DependencyProperty = DependencyProperty.Register("MessageParser", GetType(System.Text.RegularExpressions.Regex), GetType(ErrorMessage))
    Private Property MessageParser As System.Text.RegularExpressions.Regex
        Get
            Return GetValue(MessageParserProperty)
        End Get
        Set(ByVal value As System.Text.RegularExpressions.Regex)
            SetValue(MessageParserProperty, value)
        End Set
    End Property

    Public Shared ReadOnly MessageRegExProperty As DependencyProperty = DependencyProperty.Register("MessageRegEx", GetType(String), GetType(ErrorMessage), New PropertyMetadata(AddressOf OnMessageRegExChanged))
    Public Property MessageRegEx As String
        Get
            Return GetValue(MessageRegExProperty)
        End Get
        Set(ByVal value As String)
            SetValue(MessageRegExProperty, value)
        End Set
    End Property
    Private Shared Sub OnMessageRegExChanged(ByVal d As ErrorMessage, ByVal e As DependencyPropertyChangedEventArgs)
        Try
            d.MessageParser = New System.Text.RegularExpressions.Regex(e.NewValue)
        Catch ex As Exception
            d.MessageParser = Nothing
        End Try
    End Sub

    Public Shared ReadOnly FormatProperty As DependencyProperty = DependencyProperty.Register("Format", GetType(String), GetType(ErrorMessage))
    Public Property Format As String
        Get
            Return GetValue(FormatProperty)
        End Get
        Set(ByVal value As String)
            SetValue(FormatProperty, value)
        End Set
    End Property

    Public Function IsMatch(ByVal ex As Exception) As Boolean
        Dim result As Boolean = ex IsNot Nothing
        If result Then
            result = ex.GetType.FullName.EndsWith(Exception)
        End If
        If result And MessageParser IsNot Nothing Then
            result = MessageParser.IsMatch(ex.Message)
        End If
        Return result
    End Function

    Public Function GetMessage(ByVal ex As Exception) As String
        Dim result As String = Nothing
        If IsMatch(ex) Then
            If MessageParser Is Nothing Then
                result = Format
            Else
                Dim m As System.Text.RegularExpressions.Match = MessageParser.Match(ex.Message)
                Dim values() As String = (From g As System.Text.RegularExpressions.Group In m.Groups
                                          Select g.Value).ToArray
                result = String.Format(Format, values)
            End If
        End If
        Return result
    End Function
End Class

Public Class ErrorMessageCollection
    Inherits FreezableCollection(Of ErrorMessage)
End Class

 

The most important element here is the ErrorMessage class. The ErrorMessage class has the following properties:

  • Exception: The type name of the Exception class, or at least the last part of it.
  • MessageRegEx: optionally, a standard .Net regular expression which must match the original exception message. It can contain grouping constructs (i.e. parentheses) in order to extract information from the exception message.
  • Format: the String into which the exception message should be translated. This string can contain format placeholders (like {1}) for the information extracted from the original exception message.
    If the converter receives an exception, it iterates the configured ErrorMessage objects in order to find the first one that matches the exception. Then it calls GetMessage on this object in order to return a message, and stops the iteration. Since the Exception property only matches the last part of the exception’s type name, can configure a catchall using Exception=”Exception” on the last ErrorMessage object in your ErrorMessageConverter.

Here an example for how to configure an ErrorMessageConverter:

<l:ErrorMessageConverter x:Key="errorMessageTranslator">
    <l:ErrorMessage Exception="InvalidOperationException"
                    MessageRegEx="This operation is ([^!]*)!" Format="Don't try {1} operations!"/>
</l:ErrorMessageConverter>

 

Notice that the regular expression’s first capture group has index 1, while index 0 will return the complete match.

Creating a Status Line

While we can now display status messages for one command, normally one doesn’t want one status control for each command that might be executed through the UI. Instead, normally there is a status line that displays status of any command that might be executing at the moment.

So, what we need is kind of a funnel which collects status messages from all commands and always returns the most recent message. The component which does this is called FunnelConverter:

Public Class FunnelConverter
    Implements IMultiValueConverter

    Private _Values() As Object
    Private _Result As Object = Nothing

    Public Function Convert(values() As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert
        Dim result As Object = Nothing

        If values Is Nothing OrElse values.Length = 0 Then
            _Values = Nothing
        Else
            Dim i As Integer = 0
            Do While _Values IsNot Nothing AndAlso (i < _Values.Length And i < values.Length And result Is Nothing)
                If _Values(i) Is Nothing OrElse Not _Values(i).Equals(values(i)) Then
                    result = values(i)
                End If
                i += 1
            Loop
            Do While result Is Nothing And i < values.Length
                result = values(i)
                i += 1
            Loop
            _Values = values.ToArray
        End If

        If result IsNot Nothing Then
            _Result = result
        End If
        Return _Result

    End Function

    Public Function ConvertBack(value As Object, targetTypes() As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
        Throw New NotSupportedException
    End Function

 

This class is a MultiBindingConverter. It is supposed to be used with a MultiBinding, which accepts an array of bindings, and returns a new value whenever one of the bound values changes. The FunnelConverter stores a copy of the list of values it receives, and displays the first value that has changed, or the first value which is not nothing if there are no values, yet. Since all command status changes are executed on the UI thread, only one value will change at any given time, and no changes will be lost. Since this converter stores the results of the last invocation, one has to make sure that each converter instance is only used in one binding; so, the most natural solution is to not declare the converter as a resource, but to instantiate it inline in the MultiBinding tag:

<StatusBarItem>
    <StatusBarItem.Content>
        <MultiBinding>
            <MultiBinding.Converter>
                <c:FunnelConverter/>
            </MultiBinding.Converter>
            <Binding Source="{StaticResource commands}" Path="Commands[Open].Status"/>
            <Binding Source="{StaticResource commands}" Path="Commands[Find].Status"/>
            <Binding Source="{StaticResource commands}" Path="Commands[Print].Status"/>
            <Binding Source="{StaticResource commands}" Path="Commands[Stop].Status"/>
            <Binding Source="{StaticResource commands}" Path="Commands[Print].Exception" Converter="{StaticResource errorMessageTranslator}"/>
        </MultiBinding>
    </StatusBarItem.Content>
</StatusBarItem>

Conclusion

Using the techniques described in this post, one can keep the users informed about what the application is currently doing, and about any exceptions that might occur in ViewModel code. Since all messages which are configured in XAML can be localized, using the classes described here is also a step towards globalizing the application.

Another topic I want to cover in a later post is how to have commands executed asynchronously in the background, either on a background thread or by executing a number of short operations on the Dispatcher.

Localization Revisited

May 31, 2012 Leave a comment

Among the first things I covered in this blog was Localization. After I had finished those posts, I felt like I had covered everything one would need to write fully globalized applications. However, meanwhile I found out how to do some things better, and I’d like to share that here.

Making Localization Work at Design Time

One of the drawbacks of the original WPFGlue localization components was that they didn’t work nicely at design time. The reason was that I hadn’t found out how to automatically locate an application’s resources when the application was hosted inside the VS XAML designer.

The key here is a property on the Application class named ResourceAssembly. When the VS designer hosts an assembly, it will put a reference to it into this property, so the MarkupExtensions can locate the assembly that contains the current project’s resources.

Also, I revised the detection for the resource basename. The basename contains the project’s root namespace. One can find this as the namespace of the project’s Application class, which will normally be the project’s only class derived of System.Windows.Application.

I encapsulated this all into a new base class for the localization markup extensions:

Namespace Localization
    Public MustInherit Class ResourceExtension
        Inherits System.Windows.Markup.MarkupExtension

        Public Sub New()
            MyBase.New()
        End Sub

        Public Sub New(ByVal key As String)
            MyBase.New()
            Me.Key = key
        End Sub

        Private _Key As String
        Public Property Key() As String
            Get
                If String.IsNullOrEmpty(_Key) Then
                    Return "No Key"
                Else
                    Return _Key
                End If
            End Get
            Set(ByVal value As String)
                _Key = value
            End Set
        End Property

        Private Shared _BaseName As String
        Public Shared Property BaseName() As String
            Get
                If _BaseName Is Nothing And Assembly IsNot Nothing Then
                    Dim applicationType As Type = (Aggregate t As Type In Assembly.GetTypes() Where t.IsSubclassOf(GetType(System.Windows.Application)) Into First())
                    If applicationType IsNot Nothing Then
                        _BaseName = applicationType.Namespace & ".Resources"
                    End If
                End If
                Return _BaseName
            End Get
            Set(ByVal value As String)
                _BaseName = value
                _ResourceManager = Nothing
            End Set
        End Property

        Private Shared _Assembly As System.Reflection.Assembly
        Public Shared Property Assembly() As System.Reflection.Assembly
            Get
                If _Assembly Is Nothing Then
                    _Assembly = System.Reflection.Assembly.GetEntryAssembly
                End If
                If _Assembly Is Nothing Then
                    _Assembly = Application.ResourceAssembly
                End If
                Return _Assembly
            End Get
            Set(ByVal value As System.Reflection.Assembly)
                _Assembly = value
                _ResourceManager = Nothing
            End Set
        End Property

        Private Shared _ResourceManager As System.Resources.ResourceManager
        Public Shared Property ResourceManager() As System.Resources.ResourceManager
            Get
                If _ResourceManager Is Nothing Then
                    If Assembly IsNot Nothing And BaseName IsNot Nothing Then
                        _ResourceManager = New System.Resources.ResourceManager(BaseName, Assembly)
                    End If
                End If
                Return _ResourceManager
            End Get
            Set(ByVal value As System.Resources.ResourceManager)
                _ResourceManager = value
            End Set
        End Property
    End Class
End Namespace

 

Notice the fallback detection for the resource assembly: the first location is the application domain’s entry assembly. If the assembly isn’t running it’s own domain (because it’s hosted in the designer), the next location is the ResourceAssembly. As before, one can override the resource assembly by setting the Localization.ResourceAssembly attached property.

By the way, in the post "The Last Word on Localized Resources" I suggested using a localized URL in order to use different localized ResourceDictionaries in ones XAML files. I don’t recommend this approach any longer. While it works at run time, the designer experience is bad: one will get squiggly lines telling that the resources can’t be found, and one cannot see the values of the resources in the designer panel. With the new markup extensions based on the above class, one will have instant feedback.

Funny enough, VS will use the set of resources that match its own language version. So, running US-English Microsoft Visual Basic 2010 Express on German Windows, I see the English localization in the designer and the German localization when I start the debugger.

How to Use Bitmap Resources

Another thing I learned is how to convert from System.Drawing.Bitmap, which is what one gets from the ResourceManager when one accesses an image resource, to System.Windows.Media.Imaging.BitmapSource, which is what the WPF Image control expects:

Public Overrides Function ProvideValue(ByVal serviceProvider As System.IServiceProvider) As Object
    Dim result As BitmapSource = NoImage
    If ResourceManager IsNot Nothing Then
        Dim data As System.Drawing.Bitmap = TryCast(ResourceManager.GetObject(Key), System.Drawing.Bitmap)
        If data IsNot Nothing Then
            Dim sourceRect = New Int32Rect(0, 0, data.Width, data.Height)
            result = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(data.GetHbitmap, Nothing, sourceRect, BitmapSizeOptions.FromEmptyOptions)
        End If
    End If
    Return result
End Function

 

The conversion happens through a shared bitmap handle, so I assume it’s not too heavy on memory consumption. As a nice side effect, one can now use PNG images without them being unaccountably scaled to 133% their pixel size. Also, the images will now instantly be visible in the designer.

How to Create Alternative Resource Files

In my first post about localized resources, I described a kind of complicated procedure for creating resource files for alternative languages. A somewhat simplified procedure, which goes without closing the IDE, is as follows:

  • Complete your application, creating resources in the My Project / Resources designer as you go.
  • When done, activate the “Show All Files” icon in Solution Explorer.
  • Locate the Resources.resx file in the My Project folder and create a copy of it. You can do this using the context menu in Solution Explorer. The copy can be located anywhere in your project directory. Probably you should keep it outside the My Project folder.
  • Rename the copy to reflect the intended language and culture, e.g. to Resources.de.resx for general German, or Resources.ar-SA.resx for Arabic (Saudi Arabia). At that stage, you can deactivate the “Show All Files” switch again.
  • Double click the file in order to open it in the resource designer.
  • Make sure that the Access Modifier is set to “No Code Generation”.
  • Replace the original resource strings and icons with the appropriate translations.
  • Make sure that the Neutral Language in My Project / Application / Assembly Information is set to the language you used for the original version of the resources.
  •  

    Conclusion

    While support for .Net resources is completely missing from WPF, one can create an experience that feels equally nice at design time and at run time by using some simple custom markup extensions.

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.

Checking Property Types Automatically

May 6, 2012 2 comments

It’s been a long time since I have written in this blog. The reason is: there have been many changes in my life during the past two years, and I simply haven’t found the time until now. Anyway, I’ve been checking out things and learning continuously, and I hope to be able to share these things more regularly in the future.

In the last post, Validating Objects with ValidationRules, I proposed a way to define ValidationRules for an object in one location and to set them on all Bindings that need them through a single attached property.

Today, I want to show how to set up basic type checks automatically. The example code is contained in the ValidationExample project in the source code distribution, which you can access through the downloads page.

How WPF handles Type

Normally, if the value entered in a TextBox cannot be converted into the type of the bound property, e.g. if you enter letters into a TextBox expecting an integer value, first of all there will be a runtime exception. By default, WPF will handle this exception, mark the TextBox as having invalid data, and assign a default “Conversion error” error message to it. Since the value cannot be converted, WPF will not update the bound property, which means that it will keep its value unchanged.

The first level of refinement here would be to allow the Binding to display exception messages by setting ValidatesOnExceptions to true. Now, if changing a property causes an exception, Validation.Errors will contain the exception’s message. Now the user will at least know whether the reason for the exception is a type mismatch or a numerical overflow.

Validation as a User Interface Responsibility

Why is this not enough?

Reusable software components like services or libraries need to check their input and throw exceptions if it is invalid. This is how one piece of software tells another piece of software that it has done something wrong.

User Interfaces, which basically translate between human users and the software they want to use, are responsible for making sure that those users only provide valid input values. The best way to do that is to offer only valid options to the user. Where that isn’t possible, validation steps in by providing explicit and meaningful validation messages which tell the users what they have to change in order to achieve the intended result.

As a rule of thumb, I’d say that one should protect one’s services and libraries by throwing exceptions, and that one should use validation in the UI to make sure that those exceptions never get thrown.

So, while exceptions are directed at technically aware developers debugging their software components, validation messages are directed at end users with possibly no technical background at all. For example, the error message you receive when you enter too many digits into the TextBox that validates with exceptions in the example project, reads: “Value was either too large or too small for an Int32”. This is obviously not what one would display to a user who has never heard of Int32.

Therefore, in the user interface, one would need two functionalities:

  1. a way to provide globalized validation messages independent of the system’s exception messages.
  2. a way to keep WPF controls from trying to update properties with values of non-matching type.

The TypeValidationRuleSetter Class

The TypeValidationRuleSetter class is a subclass of the ValidationRuleSetter class I mentioned in the last post. In addition to adding predefined ValidationRules to specified properties on a bound object, it will inspect the object’s type, find out the primitive CLR-type of a bound property, and will add default validation rules for each type which use XAML-configurable validation messages.

At the heart of the TypeValidationRuleSetter class is the following method:

Protected Overrides Sub AttachRules(ByVal base As Object)
    Dim f As FrameworkElement = TryCast(base, FrameworkElement)
    If f IsNot Nothing And ReferenceType IsNot Nothing Then
        If f IsNot Nothing Then
            Dim bg As BindingGroup = f.BindingGroup
            If bg IsNot Nothing Then
                For Each be As BindingExpression In bg.BindingExpressions.OfType(Of BindingExpression)()
                    Dim propertyName = be.ParentBinding.Path.Path
                    Dim propertyInfo As System.Reflection.PropertyInfo = ReferenceType.GetProperty(propertyName)
                    Select Case propertyInfo.PropertyType
                        Case GetType(DateTime)
                            be.ParentBinding.ValidationRules.Add(New DateValidationRule(Me.DateMessage))
                        Case GetType(Int16)
                            be.ParentBinding.ValidationRules.Add(New IntegerValidationRule(Int16.MinValue, Int16.MaxValue, Me.IntegerMessage))
                        Case GetType(Int32)
                            be.ParentBinding.ValidationRules.Add(New IntegerValidationRule(Int32.MinValue, Int32.MaxValue,Me.IntegerMessage))
                        Case GetType(Int64)
                            be.ParentBinding.ValidationRules.Add(New IntegerValidationRule(Int64.MinValue, Int64.MaxValue, Me.IntegerMessage))
                        Case GetType(Single)
                            be.ParentBinding.ValidationRules.Add(New NumericValidationRule(Single.MinValue, Single.MaxValue,Me.NumericMessage))
                        Case GetType(Double)
                            be.ParentBinding.ValidationRules.Add(New NumericValidationRule(Double.MinValue, Double.MaxValue, Me.NumericMessage))
                        Case GetType(Decimal)
                            be.ParentBinding.ValidationRules.Add(New NumericValidationRule(Decimal.MinValue, Decimal.MaxValue, Me.NumericMessage))
                        Case GetType(Integer)
                            be.ParentBinding.ValidationRules.Add(New IntegerValidationRule(Integer.MinValue, Integer.MaxValue, Me.IntegerMessage))
                    End Select
                    If Setters.Contains(propertyName) Then
                        For Each v As System.Windows.Controls.ValidationRule In Setters.Item(propertyName).ValidationRules
                            be.ParentBinding.ValidationRules.Add(v)
                        Next
                    End If
                Next
            End If
        End If
    End If
End Sub

 

This method will be called during the Initialized event, when Bindings have been established but still can be modified. It inspects the type of the property and adds one of a choice of default ValidationRules, which basically make a difference only between date, integer and other numerical values, providing different value ranges as feedback to the end user. So, the class translates from the technical, physical representation, which is determined by the CLR type of the property, to the logical limitations that type entails, which is what is relevant for the user.

So, although there are different checks for different allowed value ranges, there are only three configurable, i.e. localizable, validation messages:

Public Property DateMessage As String = "Must be a date value."

Public Property IntegerMessage As String = "Must be an integer between {1} and {2}."

Public Property NumericMessage As String = "Must be a number between {1} and {2}."

 

The default ValidationRules use basic .Net functions in order to determine whether .Net can convert the value:

Protected Overrides Function ValidateCore(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As Boolean
    Dim result As Boolean = IsNumeric(value)
    If result And MinValue.HasValue Then
        result = CDbl(value) >= MinValue
    End If
    If result And MaxValue.HasValue Then
        result = CDbl(value) <= MaxValue
    End If
    Return result
End Function

 

Finally, the allowed minimum and maximum values are formatted into the validation message, to give the users complete feedback on what they have to enter in order to make it a valid value:

Protected Overrides Function FormatMessage(ByVal value As Object) As String
    Return String.Format(Me.Message, value, MinValue, MaxValue)
End Function

 

Conclusion

The TypeValidationRuleSetter class allows it to include meaningful and localizable validation messages for type checks with minimal effort. At the same time, it protects the application from exceptions which would otherwise be thrown because of type mismatches between what is entered into a TextBox and the type of the bound property.

Validating Objects with ValidationRules

December 31, 2010 4 comments

In examples about writing WPF applications with the MVVM pattern, normally one doesn’t find any reference to ValidationRules. The normal way to do things there is to wrap the business object into the ViewModel and to implement IDataErrorInfo on the ViewModel. However, there are several points about this approach which I don’t like:

  • I don’t like to wrap classes into other classes. The kind of code this produces is exactly the kind of useless boilerplate code I want to get rid of by using a flexible data binding system like WPF’s. If it can’t be helped, this kind of code should be generated, but that is not the topic of this blog.
  • Validation on the ViewModel or Business level removes validation logic and error messages from the domain of the user interface definition to the domain of business logic. Now I like my business logic to be language independent, and it is absolutely valid for a user interface to implement different (more restrictive) checks on a property value than the business object it accesses. So, even if validation logic is implemented on the business level, I want to be able to choose how to use it and what error messages to display on the UI level, i.e. in XAML.

That said, ValidationRules have another significant advantage: they can check values before they are passed to the properties of the bound object. This means that I don’t have to implement rollback capabilities on the business object, but can make sure on the UI level that only valid values are set on the business object. This will be covered in a later post.

With all these advantages, there is a serious drawback to ValidationRules: defining them in XAML is rather unwieldy. Done the traditional way, they tend to bloat the XAML code, and sometimes it is difficult to get at all the reference data they need for their checks.

This is where WPFGlue comes into the picture: here, I want to introduce some ideas which allow to define and apply ValidationRules in a more concise manner, and to do some basic checks even without specifying ValidationRules explicitly.

The BindingGroup Class

The BindingGroup class has been introduced in WPF 3.5 SP1. A BindingGroup can be set on every FrameworkElement. If set, it can define ValidationRules which are able to access multiple properties of the DataContext in order to validate them against each other. In order to afford this possibility, the BindingGroup class provides methods which can access all Bindings in the subtree under the element which defines the BindingGroup, as long as these Bindings have the same DataContext as that element.

The idea here is to use this ability of the BindingGroup class in order to set up ValidationRules for all bindings which share a given DataContext.

So, in order to validate objects of a given class, first of all one would use a container like a StackPanel, Grid or GroupBox to combine a group of controls which share an instance of that class as their DataContext. Then, one would create a BindingGroup object and set it to the BindingGroup property of that container. Further, one would define a set of ValidationRules for a given class. Some of them would be at the property level, others would access multiple properties, as would be normal for ValidationRules on a BindingGroup object. Then, one would apply this set of ValidationRules to the container, instead of the separate controls which edit the object’s properties. One could reuse the set of ValidationRules wherever an instance of the DataContext class was used.

Using the BindingGroup’s ability for finding Bindings which have the same DataContext, one would add the ValidationRules  to the appropriate Bindings inside the container.

How to Modify Bindings Which are Defined in XAML

The documentation says that Bindings can not be modified after they have been used. However, there is an event that comes right between the time when the Bindings have all been created, and when they are used for the first time: Initialized. If the DataContext is not null and a BindingGroup is present on a FrameworkElement, this BindingGroup can access and modify those Bindings during the FrameworkElement’s Initialized event.

So, one would need a couple of things:

  • A way to define sets of ValidationRules.
  • A Sticky Component which handles distributing the ValidationRules to the Bindings.
  • A Sticky Property which attaches an instance of a set of ValidationRules to a container.

Defining Sets of ValidationRules

First of all, I said that I wanted to be able to define error messages in XAML. Using the techniques from the WPFGlue Localization namespace, I can handle multiple languages quite conveniently in XAML, and there might be other considerations like the intended audience of an application which might induce me to change the text of error messages. So, my first addition to a WPFGlue ValidationRule base class would be a Message property, which allows me to set the error message in XAML. In addition to that, I just added the functionality to format the property value into the error message.

Namespace Validation
    Public MustInherit Class ValidationRule
        Inherits System.Windows.Controls.ValidationRule

        Private _Message As String = String.Empty
        Public Property Message() As String
            Get
                Return _Message
            End Get
            Set(ByVal value As String)
                _Message = value
            End Set
        End Property

        Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As System.Windows.Controls.ValidationResult
            Dim result As ValidationResult = ValidationResult.ValidResult
            If Not ValidateCore(value, cultureInfo) Then
                Dim message As String = Me.FormatMessage(value)
                result = New ValidationResult(False, message)
            End If
            Return result
        End Function

        Protected MustOverride Function ValidateCore(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As Boolean

        Protected Overridable Function FormatMessage(ByVal value As Object) As String
            Return String.Format(Me.Message, value)
        End Function
    End Class
End Namespace

 

The set of ValidationRules would be defined as a resource in XAML. In order to be able to do that, I create several classes which allow to define collections of ValidationRules grouped by property name:

Namespace Validation
    <System.Windows.Markup.ContentProperty("Setters")>
    Public Class ValidationRuleSetter
        Inherits DependencyObject
        Implements IStickyComponent

        Private Shared _DummyDataContext As Object = New Object
        Protected Overridable ReadOnly Property DummyDataContext As Object
            Get
                Return _DummyDataContext
            End Get
        End Property

        Private _Setters As New PropertyValidationRuleSetterCollection
        Public ReadOnly Property Setters As PropertyValidationRuleSetterCollection
            Get
                Return _Setters
            End Get
        End Property

 

The collection Setters contains collections of ValidationRules, grouped under different property names.

The ValidationRuleSetter class is a Sticky Component: in addition to defining the set of ValidationRules, it also has the ability to set the ValidationRules on the Bindings which belong to the container’s BindingGroup. The following method will be called during the container’s Initialized event:

Protected Overridable Sub AttachRules(ByVal base As Object)

    Dim f As FrameworkElement = TryCast(base, FrameworkElement)
    If f IsNot Nothing Then
        Dim bg As BindingGroup = f.BindingGroup
        If bg IsNot Nothing Then
            For Each be As BindingExpression In bg.BindingExpressions.OfType(Of BindingExpression)()
                If Setters.Contains(be.ParentBinding.Path.Path) Then
                    For Each v As System.Windows.Controls.ValidationRule In Setters.Item(be.ParentBinding.Path.Path).ValidationRules
                        be.ParentBinding.ValidationRules.Add(v)
                    Next
                End If
                If Setters.Contains(String.Empty) Then
                    For Each v As System.Windows.Controls.ValidationRule In Setters.Item(String.Empty).ValidationRules
                        bg.ValidationRules.Add(v)
                    Next
                End If
            Next
        End If
    End If

End Sub

 

For each Binding that is accessible to the BindingGroup, it looks up the name of the bound property and attaches all ValidationRules which are grouped under that name, if any. By convention, ValidationRules which have an empty String as property name are attached to the BindingGroup itself.

How to Use ValidationRuleSetter on a Container

In order to be able to attach the Sticky Component to a container, one defines a custom attached property like this:

Public Shared ReadOnly RuleSetterProperty As DependencyProperty = DependencyProperty.RegisterAttached("RuleSetter", GetType(ValidationRuleSetter), GetType(Validation), New PropertyMetadata(AddressOf WPFGlue.Framework.StickyComponentManager.OnStickyComponentChanged))
Public Shared Function GetRuleSetter(ByVal d As DependencyObject) As ValidationRuleSetter
    Return d.GetValue(RuleSetterProperty)
End Function
Public Shared Sub SetRuleSetter(ByVal d As DependencyObject, ByVal value As ValidationRuleSetter)
    d.SetValue(RuleSetterProperty, value)
End Sub

 

This is actually the standard way of defining a sticky property in the WPFGlue framework.

One would then define the container in XAML like this:

<StackPanel v:Validation.RuleSetter="{StaticResource vrules}" >
    <StackPanel.BindingGroup>
        <BindingGroup/>
    </StackPanel.BindingGroup>
    <TextBox Text="{Binding StringProperty, UpdateSourceTrigger=PropertyChanged}"
     Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
    <TextBox Text="{Binding IntegerProperty, UpdateSourceTrigger=PropertyChanged}"
     Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
    <TextBox Text="{Binding DoubleProperty, UpdateSourceTrigger=PropertyChanged}"
     Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
</StackPanel>

 

Side Effects of Using BindingGroups

The presence of a BindingGroup on a container has various effects:

  • Any failed ValidationRule on any Binding inside the BindingGroup will make Validation.HasError return true on the container.
  • The BindingGroup’s own ValidationRules will cause the Validation.ErrorTemplate to be displayed on the container. ValidationRules on the Bindings will show the ErrorTemplate on the respective control and also the container’s ErrorTemplate.
  • The UpdateSourceTrigger on all Bindings inside the BindingGroup will be set to Explicit by default, which means that one would have to call BindingGroup.CommitEdit in order to have the Bindings update their sources. However, if the UpdateSourceTrigger on a Binding is set to another value in XAML, this value will be honoured.

Conclusion

Using this technique, it is possible to separate the definition of validation rules both from the model and from the view. This overcomes the main disadvantage of ValidationRules and opens up the way for further applications.

In posts to come, I intend to cover how to extend this technique in order to do type checks on property values before they are committed to the bound properties, and how to connect this with general data entry handling.

On the Downloads page, you can find the full code of the examples, plus some additions which will be covered in a later post.

WPFGlue goes WPF 4.0

May 23, 2010 Leave a comment

When VS 2010 was released, I was quite eager to shift to the new version of the framework: the last two releases had brought a lot of real improvements, so I expected the new release to do likewise. Therefore, I upgraded the WPFGlue code to .Net 4.0 and tried it out. These were my experiences:

Upgrade Experience

Upgrading went real smooth: it’s possible to install VS 2010 parallel to VS 2008, so I did that, copied my projects to the new version’s project folder, and opened them. After completing an Upgrade Wizard, there I was… No problem at all, and the Upgrade Wizard didn’t have to change any code, it just set the projects to target the .Net 4.0 Framework instead of .Net 3.5 . (Cool feature: in VS 2010 Express you can select the targeted framework version in the compile options, and you can select older and (hopefully) even newer versions than the one that comes with the VS installation). I have been running VS 2010 and VS 2008 in parallel ever since, and so far haven’t experinced any problems with that.

However, when testing the examples again, there were a few glitches:

  • The LocalizedList in the WPFGlue.Localization namespace caused an error message in the XAML designer.
  • There was a problem with Bindings when navigating backwards through the navigation history in a NavigationWindow.

Error Message in the XAML Designer

The new XAML designer is supposed to be more robust and to be more consistent with the Blend XAML designer. However, one of the tricks I did with the LocalizedList class caused it to hiccup: On this class, I implemented IList so one could set the child elements directly. This was due to the fact that the VS 2008 XAML designer was not handling this very graceful, and I wrote the class before I found out what one has to do to have proper support for typed collections. At the same time, this class can be used as a converter in bindings, in order to translate Enum values into human readable localized strings.

The combination of IList and IValueConverter seemed to be too much for the designer: while the application was compiling and running alright, the designer kept showing squiggly lines about an unset object reference in the XAML pane.

Debugging custom mark-up extensions in VS 2010 Express Edition is a little bit painful. I would recommend to buy the full version if you consider this, and meanwhile I’d recommend to keep away from them… Anyway, I found out that the error message goes away if you don’t implement IList on the same class as IValueConverter, so I just followed the pattern described in the Coding for XAML post.

I posted this problem on the Connect website as product feedback, but they decided to not do anything about it, which I can understand.

TextBox Bindings not Restored when Navigating Backwards

I found this problem first in this thread in the MSDN WPF forum. When you have a page that has data-bound TextBoxes, navigate away from the page and then go back using the Back button of the NavigationWindow that contains the page, the TextBoxes come up blank, even though the DataContext of the page and TextBox is set correctly. It seems that the XAML of the Page is not read the same way when the page is re-displayed; I tried to solve the problem with a custom mark-up extension, and the extension was not instantiated when the page was revisited. So, I tried a DataTemplate instead, and that worked. Finally, I came up with wrapping the page contents into a UserControl. The UserControl seems to read its XAML definition every time it is displayed, and since it inherits the correct DataContext, the problem goes away.

Still, I think this is quite a serious glitch. I posted it to Connect as well, the answer is pending.

Conclusion

Having these strange kinds of errors was a little bit sobering about .Net 4.0. Even so, since I found workarounds, and since the new framework has some cool new features I hope to be using soon, I’m going to stick to it.

So, I updated the downloads page with projects targeting the new version. I’m still keeping copies of the WPF 3.5 version, as well, but I’m not going to include new code into these versions.

Coming Soon…

The next thing I’m planning to do is to research the object lifetime events in WPF 4.0. I was stopped in my tracks a little bit when I found out that the pattern of Loaded and Unloaded events is not quite what one expects (this was still under WPF 3.5), and I’d like to find out what happens there exactly.

Categories: WPFGlue Tags:

Coding for XAML

April 1, 2010 1 comment

XAML is basically a way of describing object structures, which will be instantiated by the XAML parser when a page, resource or template is used. Since one of the ideas in WPFGlue is to configure components in XAML, I had to wrestle a bit with the VS XAML designer and my classes in order to make them work well with each other. This post is about what patterns you can use in order to make classes nice to use in XAML, and how to implement them.

Basics

A XAML tag is basically nothing but a class name and some attributes. The class name stands for the component, the attributes, for its properties. What the XAML parser will do is to create an instance of the class using a parameterless constructor, and to set the properties which have the same names as the attributes to the provided attribute values. So, the basic rules for creating a class for use in XAML are:

  • The class must be public.
  • It must have a public, parameterless constructor.
  • All properties that will be accessed in XAML must be public.

Two other best practices turned out to influence the XAML experience of my custom components, even though I didn’t quite find out why, and I expect this behaviour to change with version 4.0… Well, the problem was that the VS 2008 XAML editor didn’t recognize collection types properly, and that its Intellisense function would suggest every available element, not only the one which are allowed in the collection.

In order to fix that, I did the following:

    Using the ContentProperty Attribute

    XAML elements can have one or many content elements. A content element is something like the “natural” or most important child element of its parent. It is special because you can set it in XAML without mentioning the name of the property which references it. However, in order to create an object structure, the XAML parser needs to know the property name. Therefore, you can set the name of the property which holds the child element by adding the System.Windows.Markup.ContentProperty(“MyChild”) attribute to the class declaration, where “MyChild” would be the name of the property which holds the child.

    If the property is of an collection type, the element can contain multiple child elements, and they will all be added to the collection which the property holds.

    Collections for XAML

    If you set up your project like I explained in the first section, all you have to do is to derive a collection class from List(Of T) or ObservableCollection(Of T), where T is your item type.

    Public Class XamlCollection
        Inherits List(Of XamlCollectionItem)

    End Class

    Public Class XamlCollectionItem

        Private _IntegerValue As Integer
        Public Property IntegerValue() As Integer
            Get
                Return _IntegerValue
            End Get
            Set(ByVal value As Integer)
                _IntegerValue = value
            End Set
        End Property

    End Class

    If you do it this way, the XAML will display and compile without problems, and Intellisense will offer you only the available classes of the correct type. Normally, since we are talking about configuring components through XAML, the collection is not expected to change during run time, so it would be enough to use List(Of T).

    If you want to use a collection type as the content for an element, you’d have to do the following further steps:

    • Create a read-only property which holds an instance of the collection type.
    • Initialize it with an empty collection in the elements constructor.
    • Set the ContentProperty attribute to the property’s name.
    <System.Windows.Markup.ContentProperty("Children")> _
    Public Class XamlCollectionParent
        Private _Children As New XamlCollection
        Public ReadOnly Property Children() As XamlCollection
            Get
                Return _Children
            End Get
        End Property
    End Class

    Referring to Objects by Name I: Keyed Collections

    Sometimes you want to refer to be able to refer to specific children of an element. The best way to make sure that you get the correct child is to name the children and to refer to them by name. In XAML, you would do this in a binding’s property path, like this: {Binding Path=MyParentElement.Children[ChildName]}

    In order to be able to do that, you’d derive the collection class from KeyedCollection(Of T). Then you’d have to override the GetKeyForItem function, which is supposed to create a unique string key from an item of type T.

    Public Class XamlDictionary
        Inherits System.Collections.ObjectModel.KeyedCollection(Of String, XamlDictionaryItem)

        Protected Overrides Function GetKeyForItem(ByVal item As XamlDictionaryItem) As String
            Return item.Key
        End Function
    End Class

    Public Class XamlDictionaryItem
        Private _Key As String
        Public Property Key() As String
            Get
                Return _Key
            End Get
            Set(ByVal value As String)
                _Key = value
            End Set
        End Property

        Private _IntegerValue As Integer
        Public Property IntegerValue() As Integer
            Get
                Return _IntegerValue
            End Get
            Set(ByVal value As Integer)
                _IntegerValue = value
            End Set
        End Property
    End Class

    You may ask why I don’t use Dictionary(Of TKey, TValue). Well, it seems that XAML’s support for dictionaries is very much geared towards the special needs of ResourceDictionaries, and I believe it is not intended to be used generally. However, KeyedCollection offers you the additional benefit that you can simply iterate over the items and don’t have to worry about mapping Key / Value pairs, since the keys are taken directly from the items.

    Referring to Objects by Name II: NameScopes

    A NameScope in WPF is what makes element names in a page unique and allows to reference elements by name using their x:Name attribute. If you have a tree structure of elements, but need to reference single elements by name also, a NameScope is the thing to use.

    A Namescope is defined through a root element XAML. All descendants of this element which have an x:Name attribute will automatically be registered to the Namescope. In order for this to work, you have to do the following:

    • The class which is the root element has to implement the System.Windows.Markup.INameScope interface. Internally, the implementation can be based on the NameScope class, which comes with the WPF framework.
    • The classes which are used as descendants of the root element need to use the System.Windows.Markup.RuntimeNameProperty attribute in their declaration, so that the XAML parser knows which property of the class stores the name.
    • In order to access the items by name, one needs an indexer. XAML is a little bit limited in its support for indexed properties: you have to have a class which declares the indexer as its default property. In order to provide indexed access to the named elements, this examples uses a nested class which does nothing but find the elements through its parent’s NameScope.
    Private _Indexer As New NamescopeIndexer(Me)
    Public ReadOnly Property Node() As NamescopeIndexer
        Get
            Return _Indexer
        End Get
    End Property

    Public Class NamescopeIndexer
        Private _Root As XamlNamescopeRoot
        Friend Sub New(ByVal root As XamlNamescopeRoot)
            MyBase.New()
            _Root = root
        End Sub

        Default Public ReadOnly Property Item(ByVal name As String) As NamescopeNode
            Get
                Return _Root.Namescope.FindName(name)
            End Get
        End Property
    End Class

     

    Markup Extensions

    Markup extensions are helpful in situations where you have to do more complicated things than just creating an instance of a class and assign it some values. For example, you can use a Markup extension if you want to establish a binding, or if you want to access a backing store which itself is not accessible to XAML, like application settings, localized resources or a configuration file.

    However, I am not very fond of markup extensions. They are basically a convenience: normally, you can achieve the same using just a more verbose syntax. And the convenience comes at a price:

    Markup extensions have to execute well both at runtime and in the VS designer. Both environments are radically different, and it is not trivial to find out the difference from inside the markup extension. Also, if there is any problem in design mode, there is no debugging support, since the designer always uses the release compiled version of the markup extension. Moreover, I even couldn’t use structured error handling inside the class, since my catch blocks never were called, and sometimes VS crashes when compiling or editing markup extensions.

    Anyway, for the sake of completeness, here what I learned:

    • Derive a markup extension from System.Windows.Markup.MarkupExtension and override the ProvideValue method.
    • Set the MarkupExtensionReturnType attribute to the correct type.
    • If the type of the serviceProvider parameter is System.Windows.Markup.ProvideValueServiceProvider, you are in runtime mode, otherwise, you are in design mode.
    • Use serviceProvider.GetService(GetType(IProvideValueTarget)) in order to retrieve information about the property which will receive the value. If the TargetProperty parameter is of type DependencyProperty, the property is a dependency property and you can do things like set up a binding, so that the value will be updated automatically if the underlying data source changes.
    • Always return a valid value of the correct type, even if the value is some message like “I couldn’t find what you wanted”.
    • Whatever you do, avoid exceptions in the ProvideValue method at all costs. In this case this doesn’t mean “catch”, but really “avoid”, since structured exception handling doesn’t seem to work when the extension is used in the designer.

    Reporting Configuration Errors

    As a rule, your component shouldn’t raise any exceptions if it doesn’t have all the information it needs to do its job, but just make do without it, using intelligent default behaviour or gracefully reducing the functionality. However, sometimes you just have to report something.

    In this case, you can raise an ArgumentException with an appropriate message in the property setter which receives the invalid value. Exceptions that are raised in this way will show up in the VS Error List and keep the project from building.

    Other Best Practices

    • Don’t do complex things like e.g. accessing a database in a constructor. Constructors will run at design time as well as at run time. So, if they need information from the application they are running in, this information might not be accessible at design time. Therefore, one might see strange error messages at design time which are difficult to debug.
    • Don’t rely on a certain order in which the properties of your component should be set. If you need several property values in order to do a job, either don’t access the values until you need to do the job, or check whether you have a complete set of values each time any of the properties changes, and start the job as soon as you have a valid set of values.

    Conclusion

    If you follow these guidelines, your components will behave similar to the components that come with the WPF framework, and they will integrate into the support given by the VS XAML editor.

    There are some other techniques which I haven’t covered here, but which I would like to mention: using dependency properties and using type converters. You can find examples for dependency properties all over this blog, e.g. here: http://wpfglue.wordpress.com/2009/12/16/the-sticky-viewmodel-pattern/ . An example for using type converters is given in this post: http://wpfglue.wordpress.com/2009/11/30/css-class-equivalent-in-wpf/ . Otherwise, you will find the patterns which I discussed here everywhere in the WPFGlue code. A special example project for this post is available for download here:

    (as always on wordpress.com, you will have to rename it to a .zip extension in order to extract it).

    Categories: Patterns
    Follow

    Get every new post delivered to your Inbox.