views:

415

answers:

2

I am looking into creating type-safe generic controls. This is targeting the (reduced) generics support in WPF 4 and future Silverlight, and will include a hierarchy of generic controls.

I have two questions:

  1. Can you use style setters and template bindings for non-generic properties defined on a generic control?
  2. In Silverlight, is there a value I can use for the default style key in the base class that will allow using the same style in the (temporary) specific-type derived classes? (ComponentResourceKey does not exist in Silverlight, so the setup described below does not work.)


The test generic control below defines two test properties: a non-generic Description property, and a generic Data property. The control sets DefaultStyleKey to a ComponentResourceKey for the control.

Here is how the test control is defined:

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 { ... } }
}

Here is the style for the test control in 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>

Here are some examples of how this test control would be declared in 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}" />


With the current generics support in WPF 4, you cannot use an open generic type as the TargetType of a style or control template (doing so results in a "'GenericControl`1' TargetType does not match type of element 'GenericControl`1'." exception). This has two main consequences, as mentioned in question 1 above:

  • You must use a normal binding with RelativeSource={RelativeSource TemplatedParent} instead of a TemplateBinding in the control template to reference properties defined by the generic control.
  • You cannot create a style setter for the Description property, even though it does not depend on the generic type of the control.

For the latter, there is a workaround in WPF: just define the non-generic properties as attached dependency properties on a proxy type. Then you can use AddOwner to "declare" the properties on the generic control, and you can use "ProxyType.Property" syntax in a style setter. Of course, Silverlight does not support AddOwner, and turning what is supposed to be an instance property into an attached property is not ideal in any case, so this is not really a long-term solution.

Aside: It looks like there is a regression in the xaml parsing behavior for types. Using VS2008, I can use {x:Type local:GenericControl`1} to get the open type of the control, which I used as the example type in the ComponentResourceKey. In VS2010 though, this results in the following error: "Character '`' was unexpected in string 'local:GenericControl`1'. Invalid XAML type name.", so I changed it to use the proxy type instead.

A: 
  1. Yes, you can. But your xaml should be based on non-generic type only. For example we made slider control like this...
SliderInt32 -> BaseRange<T> -> BaseSliderControl

We could only define style either on BaseSliderControl or only SliderInt32, but not on BaseRange. We could specify generic as well as non generic properties in BaseRange class, and they work well in SliderInt32.

  1. Even in silverlight, you can introduce a parent class of generic class which can serve as your type key, like as shown in above example "BaseSliderControl" control style always works as long as child does not override it.

GenericControl`1 etc name is not proper fully qualified name, so it will never work.

Akash Kava
1) I'm afraid the wording of my original question was a bit misleading (now updated). Only the generic control is meant to be used, the "GenericControlInt32" was just for convenience of testing. Defining GenericControlDouble, GenericControlInt32, GenericControlUInt64, etc is not a very practical solution.2) This does not work for larger type hierarchies. For example, given a hierarchy like `Derived<T> -> Intermediate<T> -> Base<T>`, there is no place to put a non-generic type to style properties defined on the intermediate control (other than DerivedInt32 from #1).
Emperor XLII
Also, `GenericControl\`1` is the name of the open type `GenericControl<>` (i.e. `typeof(GenericControl<>).Name == "GenericControl\`1"`).
Emperor XLII
A: 

I posted this same question to the WPF and the Silverlight forums. There was no response on Silverlight, but here is a summary of the answer for WPF:

  • To quote Shreedhar, "XAML doesn't currently support specifying open generic types".
  • Using a closed generic type, such as TargetType="{x:Type local:GenericControl(x:Int32)}", will work for an individual style, but will require copy-and-paste to target the same control for other type parameters.
  • Can create a default template on the fly for any given type argument using XamlReader and some string replacement, but this leaves something to be desired when creating a new style or template outside the control.
Emperor XLII