Search Wiki:


This example show how to implement XAMl Markup Extension that has similar functionality as Markup Extension. x:Static Markup Extension simply returns value of static field or property of of given type with given name like {x:Static prefix:TheType.TheMember} returns value of static field or property named TheMember form type TheType in namespace and assembly specified by prefix prefix.

StaticEx has the same behavior as x:Static but it is extended with possibility to access fields and properties of given value returned by x:Static. Simply specify {my:StaticEx prefix:TheType.TheMember.TheSubMember.TheSubSubMember}.


Motivation for creating this extension was possibility to pass values from application settings (My.Settings in Visual Basic) to {Binding ConverterParameter}. Which is not possible AFAIK. You can pass there any anywhere else values from My.Resources using {x:Static} because My.Resources are implemented as class with plenty of static (Shared in Visual Basic) properties. Unlike Resources, Settings are implemented as class MySettings with single static property Default providing instance properties for each setting. And unfortunately x:Static can access only top-level properties. It cannot acess properties of properties. This is not problem at places where target of assignement is dependency property - one can use something like {Binding Source={x:Static my:MySettings.Default}, Path=TheSetting}. Using {Binding Path=SomeThing Converter={StaticResource SomeConverter} ConverterParameter={Binding Source={x:Static my:MySettings.Default}, Path=TheSetting}} is unfortunately not possible because BindingExtension.ConverterParameter is not a dependency property which is required to be for being target of assignment of {Binding}.


Implementation is quite straightforward:
  • Create a class which inherits from MarkupExtension
  • Add CTors and properties
  • Implement the ProvideValue method
    • Get IXamlTypeResolver from serviceProvider
    • Use IXamlTypeResolver to get type from 1st part of string
    • Use reflection to find public static property on the type with name from 2nd part of string
    • Get value of the property
    • For 3rd and all subsequent parts:
      • Get type of value returned by previous part and on that type find public instance property with name provided by appropriate part of string
      • Get value of that property
    • Finally return value got for last part

