UWP 更改 AppBarButton 的样式打破了动态溢出的自动样式

UWP Changing style of AppBarButton breaks the dynamic overflow's automatic styling

尝试在 CommandBar 上使用新的 IsDynamicOverflowEnabled 属性,我 运行 遇到了溢出的样式问题。问题如下,当AppBarButton的Style没有被覆盖掉落到Overflow区域时,AppBarButton的高亮横跨整个Popup的宽度,它的hover/hit检测也是整个Popup的宽度。

当它的 Style 被覆盖时,高亮仅覆盖文本区域(在本例中为 home)并且其 hover/hit 检测仅覆盖该区域,即使覆盖的 Style 与generic.xaml.

中的一个

我在运行时检查属性时注意到,未被覆盖的属性正在应用来自 TargetType - FrameworkElement 的另一种样式。

通过查看 generic.xaml,我唯一可以看到这种样式更改的地方是 CommandBarOverflowPresenter(见下文)

<CommandBarOverflowPresenter.ItemContainerStyle>
  <Style TargetType="FrameworkElement">
    <Setter Property="HorizontalAlignment" Value="Stretch" />
    <Setter Property="Width" Value="NaN" />
  </Style>
</CommandBarOverflowPresenter.ItemContainerStyle>

为了消除我正在处理的应用程序的所有其他噪音,我创建了一个新项目,主页上只有一个 CommandBar 以及复制我的问题所需的必要按钮和文本。下面是 mainpage.xaml:

