WPF 样式:对样式如何应用于组件的全面描述

阿戈斯蒂诺

警告:这不是问题,而是 WPF 样式工作的回顾。问题是这个总结是否正确。

我在样式定义中读到,如果在属性名称中包含控件的类名称,则可以去掉 TargetType。就是这样:

<Style x:Key="SomeKey" TargetType="{x:Type Button}">
        <Setter Property="Foreground" Value="Red"/>
</Style>

变成这样:

<Style x:Key="SomeKey">
        <Setter Property="Button.Foreground" Value="Red"/>
</Style>

哦,太好了,这意味着,给定三个控件:

<StackPanel Height="40" Orientation="Horizontal">
    <Button Style="{StaticResource MyStyle}" Content="First"/>
    <TextBox Style="{StaticResource MyStyle}" Text="Second"/>
    <Label Style="{StaticResource MyStyle}" Content="Third"/>
</StackPanel>

我可以做这样的事情:

<Window.Resources>
    <Style x:Key="MyStyle">

        <Setter Property="Button.Foreground" Value="Red"/>
        <Setter Property="TextBox.BorderBrush" Value="DarkBlue"/>
        <Setter Property="Label.Background" Value="LightPink"/>

        <Setter Property="Control.Margin" Value="4,0,0,0"/> 

    </Style>
</Window.Resources>

也就是说:第一个按钮应该是标准的,但带有红色文字;第二个应该是标准的,但带有深蓝色边框。第三个应该是 LightPink 前景。
这是我得到的:

在此处输入图片说明

也就是说:除了第三个是标签并且它的 BorderThickness 默认为 0 之外,每个样式都适用于每个控件。

经过一番挖掘,在我看来,应用样式的所有机制都归结为以下内容。由于它似乎很基本,我想知道这个描述是否正确,或者我是否遗漏了一些重要的东西。

第一阶段:定义风格

1.a 如果设置了 x:Key ( <Style x:Key="MyStyle">)
1.b 如果未设置 x:Key,则必须设置 TargetType。( <Style TargetType="{x:Type Button}">). 在内部,它为样式分配一个键,该键是 TargetType
1.c 中指定的类型名称。如果设置了 TargetType 并且使用语法定义了一个或多个 Setter <Setter Property="Class.Property" Value=.../>,则不会检查 TargetType 和 Classes 值之间的一致性在二传手。也就是说,这是合法的:

<Style x:Key="MyStyle" TargetType="{x:Type Button}">
    <Setter Property="Control.Margin" Value="4,0,0,0"/>
</Style> 

即使它没有什么意义,而以下内容(至少)在一种情况下可能有用。

<Style x:Key="MyStyle" TargetType="{x:Type Control}">
    <Setter Property="TextBox.Text" Value="just an example"/>
</Style> 

这几乎是有关样式定义的所有内容。

阶段 2:将样式与控件相关联

2.a 控件是否定义了样式 ( <Button Style="{StaticResource MyStyle}">)?如果有这样的样式,请在资源中查找。如果有,检查它是否也有一个 TargetType;如果控件不是子类的那个类,则引发异常:

XamlParse 异常。例外:找不到名为“MyStyle”的资源。资源名称区分大小写

因此,在这种情况下 TargetType 不用于过滤。它只需要匹配。

2.b 如果控件没有定义样式,则在资源中查找键值等于控件类名的样式这来自仅使用 TargetType 定义的样式。如果找到,继续应用阶段。
请注意,不会应用使用控件超类的 TargetType 定义的样式。他们必须完全匹配。这是样式系统的另一个“限制”,它与 CSS 的复杂性相去甚远;WPF 样式似乎只支持字典锁定。

阶段 3:应用样式

此时,控件具有要应用的样式。如果样式定义了 TargetType,则它与控件匹配(控件属于该类型或应用的样式中定义的类型的子类型)。样式是一组 Setter,它们可能有也可能没有自己的目标控件规范(带:<Setter Property="Control.Foreground" Value="Red"/>;不带:)<Setter Property="Foreground" Value="Red"/>
似乎(我之前的例子似乎证明了这一点)通过设计setter 级别的类规范并不意味着进一步提炼或过滤是为了什么?好问题,我稍后会回到它。
那它是如何工作的呢?它只是忽略类信息(如果存在),并继续尝试将每个 setter 应用于控件。这就是为什么即使我像这样定义了一个 Setter:

<Setter Property="Label.Background" Value="LightPink"/>

所有三个控件都获得了 LightPink 背景,而不仅仅是 Label。

这就是这篇文章的原因。我找不到关于样式如何真正起作用的完整解释。我能找到的所有信息都仅限于展示一些基本用法;也就是说,他们没有向您展示如何处理需要更详细知识的复杂解决方案。

最后,如果成名作品不使用它们进行过滤,我为什么要在 Setter 中指定类名称?这只是无用的重复。为什么有这个功能?

    <Style x:Key="MyStyle">
        <Setter Property="Control.Foreground" Value="Red"/>         
        <Setter Property="Control.BorderBrush" Value="DarkBlue"/>
        <Setter Property="Control.Background" Value="LightPink"/>
        <Setter Property="Control.Margin" Value="4,0,0,0"/>
    </Style>

