Is it possible to provide a default style for a generic base control in WPF?
Assume I have the following base classes:
public abstract class View<T> : ContentControl
where T : ViewModel
{
static View()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<T>)));
}
// Other properties, methods, etc in here
}
public abstract class ViewModel
{
// Other properties, methods, etc in here
}
Then assume I have a two classes which inherit from these base classes:
public partial class TestView : View<TestViewModel>
{
public TestView()
{
InitializeComponent();
}
// TestView specific methods, properties, etc
}
public class TestViewModel : ViewModel
{ /* TestViewModel specific methods, properties, etc */ }
Now I want to provide a default style for the base control that all my derived controls use:
<Style TargetType="{x:Type local:View`1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:View`1}">
<Border Background="Magenta"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<Button>Test</Button>
<ContentPresenter ContentSource="Content" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
However, when I use my TestView control, I don't have the template markup applied (and thus any content i might define in the XAML of my TestView control is not in the visual/logical tree).
I am basically trying to take my base view/viewmodel classes and apply a consistent look and feel. This of course works in the non-generic base view cases. However, I require the type-safe hook up between view and viewmodel so I can call methods on the viewmodel from anything that has reference to the view (which I know may not "sit well" with the way some people have implemented MVVM).
I found fairly simple solution involving a custom TypeExtension
.
1 - Set the DefaultStyleKey to the default generic type as mentioned in CodeNaked's answer:
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<>)));
2 - Create the following class than inherits from System.Windows.Markup.TypeExtension
[System.Windows.Markup.ContentProperty("Type")]
public class TypeExtension : System.Windows.Markup.TypeExtension
{
public TypeExtension()
: base()
{ }
public TypeExtension(Type type)
: base(type)
{ }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Type == null)
throw new InvalidOperationException("Must specify the Type");
return Type;
}
}
3 - Update the style's TargetType to point to the new local:Type
extension instead of the usual x:Type
extension
<Style>
<Style.TargetType>
<local:Type Type="{x:Type local:View`1}" />
</Style.TargetType>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
. . .
Thats it..
There is a caveat though, VS throws a compile error when you attempt to bind/set any of the dependency properties defined on the View<T> class. So you cannot use simple syntax like {TemplateBinding ViewTProperty}
...
Short answer: no
Long answer:
In your code behind you are specifying a DefaultStyleKey of typeof(View<T>)
, where T is resolved to an actual type. In the XAML, you are effectively doing typeof(Value<>)
, where T is still "undefined".
You can set your DefaultStyleKey to:
DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>),
new FrameworkPropertyMetadata(typeof(View<>)));
This will correctly find the Style, but will result in a exception (as TestView
cannot be case to View<>
).
Your best bet is to define your base Style like you do, but give it an x:Key like "ViewBaseStyle". Then create a Style for each derive type that is BasedOn ViewBaseStyle.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With