WPF:如何对通用基础控件进行样式设置

10

在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中的任何内容都不在视觉/逻辑树中)。
我基本上试图采取我的基本视图/视图模型类并应用一致的外观和感觉。当然,这在非泛型基本视图情况下是有效的。然而,我需要视图和视图模型之间的类型安全连接,以便我可以从任何引用视图的地方调用视图模型的方法(我知道这可能不符合某些人实施MVVM的方式)。

删除这个:x:Key="TestStyle" 这样可以吗? - NVM
哎呀,那个不应该在里面的——我在测试一些东西,忘记删除那行了。如果删除那行,它就不能工作了。 - Brad Leach
3个回答

5
我发现一个相当简单的解决方案,涉及自定义的。
1- 将DefaultStyleKey设置为默认通用类型如CodeNaked答案中所述
    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;
        }
    }

第三步 - 将样式的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<T>类上定义的任何依赖属性时,VS会抛出编译错误。因此,您不能使用简单的语法,如{TemplateBinding ViewTProperty}...


1

简短回答:不行

详细回答:

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

您可以将DefaultStyleKey设置为:

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

这将正确地找到样式,但会导致异常(因为TestView无法转换为View<>)。

您最好的选择是像您所做的那样定义基本样式,但给它一个名为“ViewBaseStyle”的x:Key。然后为每个派生类型创建一个基于ViewBaseStyle的样式。


1
然后为每个派生类型创建一个基于ViewBaseStyle的样式。这会导致异常,因为“只能基于目标类型为基本类型'TestView'的样式”。 - Steven Jeuris

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拥有的属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接