WPF:设置通用基本控件的样式

时间:2011-03-23 21:40:07

标签: wpf

是否可以在WPF中为通用基本控件提供默认样式?

假设我有以下基类:

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
}

然后假设我有两个继承自这些基类的类:

public partial class TestView : View<TestViewModel>
{
    public TestView()
    {
        InitializeComponent();
    }

    // TestView specific methods, properties, etc
}

public class TestViewModel : ViewModel
{ /* TestViewModel specific methods, properties, etc */ }

现在我想为所有派生控件使用的基本控件提供默认样式:

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

但是,当我使用TestView控件时,我没有应用模板标记(因此我在TestView控件的XAML中定义的任何内容都不在visual / logical树中)。

我基本上尝试使用我的基本视图/ viewmodel类并应用一致的外观。这当然适用于非通用基本视图案例。但是,我需要在视图和视图模型之间进行类型安全连接,这样我就可以从视图中调用视图模型上的方法(我知道这些方法可能与某些人已经实现MVVM的方式“坐得不好”)。

3 个答案:

答案 0 :(得分:5)

我找到了一个涉及自定义TypeExtension的相当简单的解决方案。

1 - 将DefaultStyleKey设置为默认泛型类型as mentioned in CodeNaked's answer

    DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>), 
        new FrameworkPropertyMetadata(typeof(View<>)));

2 - 创建以下类而不是继承自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 - 更新样式的TargetType以指向新的local:Type扩展名,而不是通常的x:Type扩展名


    <Style>
        <Style.TargetType>
            <local:Type Type="{x:Type local:View`1}" />
        </Style.TargetType>
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Control}">

        . . .

多数民众赞成...... 但是有一点需要注意,当您尝试绑定/设置View&lt; T&gt;上定义的任何依赖项属性时,VS会抛出编译错误。类。所以你不能使用像{TemplateBinding ViewTProperty} ...

这样的简单语法

答案 1 :(得分:1)

简答:没有

答案很长:

在您的代码中,您指定了typeof(View<T>)的DefaultStyleKey,其中T被解析为实际类型。在XAML中,您实际上正在typeof(Value<>),其中T仍然是“未定义”。

您可以将DefaultStyleKey设置为:

DefaultStyleKeyProperty.OverrideMetadata(typeof(View<T>), 
        new FrameworkPropertyMetadata(typeof(View<>)));

这将正确找到样式,但会导致异常(因为TestView不能出现View<>)。

最好的办法是像你一样定义基本样式,但是给它一个x:Key,如“ViewBaseStyle”。然后为每个派生类型创建一个Style,它是BasedOn ViewBaseStyle。

答案 2 :(得分:0)

我这样做的方式是我创建了一个没有泛型的基类并模板化了。然后我继承了一个泛型类(不是模板化)的基类,它可以用来制作你的类变体(也没有模板化)。实际上,继承基类(没有泛型)的所有内容都将具有相同的模板。

例如,

//You can define a template for this!
public class ViewBase : UserControl
{
    public ViewBase() : base()
    {
        DefaultStyleKey = typeof(ViewBase);
    }
}

//Use for class variations
public class View<T> : ViewBase
{
    public View() : base()
    {
        //Do whatever
    }
}

//Example class variation
public class DecimalView : View<decimal>
{
    public DecimalView() : base()
    {
        //Do whatever
    }
}

ViewBaseView<T>DecimalView现在都拥有相同的默认样式。此外,您还可以根据原始样式(ViewBase)为每个类变体指定单独的样式,而不是针对泛型类。

值得注意的是,绑定到顶级类属性的最佳方法是使用语法{Binding Path, RelativeSource={RelativeSource AncestorType={x:Type ViewBase}}}{TemplateBinding Path}。后者以及{Binding Path, RelativeSource={RelativeSource TemplatedParent}}仅适用于ViewBase拥有的媒体资源。