<Page
    x:Class="App4.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App4"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

  <Page.Resources>
    <Style x:Key="Style" TargetType="AppBarButton">
      <Setter Property="Background"
              Value="{ThemeResource AppBarButtonBackground}" />
      <Setter Property="Foreground"
              Value="{ThemeResource AppBarButtonForeground}" />
      <Setter Property="BorderBrush"
              Value="{ThemeResource AppBarButtonBorderBrush}" />
      <Setter Property="HorizontalAlignment"
              Value="Left" />
      <Setter Property="VerticalAlignment"
              Value="Top" />
      <Setter Property="FontFamily"
              Value="{ThemeResource ContentControlThemeFontFamily}" />
      <Setter Property="FontWeight"
              Value="Normal" />
      <Setter Property="Width"
              Value="68" />
      <Setter Property="UseSystemFocusVisuals"
              Value="True" />
      <Setter Property="AllowFocusOnInteraction"
              Value="False" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="AppBarButton">
            <Grid x:Name="Root"
                  MinWidth="{TemplateBinding MinWidth}"
                  MaxWidth="{TemplateBinding MaxWidth}"
                  Background="{TemplateBinding Background}"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  BorderThickness="{TemplateBinding BorderThickness}">
              <Grid.Resources>
                <Style x:Name="LabelOnRightStyle"
                       TargetType="AppBarButton">
                  <Setter Property="Width"
                          Value="NaN" />
                </Style>
              </Grid.Resources>
              <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="ApplicationViewStates">
                  <VisualState x:Name="FullSize" />
                  <VisualState x:Name="Compact">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="Visibility">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="Collapsed" />
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="LabelOnRight">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content"
                                                     Storyboard.TargetProperty="Margin">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="12,14,0,14" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentRoot"
                                                     Storyboard.TargetProperty="MinHeight">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarThemeCompactHeight}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="(Grid.Row)">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="0" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="(Grid.Column)">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="1" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="TextAlignment">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="Left" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="Margin">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="8,15,12,17" />
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="LabelCollapsed">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentRoot"
                                                     Storyboard.TargetProperty="MinHeight">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarThemeCompactHeight}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="Visibility">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="Collapsed" />
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="Overflow">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentRoot"
                                                     Storyboard.TargetProperty="Visibility">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="Collapsed" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel"
                                                     Storyboard.TargetProperty="Visibility">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="Visible" />
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="OverflowWithToggleButtons">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentRoot"
                                                     Storyboard.TargetProperty="Visibility">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="Collapsed" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel"
                                                     Storyboard.TargetProperty="Visibility">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="Visible" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel"
                                                     Storyboard.TargetProperty="Margin">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="38,0,12,0" />
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                </VisualStateGroup>
                <VisualStateGroup x:Name="CommonStates">
                  <VisualState x:Name="Normal">
                    <Storyboard>
                      <PointerUpThemeAnimation Storyboard.TargetName="OverflowTextLabel" />
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="PointerOver">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root"
                                                     Storyboard.TargetProperty="Background">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonBackgroundPointerOver}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root"
                                                     Storyboard.TargetProperty="BorderBrush">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonBorderBrushPointerOver}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundPointerOver}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundPointerOver}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundPointerOver}" />
                      </ObjectAnimationUsingKeyFrames>
                      <PointerUpThemeAnimation Storyboard.TargetName="OverflowTextLabel" />
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="Pressed">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root"
                                                     Storyboard.TargetProperty="Background">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonBackgroundPressed}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root"
                                                     Storyboard.TargetProperty="BorderBrush">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonBorderBrushPressed}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundPressed}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundPressed}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundPressed}" />
                      </ObjectAnimationUsingKeyFrames>
                      <PointerDownThemeAnimation Storyboard.TargetName="OverflowTextLabel" />
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="Disabled">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root"
                                                     Storyboard.TargetProperty="Background">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonBackgroundDisabled}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root"
                                                     Storyboard.TargetProperty="BorderBrush">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonBorderBrushDisabled}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundDisabled}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundDisabled}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel"
                                                     Storyboard.TargetProperty="Foreground">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{ThemeResource AppBarButtonForegroundDisabled}" />
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                </VisualStateGroup>
                <VisualStateGroup x:Name="InputModeStates">
                  <VisualState x:Name="InputModeDefault" />
                  <VisualState x:Name="TouchInputMode">
                    <VisualState.Setters>
                      <Setter Target="OverflowTextLabel.Padding"
                              Value="0,11,0,13" />
                    </VisualState.Setters>
                  </VisualState>
                  <VisualState x:Name="GameControllerInputMode">
                    <VisualState.Setters>
                      <Setter Target="OverflowTextLabel.Padding"
                              Value="0,11,0,13" />
                    </VisualState.Setters>
                  </VisualState>
                </VisualStateGroup>
              </VisualStateManager.VisualStateGroups>
              <Grid x:Name="ContentRoot"
                    MinHeight="{ThemeResource AppBarThemeMinHeight}">
                <Grid.ColumnDefinitions>
                  <ColumnDefinition Width="*" />
                  <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                  <RowDefinition Height="Auto" />
                  <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <ContentPresenter x:Name="Content"
                                  Height="20"
                                  Margin="0,14,0,4"
                                  Content="{TemplateBinding Icon}"
                                  Foreground="{TemplateBinding Foreground}"
                                  HorizontalAlignment="Stretch"
                                  AutomationProperties.AccessibilityView="Raw" />
                <TextBlock x:Name="TextLabel"
                           Grid.Row="1"
                           Text="{TemplateBinding Label}"
                           Foreground="{TemplateBinding Foreground}"
                           FontSize="12"
                           FontFamily="{TemplateBinding FontFamily}"
                           TextAlignment="Center"
                           TextWrapping="Wrap"
                           Margin="2,0,2,6" />
              </Grid>
              <TextBlock x:Name="OverflowTextLabel"
                         Text="{TemplateBinding Label}"
                         Foreground="{TemplateBinding Foreground}"
                         FontSize="15"
                         FontFamily="{TemplateBinding FontFamily}"
                         TextAlignment="Left"
                         TextTrimming="Clip"
                         TextWrapping="NoWrap"
                         HorizontalAlignment="Stretch"
                         VerticalAlignment="Center"
                         Margin="12,0,12,0"
                         Padding="0,5,0,7"
                         Visibility="Collapsed" />
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Page.Resources>

  <Page.TopAppBar>
    <CommandBar IsDynamicOverflowEnabled="True">
      <CommandBar.Content>
        <TextBlock Text="Text to force the buttons to drop off at some point when the screen is resized" />
      </CommandBar.Content>
      <CommandBar.PrimaryCommands>
        <AppBarButton Label="Edit"
                      Icon="Edit" />
        <AppBarButton Label="People"
                      Icon="People" />
        <AppBarButton Label="Really long text should should make overflow wider"
                      Icon="Highlight" />
        <AppBarButton Label="Home"
                      Icon="Home"
                      Style="{StaticResource Style}" />
      </CommandBar.PrimaryCommands>
    </CommandBar>
  </Page.TopAppBar>
