样式控件:如何覆盖不同“VisualState”(PointerOver、Pressed、...)中的样式

Styling controls: How to overwrite the styles in different 'VisualState's (PointerOver, Pressed, ...)

如何处理悬停状态等?

可以用 SystemControlHighlightBaseHighBrush 这样的笔刷覆盖 Button:

<SolidColorBrush x:Key="SystemControlHighlightBaseHighBrush" Color="White" />

如果任何其他控件使用该画笔,它也会采用该值,这是不希望的。我发现的另一个选项是覆盖默认样式:

<!-- Default style for Windows.UI.Xaml.Controls.Button -->
<Style TargetType="Button">
    <Setter Property="Background" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
    <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}"/>
    <Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundTransparentBrush}" />
    <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
    <Setter Property="Padding" Value="8,4,8,4" />
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
    <Setter Property="FontWeight" Value="Normal" />
    <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
    <Setter Property="UseSystemFocusVisuals" Value="True" />
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="Button">
          <Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
            <VisualStateManager.VisualStateGroups>
              <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal">
                  <Storyboard>
                    <PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
                  </Storyboard>
                </VisualState>
                <VisualState x:Name="PointerOver">
                  <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="BorderBrush">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseMediumLowBrush}" />
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseHighBrush}" />
                    </ObjectAnimationUsingKeyFrames>
                    <PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
                  </Storyboard>
                </VisualState>
                <VisualState x:Name="Pressed">
                  <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
                                                       Storyboard.TargetProperty="Background">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseMediumLowBrush}" />
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="BorderBrush">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseHighBrush}" />
                    </ObjectAnimationUsingKeyFrames>
                    <PointerDownThemeAnimation Storyboard.TargetName="RootGrid" />
                  </Storyboard>
                </VisualState>
                <VisualState x:Name="Disabled">
                  <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
                                                       Storyboard.TargetProperty="Background">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="BorderBrush">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledTransparentBrush}" />
                    </ObjectAnimationUsingKeyFrames>
                  </Storyboard>
                </VisualState>
              </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <ContentPresenter x:Name="ContentPresenter"
                              BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="{TemplateBinding BorderThickness}"
                              Content="{TemplateBinding Content}"
                              ContentTransitions="{TemplateBinding ContentTransitions}"
                              ContentTemplate="{TemplateBinding ContentTemplate}"
                              Padding="{TemplateBinding Padding}"
                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                              AutomationProperties.AccessibilityView="Raw"/>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
</Style>

在这里,我将更改 ObjectAnimationUsingKeyFrames 中的值。但是,如果即将推出的 Windows 版本中的默认样式发生变化怎么办?

目前我看不到除了覆盖默认样式之外的其他方法。也许可以覆盖一些事件并设置例如那里的边框颜色。但我认为并非所有事件都可用,您每次都必须编写自定义按钮。

在我看来,为了仅更改一个值而覆盖完整的样式有点不对。你如何处理这种情况?

TL;DR: 是的,更改整个模板(不是样式)是最常用的场景。

这里有不同的点需要考虑:

  • 您希望所有控件在整个应用程序中具有一致的布局。

所以我的第一个选择是为您的应用程序定义一个新的 主题 并覆盖完成您的应用程序所需的 SystemControlHighlightBaseHighBrush 和其他 ThemeResource 资源新品牌。

  • 您只需要一个控件来更改布局。

如果出于某种正当理由您不希望使用 ThemeResource 的所有控件都更改为您的新主题,您将不得不覆盖整个控件模板(因为模板是一个块,因此是一个全有或全无)。无需覆盖所有其他 属性 setters(如前景、背景、填充...在您的示例中)。

如果您很幸运,您尝试更改的 属性 是模板化的 属性,您只需使用一个 属性 setter 修复布局。

这种方法的缺点确实是 SDK 的未来版本可能会更改给定控件的模板。 GridView/GridViewItem 样式在 Windows 8/8.1 和 10 之间就是一个例子。到目前为止,样式大多具有向后兼容性,这意味着您的应用程序将继续工作,但可能不符合最新的布局指南或错过一些性能改进。因此 'best practice' 到 在最新模板上重新应用自定义布局更改(如果时间允许)。

'dirty' 解决方案是 'hacking' 进入可视化树以在运行时更改属性(基于事件,...)。但这也不会阻止您对未来的 SDK 更新进行可能的重大更改,因此我什至不认为这是一个有效的选项。

  • 使用自定义控件

最后的方法是使用自定义控件,定义您自己的模板(同样是新版 SDK 的问题)或使用默认模板并覆盖 OnApplyTemplate 并调整 属性 你想通过从可视化树中获取它来进行更改。但同样的评论在这里适用,未来的 SDK 版本可能会在您的树中删除 control/state 并破坏您的代码。

结论:无论您选择哪个选项,未来的 SDK 版本都有可能破坏您的实现。