65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 5 和 WPF 的静态和类型标记扩展

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2012年2月20日

CPOL

7分钟阅读

viewsIcon

48027

downloadIcon

865

一个为 Silverlight 5 和 WPF 实现的扩展静态标记扩展,支持带参数调用静态方法,以及一个 Silverlight 类型标记扩展实现。

引言

本文介绍了一个 Silverlight 5 的自定义 `Static` 和 `Type` 标记扩展实现,这些扩展在 WPF 中已经可用。`Static` 标记扩展除了访问属性和字段之外,还扩展了对调用带参数的静态方法的支持。它还会进行目标属性类型的转换。

背景

与 WPF 相比,Silverlight 标准标记扩展的数量有限。这意味着像 `MultiBinding`、`x:Static` 和 `x:Type` 这样的标准 WPF 标记扩展在 Silverlight 中不可用。然而,从 5.0 版本开始,Silverlight 支持自定义标记扩展,这使得创建功能相同的扩展成为可能。在上一篇文章中,我展示了如何创建一个扩展的 Silverlight `MultiBinding`,在本文中,我将介绍 Silverlight 的 `Type` 和 `Static` 标记扩展实现。与我的 `MultiBinding` 实现一样,`Static` 标记扩展也比原始 WPF 版本具有更丰富的功能。这个 `Static` 实现除了静态属性和字段之外,还支持带参数调用静态方法。

使用代码

下面的所有代码示例都假定您已将 `SilverlightMarkupExtension` 程序集引用添加到项目中,并在 XAML 文件中为 `SilverlightMarkupExtensions` 命名空间分配了 `z` 命名空间前缀。

静态标记扩展

在 XAML 中赋值属性时,可以使用 `Static` 标记扩展来引用静态成员。WPF 的 `x:Static` 扩展支持公共静态属性和字段。一个典型的用法是引用类型化资源文件中的资源键以本地化内容

<TextBlock Text={x:Static demo:Resources.Title} />

其中 `demo:Resources` 是对类型化资源文件的引用,`Title` 是资源属性的名称。Silverlight 的 `Static` 实现几乎以相同的方式支持这一点

<TextBlock Text={z:Static Member=demo:Resources.Title} /> 

由于 Silverlight 不支持位置参数,我们必须在声明中指定 `Member` 属性。与原始 `x:Static` 扩展一样,您也可以像这样分离类型和成员

<TextBlock Text={z:Static MemberType=demo:Resources, Member=Title} />

如果您想反复引用同一类型中的成员(例如资源),此 `Static` 标记扩展还支持在根 XAML 对象(`UserControl`)上一次性指定成员类型,方法是使用 `Static.DefaultMemberType` 附加属性。