我能想到的唯一情况是这样;我不能在不指定 TextBox 的情况下设置“Text”属性,因为 Control 没有 Text 属性。同样,这并不意味着过滤,我想如果另一个控件,而不是 Text 的子类具有不同的 Text 属性,它也会被设置。

    <Style x:Key="MyStyle" TargetType="{x:Type Control}">
        <Setter Property="Foreground" Value="Red"/>         
        <Setter Property="BorderBrush" Value="DarkBlue"/>
        <Setter Property="Background" Value="LightPink"/>
        <Setter Property="Margin" Value="4,0,0,0"/>
        <Setter Property="TextBox.Text" Value="just a test"/>
    </Style>
那个家伙

我在样式定义中读到,如果在属性名称中包含控件的类名称,则可以去掉 TargetType。

是的,根据 的参考,这是正确的Style

[...] 除了第三个是标签并且它的 BorderThickness 默认为 0,每个样式都适用于每个控件。

样式具有可以应用的特定目标类型。如果您定义了一个没有目标类型的样式,它将默认为IFrameworkInputElement. 双方FrameworkElementFrameworkContentElement实现了这个接口,这意味着它适用于几乎所有的元素,包括ButtonTextBoxLabel

让我们看看您在此样式中定义的属性。

  • Foreground在 上定义TextElement,但按钮通过添加Control为其所有者来公开它
  • BorderBrush在 上定义Border,但TextBox通过添加Control为其所有者来公开它
  • Background在 上定义Panel,但Control通过添加Control为其所有者来公开它,并且Label是 的派生Control,因此它继承了它。
  • Margin在其上定义FrameworkElementControl继承它。

将控件添加为所有者的意思是,相应的控件本身并不定义依赖属性,而是使用该方法从其他人那里“借用”它们AddOwner

这导致了您在示例中看到的情况,属性是为所有Controls有效定义的,因为默认值TargetType不会限制除框架和框架内容元素之外的类型以及受影响的依赖属性的实现方式。

阶段 1:定义样式

1.a 如果设置了 x:Key ( <Style x:Key="MyStyle">),则样式会显式获取键

是的。这称为显式样式,因为您必须自己将其应用于每个目标控件。

1.b 如果未设置 x:Key,则必须设置 TargetType。( <Style TargetType="{x:Type Button}">). 在内部,它为样式分配一个键,该键是 TargetType 中指定的类型的名称

一般不会。如果在资源字典中定义样式,是的。键将是 type object,所以不是类型的名称,而是它Type本身将是它的键。如果直接在Style控件属性中定义样式,则不需要键或目标类型。

1.c 如果同时设置了 TargetType 并且使用语法定义了一个或多个 Setter,则不会检查 TargetType 和 Setter 中的 Classes 值之间的一致性。

是的。你的第一个例子不是非法的,因为按钮从 继承了它的Margin属性FrameworkElement,它只是多余的。在这种情况下,以下设置器本质上是相同的。

<Setter Property="FrameworkElement.Margin" Value="4,0,0,0"/>
<Setter Property="Control.Margin" Value="4,0,0,0"/>
<Setter Property="Margin" Value="4,0,0,0"/>

我认为你的第二个例子没有多大意义。定义键时,必须将样式显式应用于控件,这仅对 有意义TextBox,因此您可以将其定义为目标类型。

阶段 2:将样式与控件相关联

2.a 控件是否有样式定义()?如果有这样的样式,请在资源中查找。如果有,检查它是否也有一个 TargetType;如果控件不是子类的那个类,则引发异常 [...]

基本上是的,目标类型必须匹配类型或派生类。之间的资源查找不同StaticResourceDynamicResource请参考链接的来源以获得更好的理解。

如果类型不匹配,它会抛出InvalidOperationException包裹在 aXamlParseException中。您显示的异常是不同的,表明根本找不到样式。

2.b 如果控件没有定义样式,则在资源中查找键值等于控件类名的样式。这来自仅使用 TargetType 定义的样式。

是的,类型必须与隐式样式完全匹配控件的类型。

请注意,不会应用使用控件超类的 TargetType 定义的样式。他们必须完全匹配。这是样式系统的另一个“限制”,它与 CSS 的复杂性相去甚远;

是的,这是隐式样式的必然结果。关键是目标控件的类型,如果它是控件的基本类型,则两种类型不相等。

WPF 样式似乎只支持字典锁定。

如果你仔细观察,aResourceDictionary就是这样,一个资源字典。应用资源是查找x:Key在所述字典中定义的键

阶段 3:应用样式

那它是如何工作的呢?它只是忽略类信息(如果存在),并继续尝试将每个 setter 应用于控件。[...] 所有三个控件都获得了 LightPink 背景,而不仅仅是 Label。

正如开头所解释的,类信息不会被忽略。

最后,如果成名作品不使用它们进行过滤,我为什么要在 Setter 中指定类名称?这只是无用的重复。为什么有这个功能?

反过来看。通常,您必须明确指定目标类型,例如Button在每个 setter 中。定义一个TargetType让您省略完整限定的 a 是一个方便的功能。此外,正如您的示例所示,您可以使用它来将样式应用于多个控件。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章