Imports System.Windows.Markup
Imports System.ComponentModel, System.ComponentModel.Design.Serialization
Imports System.Reflection
''' <summary>Implements markup extension to acces static properties and fields and their members</summary>
<MarkupExtensionReturnType(GetType(Object))> _
<TypeConverter(GetType(StaticExExtension.StaticExTypeConverter))> _
Public Class StaticExExtension
    Inherits MarkupExtension
    ''' <summary>Contains value of the <see cref="Member"/> property</summary>
    Private _Member As String
    ''' <summary>Default contstructor</summary>
    ''' <remarks>This constructor is intended to be used by XAML only. Use parametrized constructor overload instead.</remarks>
    <EditorBrowsable(EditorBrowsableState.Advanced)> _
    Public Sub New()
    End Sub
    ''' <summary>CTor with member</summary>
    ''' <param name="Member">Name and path of member this extension provides value of. See <see cref="Member"/> for more information.</param>
    ''' <exception cref="ArgumentNullException"><paramref name="Member"/> is null.</exception>
    Public Sub New(ByVal Member As String)
        If Member Is Nothing Then Throw New ArgumentNullException("Member")
        Me.Member = Member
    End Sub
    ''' <summary>Gets or sets name and path of member this extension provides value of</summary>
    ''' <value>Name and path of member this extension provides value of</value>
    ''' <remarks>
    ''' This property shall be set to string containg several (at least 2) dot(.)-separated substring. (No leading and terminating dots!)
    ''' First part is interpreted as name of type and is resolved via <see cref="IXamlTypeResolver.Resolve"/> (so it can contain XML namestace prefix).
    ''' Second part must me name of public static property or field of type specified in first part.
    ''' Thirt and all subsequent parts must be names of public instance fields of type returned by field or property from preceding part (type descriptors are not utilized).
    ''' If any member is not found or (with exceptio of last) returns null, an exception is thrown.
    ''' </remarks>
    <ConstructorArgument("Member")> _
    Public Property Member() As String
            Return _Member
        End Get
        Set(ByVal value As String)
            If value Is Nothing Then Throw New ArgumentNullException("value")
            _Member = value
        End Set
    End Property
    ''' <summary>Returns an object that is set as the value of the target property for this markup extension.</summary>
    ''' <returns>The object value to set on the property where the extension is applied.</returns>
    ''' <param name="serviceProvider">Object that can provide services for the markup extension.</param>
    ''' <exception cref="InvalidOperationException"><see cref="Member"/> is null, an empty string or contains fewer than 2 dot-separated parts.</exception>
    ''' <exception cref="ArgumentNullException"><paramref name="serviceProvider"/> is null.</exception>
    ''' <exception cref="ArgumentException"><paramref name="serviceProvider"/> does not provide service of type <see cref="IXamlTypeResolver"/>.</exception>
    Public Overrides Function ProvideValue(ByVal serviceProvider As System.IServiceProvider) As Object
        If Member Is Nothing Then Throw New InvalidOperationException(String.Format("{0} is null", "Member"))
        If serviceProvider Is Nothing Then Throw New ArgumentNullException("serviceProvider")
        'Dim ValueTarget As IProvideValueTarget = serviceProvider.GetService(GetType(IProvideValueTarget))
        Dim Resolver As IXamlTypeResolver = serviceProvider.GetService(GetType(IXamlTypeResolver))
        If Resolver Is Nothing Then Throw New ArgumentException(String.Format("Service provider doesn't provide {0}", GetType(IXamlTypeResolver).Name))
        'Get parts of path
        Dim parts = Member.Split(".")
        If parts Is Nothing OrElse parts.Length = 0 Then Throw New InvalidOperationException(String.Format("{0} cannot be an empty string.", "Member"))
        'Resolve type name
        Dim type = Resolver.Resolve(parts(0))
        If parts.Count = 1 Then Throw New InvalidOperationException(String.Format("{0} does not contain member name part.", "Member"))
        Dim CurrProp As Reflection.MemberInfo
        'Get static property or field of type type
        CurrProp = type.GetProperty(parts(1), Reflection.BindingFlags.Public Or Reflection.BindingFlags.Static)
        If CurrProp Is Nothing Then CurrProp = type.GetField(parts(1), Reflection.BindingFlags.Public Or Reflection.BindingFlags.Static)
        If CurrProp Is Nothing Then Throw New MissingMemberException(type.FullName, parts(1))
        Dim CurrVal As Object = Nothing
        'Get value of the property or fileld
        If TypeOf CurrProp Is PropertyInfo Then
            CurrVal = DirectCast(CurrProp, PropertyInfo).GetValue(Nothing, Nothing)
        ElseIf TypeOf CurrProp Is FieldInfo Then
            CurrVal = DirectCast(CurrProp, FieldInfo).GetValue(Nothing)
        End If
        'Walk remaining properties or fields
        For i As Integer = 2 To parts.Length - 1
            If CurrVal Is Nothing Then Throw New NullReferenceException(String.Format("Member {0} has value null.", parts(i - 1)))
            'Get instance property or field
            CurrProp = CurrVal.GetType.GetProperty(parts(i), Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance)
            If CurrProp Is Nothing Then CurrProp = CurrVal.GetType.GetField(parts(i), BindingFlags.Public Or BindingFlags.Instance)
            If CurrProp Is Nothing Then Throw New MissingMemberException(CurrVal.GetType.FullName, parts(i))
            'Get it's value
            If TypeOf CurrProp Is PropertyInfo Then
                CurrVal = DirectCast(CurrProp, PropertyInfo).GetValue(CurrVal, Nothing)
            ElseIf TypeOf CurrProp Is FieldInfo Then
                CurrVal = DirectCast(CurrProp, FieldInfo).GetValue(CurrVal)
            End If
        'If ValueTarget IsNot Nothing AndAlso ValueTarget.TargetProperty IsNot Nothing AndAlso TypeOf ValueTarget.TargetProperty Is Reflection.PropertyInfo Then
        '    Return Tools.TypeTools.DynamicCast(CurrVal, DirectCast(ValueTarget.TargetProperty, Reflection.PropertyInfo).PropertyType)
        'End If
        Return CurrVal
    End Function
    ''' <summary>Converter for type <see cref="MarkupExtension"/> and <see cref="InstanceDescriptor"/></summary>
    Friend Class StaticExTypeConverter
        Inherits TypeConverter
        ''' <summary>Returns whether this converter can convert the object to the specified type, using the specified context.</summary>
        ''' <returns>true if this converter can perform the conversion; otherwise, false. This implementation returns true if <paramref name="destinationType"/> is <see cref="InstanceDescriptor"/>; otherwise calls <see cref="TypeConverter.CanConvertTo"/>.</returns>
        ''' <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext" /> that provides a format context.</param>
        ''' <param name="destinationType">A <see cref="T:System.Type" /> that represents the type you want to convert to.</param>
        Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
            Return destinationType.Equals(GetType(InstanceDescriptor)) OrElse MyBase.CanConvertTo(context, destinationType)
        End Function
        ''' <summary>Converts the given value object to the specified type, using the specified context and culture information.</summary>
        ''' <returns>An <see cref="T:System.Object" /> that represents the converted value. If <paramref name="destinationType"/> is <see cref="InstanceDescriptor"/> and <paramref name="value"/> is <see cref="StaticExExtension"/> this implementation returns <see cref="InstanceDescriptor"/>.</returns>
        ''' <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext" /> that provides a format context.</param>
        ''' <param name="culture">A <see cref="T:System.Globalization.CultureInfo" />. If null is passed, the current culture is assumed.</param>
        ''' <param name="value">The <see cref="T:System.Object" /> to convert.</param>
        ''' <param name="destinationType">The <see cref="T:System.Type" /> to convert the <paramref name="value" /> parameter to.</param>
        ''' <exception cref="T:System.ArgumentNullException">The <paramref name="destinationType" /> parameter is null.</exception>
        ''' <exception cref="T:System.NotSupportedException">The conversion cannot be performed.</exception>
        ''' <remarks>This implementation performs conversion from <see cref="StaticExExtension"/> to <see cref="InstanceDescriptor"/>; otherwise calls <see cref="TypeConverter.ConvertTo"/>.</remarks>
        Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
            If destinationType.Equals(GetType(InstanceCreationEditor)) AndAlso TypeOf value Is StaticExExtension Then
                Return New InstanceDescriptor(Me.GetType.GetConstructor(BindingFlags.Public Or BindingFlags.Instance, Nothing, New Type() {GetType(String)}, New ParameterModifier() {}), New Object() {DirectCast(value, StaticExExtension).Member})
                Return MyBase.ConvertTo(context, culture, value, destinationType)
            End If
        End Function
    End Class
End Class


First one must declare saome XML prefixes and then usage is same os of x:Static:
<Window x:Class="Window1"
    Title="Window1" Height="300" Width="437">
        <my:AppendConverter x:Key="AppendConverter"/>
        <Label Height="28" Margin="30,40,0,0" Name="Label1" VerticalAlignment="Top" Content="{my:StaticEx my:MySettings.Default.Setting}" HorizontalAlignment="Left" Width="119" />
        <TextBox HorizontalAlignment="Left" Margin="56,110,0,116" Name="TextBox1" Width="138">Type something</TextBox>
        <Label Margin="30,0,43,61" Name="Label2" Content="{Binding ElementName=TextBox1, Path=Text, Converter={StaticResource AppendConverter}, ConverterParameter={my:StaticEx my:MySettings.Default.Appendix}, Mode=OneWay}" Height="49" VerticalAlignment="Bottom" />


In future this extension will be part of the ĐTools] project as every small but useful class I've ever created.
Last edited Sep 12 2009 at 4:48 PM  by Dzonny, version 7
Page view tracker