Introduction
This article presents a Silverlight 5 custom implementation of the Static
and Type
markup extension, already available in WPF. The Static
markup extension is extended with support for calling static methods with parameters, in addition to accessing properties and fields. It also performs conversion to the target property type.
Background
Silverlight has a limited set of standard markup extensions compared to WPF. This means that standard WPF markup extensions like MultiBinding
, x:Static
,
and x:Type
are not available in Silverlight. However, starting with version 5 Silverlight supports custom markup extensions, which makes it possible to create extensions that can perform the same job. In a previous article, I showed how an extended Silverlight MultiBinding
can be created, and in this article I present a Type
and Static
markup extension implementation for Silverlight. Just like my MultiBinding
implementation, the Static
markup extension also has extended functionality compared to the original WPF version. This Static
implementation supports calling static methods with arguments, in addition to static properties and fields.
Using the code
All code examples below assume that you have included a reference to the SilverlightMarkupExtension
assembly in the project and assigned the namespace prefix z
to the SilverlightMarkupExtensions
namespace in the XAML files.
Static markup extension
The Static
markup extension can be used to refer to static members when assigning properties in XAML. The WPF x:Static
extension supports public static properties and fields. One typical use is to refer to resources keys in typed resource files to localize content:
<TextBlock Text={x:Static demo:Resources.Title} />
where demo:Resources
is a reference to the typed resource file and Title
is the name of the resource property. The Silverlight Static
implementation supports this in almost the same way:
<TextBlock Text={z:Static Member=demo:Resources.Title} />
Since Silverlight does not support positional arguments we have to specify the Member
property in the declaration. In the same way as the original x:Static
extension, you can also separate the type and the member like this:
<TextBlock Text={z:Static MemberType=demo:Resources, Member=Title} />
If you repeatedly want to refer to members in the same type, e.g., resources, this Static
markup extension also supports specifying the member type once on the root XAML object (UserControl
) by using the Static.DefaultMemberType
attached property.
<UserControl … z:Static.DefaultMemberType="demo:Resources" >
…
<TextBlock Text={z:Static Member=Title />
Invoking static methods
In addition to support for accessing static properties and fields, like the WPF x:Static
extension, the Silverlight
Static
markup extension supports invoking static methods as simple as this:
<TextBlock Text="{z:Static MemberType=demo:MyModelView, Member='GetSum(7,6.3)'}" />
In this example we are displaying the result of calling the public static MyModelView.GetSum
method accepting two parameters. Using a comma-separated parameter list after the member name is all that is needed to invoke the static method. Basic type conversion of standard types using the IConvertible
interface is supported. To invoke methods without parameters, just specify an empty pair of parentheses.
The Static
extension also supports specifying arguments as separate properties, like this:
<ContentControl Content="{z:Static Member=demo:MyModelView.GetSum, Arg1={z:Static Member='demo:MyModelView.GetSum(6,7)'},Arg2=8}" />
As demonstrated above we can use this to pass in the results of markup extensions as arguments. The result shown in this case will be 21 (6+7+8). Up to three arguments is currently supported, and it is straightforward to add support for more arguments.
Type markup extension
In WPF the x:Type
markup extension can be used to refer to named types when assigning properties in XAML, but this markup extension is not included in the Silverlight standard framework. On the other hand Silverlight 5 has built-in support for named types when assigning properties of type System.Type
, so in most cases a Type
markup extension is not required. It is only required when the property to be set to a type is not of type System.Type
, but of its parent System.Object
type. The Silverlight implementation here has the same properties as the WPF counterpart, Type
and TypeName
– only one of them can be used on the same declaration. The former property is of type System.Type
and can be assigned a named type with an optional namespace prefix before. This is the preferred way of specifying the type since this is checked at design-time. In contrast, TypeName
is a String
property which is evaluated only in run-time, and must be set to a name of a type with the appropriate namespace prefix.
TypeName
can be useful in rare cases where the type name is specified as a XAML resource and referred to using a StaticResource
markup extension, or some other markup extension provides the type name, for instance Static
.
In the following example, we use the Type
markup extension to pass in a type reference as an argument to a method to be invoked directly in XAML using the Static
markup extension:
<ComboBox ItemsSource="{z:Static Member=demo:Sex.GetValues, Arg1={z:Type Type=demo:Sex}}"
SelectedItem="{z:Static Member=demo:Sex.Unknown}" />
Sex
here is an enum type. We are invoking the Enum.GetValues
static method with an argument specifying the type to return the values for. The result will be a combo box populated with the enum values. We also use Static
to refer to a specific enum value, Unknown
, when defining the initial selected item.
Use in WPF
Both the Static
and Type
markup extensions can be used in WPF XAML code, which makes it possible to transfer XAML written for Silverlight to WPF directly. The new Static
implementation can also be useful in WPF XAML on its own thanks to its extended ability to invoke methods. Another advantage compared to the built-in x:Static
is that the implementation for Silverlight tries to convert the result to the property type before returning it. This can be useful in situations where you have
a resource file item defined as a String
type but want to apply it to a numeric property. In the example below we have a resource TitleWidth
, which must be
String
, assigned to the value "<code>100"
and tries to use it for the numeric property Width
:
<ContentPresenter Content="{x:Static demo:Resources.Title}" Width="{x:Static demo:Resources.TitleWidth}" />
<ContentPresenter Content="{z:Static Member=demo:Resources.Title}" Width="{z:Static Member=demo:Resources.TitleWidth}" />
Using the built-in x:Static
markup extension as in the first line will result in
a run-time error saying “Set property 'System.Windows.FrameworkElement.Width' threw an exception. '100' is not a valid value for property 'Width'”. Simply replacing it with my z:Static
markup extension will make it work, thanks to its automatic type conversion.
Limited design-time support
Unfortunately, when using the Static
and Type
markup extensions, the result will not be directly visible in the Visual Studio Silverlight designer, because the
ProvideValue
of a custom markup extension is not called in Silverlight design-mode. However, this is inconsistent with the behavior in the WPF designer where the
ProvideValue
is actually called. Anyhow, the services used by the
Static
and Type
extension are not available in either WPF or Silverlight design-mode, making it impossible to resolve the values during design-time in most cases.
Some have utilized the (strange?) fact that the ToString()
of the custom markup extension is called in the Silverlight design-mode to provide a design-time value. However, this seems to be true only in a
ContentPresenter
context and therefore not generally applicable to all properties.
Implementation details
Both markup extensions use the standard services available through the service provider passed in to the ProvideValue
method. To resolve type names, possible with namespace prefixes, the IXamlTypeResolver
service is used. The full implementation of Type
’s ProvideValue
method is shown below:
public override Object ProvideValue(IServiceProvider serviceProvider)
{
if (Type == null)
{
IXamlTypeResolver resolver =
serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
if (resolver == null) DependencyProperty.UnsetValue;
Type = resolver.Resolve(TypeName);
}
return Type;
}
The Static
markup extension also uses the IXamlTypeResolver
service to resolve type names specified in the Member
property. In addition it uses the IProvideValueTarget
service to determine the target property type. In this way Static
tries to convert the value returned from the invoked static method to the correct type:
if (serviceProvider != null)
{
IProvideValueTarget pvt =
serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (pvt != null)
{
PropertyInfo targetProperty = pvt.TargetProperty as PropertyInfo;
if (targetProperty != null)
{
returnValue = ConvertToType(targetProperty.PropertyType, returnValue,
Thread.CurrentThread.CurrentUICulture);
}
#if !SILVERLIGHT
else
{
DependencyProperty propertyMetaData = pvt.TargetProperty as DependencyProperty;
if (propertyMetaData != null)
{
returnValue = ConvertToType(propertyMetaData.PropertyType,
returnValue, Thread.CurrentThread.CurrentUICulture);
}
}
#endif
}
}
Finally, the IRootObjectProvider
service is used to look for DefaultMemberType
attached properties set on the XAML root object, usually a UserControl
:
if (serviceProvider != null)
{
IRootObjectProvider rop =
serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
if (rop != null)
{
DependencyObject rootObject = rop.RootObject as DependencyObject;
if (rootObject != null)
{
memberType = GetDefaultMemberType(rootObject);
}
}
}
Conclusions
This work has shown how to extend Silverlight 5 with markup extensions similar to the basic ones in WPF, and how to use the various services available. Together with the MultiBinding
implementation presented before we are starting to get
a small utility library with useful Silverlight markup extensions. Invoking static methods can be useful, but what we really want to have is the ability to invoke instance methods as well. Therefore I have started to create a MethodBinding
markup extension based on MultiBinding
. This will be the topic of my next article, but if you are curious you can already now have a preview of the work in the source code provided with this article.
History
- February 19, 2012 – Initial version developed and tested with Silverlight 5 with Visual Studio 2010 SP1.