</Page>

如您所见,我有 1 个 CommandBar,CommandBar 的内容中有 1 个 TextBlock,CommandBar 的 PrimaryCommand 中有 4 个 AppBarButton。 AppBarButtons 样式中只有 1 个被覆盖(Home 样式),被覆盖的样式直接从 generic.xaml 的这个位置抓取 - C:\Program Files (x86)\Windows Kits\DesignTime\CommonConfiguration\Neutral\UAP.0.14393.0\Generic

为什么会这样?是否有任何已知的解决方法?

如您所知,而 AppBarButton is added into SecondaryCommands, it will display in Popup. And if you check with Live Visual Tree in Visual Studio, you will find the AppBarButton is added in to a CommandBarOverflowPresenter 名为 "SecondaryItemsControl"。

您在 CommandBarOverflowPresenter.ItemContainerStyle 下找到的样式用于将 AppBarButtonWidthHorizontalAlignment 属性 重置为默认值 AppBarButtonWidth68HorizontalAlignment设置为Left,如果我们使用默认样式,AppBarButton不能占整个宽度弹出窗口。

<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Width" Value="68"/>

但是,CommandBarOverflowPresenter.ItemContainerStyle下的样式只适用于AppBarButton带有隐式样式。如果显式设置AppBarButtonStyle属性,它将失去作用。这就是为什么更改 AppBarButton 的样式会破坏动态溢出的自动样式的原因。

作为解决方法,您可以尝试更改 WidthHorizontalAlignment 属性,就像 CommandBarOverflowPresenter.ItemContainerStyle 下的那样,并设置 MiniWidth 以限制 AppBarButton的宽度。

<Style x:Key="Style" TargetType="AppBarButton">
    <Setter Property="HorizontalAlignment" Value="Stretch" />
    <Setter Property="Width" Value="Auto" />
    <Setter Property="MinWidth" Value="68" />
    ...
</Style>

PS:此解决方法可能并不适用于所有情况,尤其是当 AppBarButton 有一个长 Label 时。可能需要根据自己的场景决定是否使用。

更新:

Your answer raises another question, why can only implicit styles inherit this CommandBarOverflowPresenter.ItemContainerStyle?

这与 Lookup behavior for XAML resource references 有关。你可以认为 Style 已明确设置为最高优先级。一旦你设置了Style属性,XAML系统将不会寻找其他样式。

参考 ResourceDictionary and XAML resource references:

The XAML framework also looks for implicit style resources (those which use TargetType rather than x:Key or x:Name) when it decides which style & template to use for a control that hasn't set the Style and ContentTemplate or ItemTemplate properties.

所以一旦你为AppBarButton设置了StyleCommandBarOverflowPresenter.ItemContainerStyle下的隐式样式将不起作用。

以下是演示此行为的简单示例。

<Page ...>
    <Page.Resources>
        <Style x:Key="ListViewItemStyle1" TargetType="ListViewItem">
            <Setter Property="Background" Value="Red" />
        </Style>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView>
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="Background" Value="Green" />
                    <Setter Property="Foreground" Value="White" />
                </Style>
            </ListView.ItemContainerStyle>
            <ListViewItem Style="{StaticResource ListViewItemStyle1}">1</ListViewItem>
            <ListViewItem Background="Blue">2</ListViewItem>
            <ListViewItem>3</ListViewItem>
            <ListViewItem>4</ListViewItem>
        </ListView>
    </Grid>
</Page>

看起来像:

如您所见,对于第一项,由于我明确设置了Style 属性,因此它不会使用ListView.ItemContainerStyle 下的样式。对于第二项,由于我没有设置Style 属性,它会使用ListView.ItemContainerStyle下的隐式样式,但是显式设置的属性有一个更高的优先级。所以这个项目的前景是白色的,但它的背景是蓝色的。而对于第三项和第四项,由于它们使用了优先级最低的默认样式,因此将使用ListView.ItemContainerStyle下的隐式样式而不是默认样式。