Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: Styling a Generic Base Control

Tags:

wpf

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).

like image 832
Brad Leach Avatar asked Mar 23 '11 21:03

Brad Leach


2 Answers

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

like image 98
cwills Avatar answered Oct 16 '22 18:10

cwills


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.

like image 42
CodeNaked Avatar answered Oct 16 '22 16:10

CodeNaked