WPF控件的通用样式设计

3

我正在研究创建类型安全的通用控件。这是针对WPF 4和未来的Silverlight中减少的泛型支持,并将包括一系列的通用控件。

我有两个问题:

  1. 在定义了通用控件的非泛型属性时,是否可以使用样式设置器和模板绑定?
  2. 在Silverlight中,是否有一个默认样式键的值可以在基类中使用,以便在(临时的)特定类型派生类中使用相同的样式?(ComponentResourceKey在Silverlight中不存在,因此下面描述的设置无法使用)

下面的测试通用控件定义了两个测试属性:一个非泛型的 Description 属性和一个泛型的 Data 属性。该控件将 DefaultStyleKey 设置为控件的 ComponentResourceKey
以下是测试控件的定义:
public class GenericControl<T> : Control {
  static GenericControl( ) {
    DefaultStyleKeyProperty.OverrideMetadata(
      typeof(GenericControl<T>), new FrameworkPropertyMetadata(
        new ComponentResourceKey( typeof(Proxy), "GenericControl`1" )
      )
    );
  }

  public static readonly DependencyProperty DescriptionProperty =
    DependencyProperty.Register(
      "Description", typeof(string), typeof(GenericControl<T>),
      new PropertyMetadata( "Default Description" )
    );
  public static readonly DependencyProperty DataProperty =
    DependencyProperty.Register(
      "Data", typeof(T), typeof(GenericControl<T>),
      new PropertyMetadata( default(T) )
    );

  public string Description { get { ... } set { ... } }
  public T Data { get { ... } set { ... } }
}

这是在generic.xaml中测试控件的样式:
<Style x:Key="{ComponentResourceKey {x:Type local:Proxy}, GenericControl`1}">
  <Setter Property="Control.Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Control}">
        <Border Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"">
          <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Description, 
                             RelativeSource={RelativeSource TemplatedParent}}" />
            <TextBlock Text="{Binding Data,
                             RelativeSource={RelativeSource TemplatedParent}}" />
          </StackPanel>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

以下是在XAML中声明此测试控件的示例:
<ListBox Name="list" ... />
<GenericControl x:TypeArguments="sys:Int32" Description="Count: "
               Data="{Binding Items.Count, ElementName=list}" />

<Slider Name="slider" ... />
<GenericControl x:TypeArguments="sys:Double" Description="Slider Value: "
               Data="{Binding Value, ElementName=slider}" />

在WPF 4中,由于当前泛型支持的限制,您不能将开放式泛型类型用作样式或控件模板的TargetType(这样做会导致“'GenericControl`1' TargetType does not match type of element 'GenericControl`1'.”异常)。如上面的问题1所述,这有两个主要后果:

  • 您必须在控件模板中使用普通绑定,并使用RelativeSource={RelativeSource TemplatedParent}引用定义在泛型控件中的属性,而不是使用TemplateBinding
  • 即使Description属性不依赖于控件的泛型类型,您也无法为其创建样式设置器。
对于后者,在WPF中有一个解决方法:只需将非泛型属性定义为代理类型的附加依赖属性即可。然后,您可以使用AddOwner在泛型控件上“声明”属性,并且您可以在样式设置器中使用“ProxyType.Property”语法。当然,Silverlight不支持AddOwner,并且将本应是实例属性的属性转换为附加属性在任何情况下都不是理想的,因此这不是真正的长期解决方案。
另外:看起来在类型的xaml解析行为中存在回归。使用VS2008,我可以使用{x:Type local:GenericControl`1}来获取控件的开放类型,我将其用作ComponentResourceKey中示例类型的示例。但是在VS2010中,这会导致以下错误:“字符串'local:GenericControl`1'中的字符'`'是意外的。无效的XAML类型名称。”,所以我改用代理类型。
2个回答

2
我将同样的问题发布到了WPFSilverlight论坛。在Silverlight上没有回应,但这是关于WPF的答案摘要:
  • 引用Shreedhar的话:“XAML目前不支持指定开放式泛型类型”。
  • 使用封闭式泛型类型,例如TargetType="{x:Type local:GenericControl(x:Int32)}",可以为单个样式工作,但需要复制和粘贴以针对其他类型参数定位相同的控件。
  • 可以使用XamlReader和一些字符串替换动态创建任何给定类型参数的默认模板,但在控件外创建新样式或模板时仍有所欠缺。

1
可以的。但是你的XAML应该只基于非泛型类型。例如,我们像这样制作滑块控件...
SliderInt32 -> BaseRange<T> -> BaseSliderControl

我们只能在BaseSliderControl或者SliderInt32中定义样式,而不能在BaseRange中定义。我们可以在BaseRange类中指定泛型和非泛型属性,在SliderInt32中它们都能正常工作。

  1. 即使在Silverlight中,您也可以引入一个通用类的父类,它可以作为您的类型关键字,就像上面的例子中"BaseSliderControl"控件样式总是起作用,只要子类不覆盖该样式。

GenericControl`1等名称不是完全合格的名称,因此永远不会起作用。


  1. 很抱歉,我原来的问题措辞有些误导(现已更新)。只需要使用通用控件,“GenericControlInt32”只是为了方便测试。定义“GenericControlDouble”,“GenericControlInt32”,“GenericControlUInt64”等并不是一个非常实际的解决方案。
  2. 这对于更大的类型层次结构无效。例如,给定一个层次结构如“Derived<T> -> Intermediate<T> -> Base<T>”,没有地方放置非泛型类型以样式化在中间控件上定义的属性(除了#1中的DerivedInt32)。
- Emperor XLII
此外,GenericControl\1是开放类型GenericControl<>的名称(即typeof(GenericControl<>).Name == "GenericControl`1"`)。 - Emperor XLII

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接