样式控件:如何覆盖不同“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 版本都有可能破坏您的实现。
如何处理悬停状态等?
可以用 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 版本都有可能破坏您的实现。