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:
- Can you use style setters and template bindings for non-generic properties defined on a generic control?
- 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 aTemplateBinding
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.