<UserControl … z:Static.DefaultMemberType=”demo:Resources” >
…
<TextBlock Text={z:Static Member=Title /> 

调用静态方法

除了支持访问静态属性和字段(类似于 WPF 的 `x:Static` 扩展)之外,Silverlight 的 `Static` 标记扩展还可以像这样简单地调用静态方法

<TextBlock Text="{z:Static MemberType=demo:MyModelView, Member='GetSum(7,6.3)'}" />

在此示例中,我们显示了调用接受两个参数的公共静态方法 `MyModelView.GetSum` 的结果。在成员名称后使用逗号分隔的参数列表即可调用静态方法。支持使用 `IConvertible` 接口对标准类型进行基本类型转换。要调用不带参数的方法,只需指定一对空括号。

`Static` 扩展还支持将参数指定为单独的属性,如下所示

<ContentControl Content="{z:Static Member=demo:MyModelView.GetSum, Arg1={z:Static Member='demo:MyModelView.GetSum(6,7)'},Arg2=8}" />

如上所示,我们可以使用此方法将标记扩展的结果作为参数传递。此处显示的结果将是 21 (6+7+8)。目前最多支持三个参数,并且可以轻松地增加对更多参数的支持。

类型标记扩展

在 WPF 中,`x:Type` 标记扩展可用于在 XAML 中为属性赋值时引用命名类型,但在 Silverlight 标准框架中不包含此标记扩展。另一方面,Silverlight 5 在为 `System.Type` 类型属性赋值时支持命名类型,因此在大多数情况下不需要 `Type` 标记扩展。仅当要设置为类型的属性不是 `System.Type` 类型,而是其父类型 `System.Object` 类型时才需要。这里的 Silverlight 实现与 WPF 的对应项 `Type` 和 `TypeName` 具有相同的属性——同一声明中只能使用其中一个。前者是 `System.Type` 类型,可以分配一个命名类型,前面可选地带有命名空间前缀。这是指定类型的首选方式,因为它在设计时进行了检查。相比之下,`TypeName` 是一个 `String` 属性,仅在运行时进行评估,并且必须设置为带有适当命名空间前缀的类型名称。`TypeName` 在某些罕见情况下可能很有用,例如当类型名称指定为 XAML 资源并通过 `StaticResource` 标记扩展引用时,或者其他标记扩展提供了类型名称,例如 `Static`。

在下面的示例中,我们使用 `Type` 标记扩展将类型引用作为参数传递给一个方法,该方法直接在 XAML 中使用 `Static` 标记扩展调用

<ComboBox ItemsSource="{z:Static Member=demo:Sex.GetValues, Arg1={z:Type Type=demo:Sex}}" 
          SelectedItem="{z:Static Member=demo:Sex.Unknown}" />

`Sex` 在这里是一个枚举类型。我们调用 `Enum.GetValues` 静态方法,并传递一个参数来指定返回值的类型。结果将是一个包含枚举值的组合框。我们还使用 `Static` 在定义初始选定项时引用一个特定的枚举值 `Unknown`。

在 WPF 中使用

`Static` 和 `Type` 标记扩展都可以在 WPF XAML 代码中使用,这使得可以直接将为 Silverlight 编写的 XAML 迁移到 WPF。新的 `Static` 实现还可以独立用于 WPF XAML,因为它具有调用方法的扩展能力。与内置的 `x:Static` 相比的另一个优势是 Silverlight 的实现尝试在返回结果之前将其转换为属性类型。这在您有一个定义为 `String` 类型的资源文件项但想将其应用于数字属性的情况下非常有用。在下面的示例中,我们有一个资源 `TitleWidth`,它必须是 `String` 类型,被赋值为 `"100"`,并尝试将其用于数字属性 `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}" />

使用内置的 `x:Static` 标记扩展(如第一行所示)将导致运行时错误,消息为“Set property 'System.Windows.FrameworkElement.Width' threw an exception. '100' is not a valid value for property 'Width'”。只需将其替换为我的 `z:Static` 标记扩展即可解决此问题,因为它支持自动类型转换。

有限的设计时支持

不幸的是,在使用 `Static` 和 `Type` 标记扩展时,结果不会直接显示在 Visual Studio Silverlight 设计器中,因为自定义标记扩展的 `ProvideValue` 在 Silverlight 设计模式下不会被调用。然而,这与 WPF 设计器中调用 `ProvideValue` 的行为不一致。无论如何,`Static` 和 `Type` 扩展使用的服务在 WPF 和 Silverlight 设计模式下都不可用,这使得在大多数情况下无法在设计时解析值。

有些人利用了(奇怪的?)事实,即自定义标记扩展的 `ToString()` 在 Silverlight 设计模式下会被调用,以提供设计时值。然而,这似乎仅在 `ContentPresenter` 上下文中才成立,因此不适用于所有属性。

实现细节

两个标记扩展都使用通过传递给 `ProvideValue` 方法的服务提供者提供的标准服务。为了解析类型名称(可能带有命名空间前缀),使用了 `IXamlTypeResolver` 服务。下面显示了 `Type` 的 `ProvideValue` 方法的完整实现

public override Object ProvideValue(IServiceProvider serviceProvider) 
{ 
   if (Type == null) 
   { 
      // Excluded some parameter checking
   
      IXamlTypeResolver resolver = 
        serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver; 
      if (resolver == null) DependencyProperty.UnsetValue; 
      Type = resolver.Resolve(TypeName); 
   } 
   return Type; 
}

`Static` 标记扩展还使用 `IXamlTypeResolver` 服务来解析 `Member` 属性中指定的类型名称。此外,它还使用 `IProvideValueTarget` 服务来确定目标属性类型。通过这种方式,`Static` 尝试将从调用的静态方法返回的值转换为正确的类型

// Try to convert return value to target property 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 // In WPF a DependencyProperty is used for dependency properties. 
        { 
            DependencyProperty propertyMetaData = pvt.TargetProperty as  DependencyProperty; 
            if (propertyMetaData != null) 
            { 
                returnValue = ConvertToType(propertyMetaData.PropertyType, 
                                            returnValue, Thread.CurrentThread.CurrentUICulture); 
            } 
        } 
#endif 
    } 
}

最后,使用 `IRootObjectProvider` 服务来查找设置在 XAML 根对象(通常是 `UserControl`)上的 `DefaultMemberType` 附加属性

// Look for Static.DefaultMemberType set on root object. 
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); 
        } 
    } 
}

结论

这项工作展示了如何使用类似于 WPF 中基本标记扩展的标记扩展来扩展 Silverlight 5,以及如何使用可用的各种服务。结合之前介绍的 `MultiBinding` 实现,我们开始构建一个包含有用 Silverlight 标记扩展的小型实用库。调用静态方法可能很有用,但我们真正想要的是能够调用实例方法。因此,我开始基于 `MultiBinding` 创建一个 `MethodBinding` 标记扩展。这将是我下一篇文章的主题,但如果您好奇,可以提前在此文章提供的源代码中预览。

历史

  • 2012 年 2 月 19 日 – 使用 Visual Studio 2010 SP1 和 Silverlight 5 开发并测试的初始版本。
© . All rights reserved.