1. 前言
每一个有理想的 UWP 应用都会打标题栏的注意, 尤其当微软提供 将 Acrylic 扩展到标题栏 这个功能后, 大部分 Windows 10 的原生应用都不乖了, 纷纷占领了标题栏的一亩三分地. 这篇博客将介绍在 UWP 中如何自定义标题栏.
2. 示例代码
UWP 的限制很多, 标题栏的自定义几乎全部内容集中在 这篇文档 里面. 但只参考这篇文章做起来还不够顺手, 我参考了微软开源的计算器应用中的 写了一个示例应用, 可以在 这里 查看它的源码. 我也把 TitleBar 实际应用到了我的 https://github.com/DinoChan/OnePomodoro 应用里面了.
3. 简单的颜色自定义
如果只想简单地自定义标题栏的颜色可以通过 ApplicationViewTitleBar,ApplicationViewTitleBar 表示应用程序的标题栏, 它提供了一些颜色属性用于控制标题栏的颜色, 示例代码如下:
- // using Windows.UI.ViewManagement;
- var titleBar = ApplicationView.GetForCurrentView().TitleBar;
- // Set active Windows colors
- titleBar.ForegroundColor = Windows.UI.Colors.White;
- titleBar.BackgroundColor = Windows.UI.Colors.Green;
- titleBar.ButtonForegroundColor = Windows.UI.Colors.White;
- titleBar.ButtonBackgroundColor = Windows.UI.Colors.SeaGreen;
- titleBar.ButtonHoverForegroundColor = Windows.UI.Colors.White;
- titleBar.ButtonHoverBackgroundColor = Windows.UI.Colors.DarkSeaGreen;
- titleBar.ButtonPressedForegroundColor = Windows.UI.Colors.Gray;
- titleBar.ButtonPressedBackgroundColor = Windows.UI.Colors.LightGreen;
- // Set inactive Windows colors
- titleBar.InactiveForegroundColor = Windows.UI.Colors.Gray;
- titleBar.InactiveBackgroundColor = Windows.UI.Colors.SeaGreen;
- titleBar.ButtonInactiveForegroundColor = Windows.UI.Colors.Gray;
- titleBar.ButtonInactiveBackgroundColor = Windows.UI.Colors.SeaGreen;
有几点需要注意:
悬停和按下状态的 Background 定义对关闭按钮无效
Foreground 不能设置透明
4. 将内容扩展到标题栏
若要隐藏默认标题栏并将你的内容扩展到标题栏区域中, 请将 CoreApplicationViewTitleBar.ExtendViewIntoTitleBar 属性设置为 true.CoreApplicationViewTitleBar 允许应用定义在应用窗口中显示的自定义标题栏. 示例代码如下:
- // using Windows.ApplicationModel.Core;
- // Hide default title bar.
- var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
- coreTitleBar.ExtendViewIntoTitleBar = true;
5. 将内容扩展到标题栏时自定义标题按钮颜色
将内容扩展到标题栏, 标题按钮的颜色就变复杂了. 因为应用内容的颜色可能和按钮的颜色冲突. 这种情况下有几种方案, 其中最简单的一种方案是写死为一个不会冲突的颜色, 但切换主题时可能会让这些颜色出问题. 计算器应用中订阅 UISettings 的 ColorValuesChanged 事件, 动态地根据 ThemeResources 的值改变标题栏颜色, 并且更进一步地考虑到使用高对比度主题的情况, 所以订阅了 AccessibilitySettings 的 HighContrastChanged 事件:
- if (_accessibilitySettings.HighContrast)
- {
- // Reset to use default colors.
- applicationTitleBar.ButtonBackgroundColor = null;
- applicationTitleBar.ButtonForegroundColor = null;
- applicationTitleBar.ButtonInactiveBackgroundColor = null;
- applicationTitleBar.ButtonInactiveForegroundColor = null;
- applicationTitleBar.ButtonHoverBackgroundColor = null;
- applicationTitleBar.ButtonHoverForegroundColor = null;
- applicationTitleBar.ButtonPressedBackgroundColor = null;
- applicationTitleBar.ButtonPressedForegroundColor = null;
- }
- else
- {
- Color bgColor = Colors.Transparent;
- Color fgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlPageTextBaseHighBrush"]).Color;
- Color inactivefgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundChromeDisabledLowBrush"]).Color;
- Color hoverbgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlBackgroundListLowBrush"]).Color;
- Color hoverfgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundBaseHighBrush"]).Color;
- Color pressedbgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlBackgroundListMediumBrush"]).Color;
- Color pressedfgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundBaseHighBrush"]).Color;
- applicationTitleBar.ButtonBackgroundColor = bgColor;
- applicationTitleBar.ButtonForegroundColor = fgColor;
- applicationTitleBar.ButtonInactiveBackgroundColor = bgColor;
- applicationTitleBar.ButtonInactiveForegroundColor = inactivefgColor;
- applicationTitleBar.ButtonHoverBackgroundColor = hoverbgColor;
- applicationTitleBar.ButtonHoverForegroundColor = hoverfgColor;
- applicationTitleBar.ButtonPressedBackgroundColor = pressedbgColor;
- applicationTitleBar.ButtonPressedForegroundColor = pressedfgColor;
- }
这段代码中, 当使用高对比度主题时将标题栏的按钮颜色还原成默认值, 否则设置成 ThemeResource 中对应的颜色, 运行效果如下:
但现在的 UWP 应用常常在 Dark 和 Light 主题之间反复横跳, 而 Application.Current.Resources 只能拿到程序加载时的 ThemeResource 的值, 所以这段代码在应用内的主题切换后无效. 我暂时不清楚怎么在代码里拿到最新的 ThemeResource, 为解决这个问题只好让 TitleBar 自己在 XAML 中获取当前的 ThemeResource, 代码如下:
- <UserControl.Resources>
- <SolidColorBrush x:Key="ButtonForegroundColor"
- Color="{ThemeResource SystemBaseHighColor}" />
- <SolidColorBrush x:Key="ButtonInactiveForegroundBrush"
- Color="{ThemeResource SystemChromeDisabledLowColor}" />
- <SolidColorBrush x:Key="ButtonHoverBackgroundBrush"
- Color="{ThemeResource SystemListLowColor}" />
- <SolidColorBrush x:Key="ButtonHoverForegroundBrush"
- Color="{ThemeResource SystemBaseHighColor}" />
- <SolidColorBrush x:Key="ButtonPressedBackgroundBrush"
- Color="{ThemeResource SystemListMediumColor}" />
- <SolidColorBrush x:Key="ButtonPressedForegroundBrush"
- Color="{ThemeResource SystemBaseHighColor}" />
- </UserControl.Resources>
- Color fgColor = ((SolidColorBrush)Resources["ButtonForegroundColor"]).Color;
- Color inactivefgColor = ((SolidColorBrush)Resources["ButtonInactiveForegroundBrush"]).Color;
- Color hoverbgColor = ((SolidColorBrush)Resources["ButtonHoverBackgroundBrush"]).Color;
- Color hoverfgColor = ((SolidColorBrush)Resources["ButtonHoverForegroundBrush"]).Color;
- Color pressedbgColor = ((SolidColorBrush)Resources["ButtonPressedBackgroundBrush"]).Color;
- Color pressedfgColor = ((SolidColorBrush)Resources["ButtonPressedForegroundBrush"]).Color;
6. 可拖动区域
都将内容扩展到标题栏了, 肯定是想在标题栏上放置自己需要的 UI 元素, 默认情况下标题栏的范围为拖动, 点击等 Windows 的窗体行为保留, 在这个范围的自定义 UI 内容没办法获取鼠标点击. 为了让自定义的 UI 内容获取鼠标, 可以用 Windows.SetTitleBar 方法指定某一元素能用于窗体的拖动和点击.
- <Grid x:Name="LayoutRoot"
- Height="32"
- HorizontalAlignment="Stretch">
- <Grid x:Name="BackgroundElement"
- Height="32"
- Background="Transparent" />
- <StackPanel Orientation="Horizontal">
- <StackPanel x:Name="ItemsPanel" Orientation="Horizontal">
- </StackPanel>
- <TextBlock x:Name="AppName"
- x:Uid="AppName"
- Text="ExtendViewIntoTitleBarDemo"
- </StackPanel>
- </Grid>
- Windows.Current.SetTitleBar(BackgroundElement);
上面的代码指定 TitlaBar 中的 BackgroundElement 元素为可拖动区域, 而下面的 StackPanel 则用于放置交互内容, 例如标题或后退按钮. 这个 StackPanel 必须比 BackgroundElement 具有较高的 Z 顺序才能接收到用户的鼠标输入.
7. 标题的系统保留区域
标题栏的右边有 188 像素的系统保留区域, 用于系统标题按钮 ("后退","最小化","最大化","关闭"). 其实这几个按钮也就占用了 141 像素的控件, 还有一小块空间是默认的可拖动区域, 这小块空间确保了无论怎么设置都总有一个用户可拖动的区域.
上面说的 188 像素是 100% 缩放的情况, 通过上面的截图可以看到实际上可能不一样, 通常来说会在窗体加载时, 或者订阅 CoreApplicationViewTitleBar.LayoutMetricsChanged 事件, 然后通过 CoreApplicationViewTitleBar 获取具体的值.
- _coreTitleBar.LayoutMetricsChanged += OnLayoutMetricsChanged;
- private void OnLayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args)
- {
- LayoutRoot.Height = _coreTitleBar.Height;
- SetTitleBarPadding();
- }
- private void SetTitleBarPadding()
- {
- double leftAddition = 0;
- double rightAddition = 0;
- if (FlowDirection == FlowDirection.LeftToRight)
- {
- leftAddition = _coreTitleBar.SystemOverlayLeftInset;
- rightAddition = _coreTitleBar.SystemOverlayRightInset;
- }
- else
- {
- leftAddition = _coreTitleBar.SystemOverlayRightInset;
- rightAddition = _coreTitleBar.SystemOverlayLeftInset;
- }
- LayoutRoot.Padding = new Thickness(leftAddition, 0, rightAddition, 0);
- }
8. 可交互区域的内容
上面的 StackPanel 是可交互区域, 详细的内容如下:
- <StackPanel Orientation="Horizontal">
- <StackPanel x:Name="ItemsPanel" Orientation="Horizontal">
- <StackPanel.Resources>
- <Style TargetType="Button"
- BasedOn="{StaticResource NavigationBackButtonNormalStyle}">
- <Setter Property="Foreground"
- Value="{StaticResource TitleBarForeground}" />
- <Setter Property="FontSize"
- Value="10" />
- <Setter Property="Width"
- Value="46" />
- <Setter Property="Height"
- Value="32" />
- <Setter Property="IsTabStop"
- Value="False" />
- </Style>
- </StackPanel.Resources>
- </StackPanel>
- <TextBlock x:Name="AppName"
- x:Uid="AppName"
- Text="ExtendViewIntoTitleBarDemo"
- Margin="12,0,12,0"
- HorizontalAlignment="Left"
- VerticalAlignment="Center"
- Foreground="{ThemeResource SystemControlPageTextBaseHighBrush}"
- FontSize="12"
- IsHitTestVisible="False"
- TextAlignment="Left"
- TextTrimming="CharacterEllipsis" />
- </StackPanel>
其中 AppName 用于显示标题栏, ItemsPanel 用于放其它按钮. TitleBar 里定义了 Buttons 属性, 调用 TitleBar 可以通过 Buttons 属性指定按钮 (这部分代码我凌晨两点写的, 写得十分敷衍, 但写完又懒得改了).
- public ObservableCollection<Button> Buttons { get; } = new ObservableCollection<Button>();
- private void OnButtonsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
- {
- ItemsPanel.Children.Clear();
- foreach (var button in Buttons)
- {
- ItemsPanel.Children.Add(button);
- }
- }
- <local:TitleBar>
- <local:TitleBar.Buttons>
- <Button x:Name="OptionsButton"
- Content=""
- ToolTipService.ToolTip="Options" />
- <Button Content=""
- ToolTipService.ToolTip="Options" />
- <Button Content=""
- ToolTipService.ToolTip="Options" />
- <Button Content=""
- ToolTipService.ToolTip="Options" />
- </local:TitleBar.Buttons>
- </local:TitleBar>
按钮的样式来自 NavigationBackButtonNormalStyle 并稍作修改, 大致上做到和标准的标题栏按钮一样.
9. 非激活状态的标题栏颜色
当窗体处于非激活状态应该让按钮和标题都变灰, 可以订阅 Windows 的 Activated 事件, 在非激活状态时改变颜色:
- Windows.Current.Activated += OnWindowActivated;
- private void OnWindowActivated(Object sender, WindowActivatedEventArgs e)
- {
- VisualStateManager.GoToState(
- this, e.WindowActivationState == CoreWindowActivationState.Deactivated ? WindowNotFocused.Name : WindowFocused.Name, false);
- }
- <UserControl.Resources>
- <SolidColorBrush x:Key="TitleBarForeground"
- x:Name="TitleBarForeground"
- Color="{ThemeResource SystemBaseHighColor}" />
- </UserControl.Resources>
- <Grid x:Name="LayoutRoot"
- Height="32"
- HorizontalAlignment="Stretch">
- <VisualStateManager.VisualStateGroups>
- <VisualStateGroup x:Name="WindowFocusStates">
- <VisualState x:Name="WindowFocused" />
- <VisualState x:Name="WindowNotFocused">
- <VisualState.Setters>
- <Setter Target="AppName.Foreground"
- Value="{ThemeResource SystemControlForegroundChromeDisabledLowBrush}" />
- <Setter Target="TitleBarForeground.Color"
- Value="{ThemeResource SystemChromeDisabledLowColor}" />
- </VisualState.Setters>
- </VisualState>
- </VisualStateGroup>
- </VisualStateManager.VisualStateGroups>
10. 全屏和平板模式
当应用在全屏或平板模式下运行时, 系统将隐藏标题栏和标题控制按钮. 但是, 用户可以调用标题栏, 以使其以覆盖形式显示在应用的 UI 顶部. 你可以处理隐藏或调用标题栏时将通知的 CoreApplicationViewTitleBar.IsVisibleChanged 事件, 并根据需要显示或隐藏你的自定义标题栏内容.
LayoutRoot.Visibility = _coreTitleBar.IsVisible ? Visibility.Visible : Visibility.Collapsed;
这部分比较难截图就不搞了, 想看效果可以试玩我的番茄钟应用.
11. 结语
就这样, 令人头痛的自定义标题栏处理完了. 还好微软开源了它的计算器里正好有我需要的代码, 抄了个爽. 有一些处理得不好, 如果错误请指正.
12. 参考
标题栏自定义
- calculator_TitleBar.xaml.cpp at master
- ApplicationViewTitleBar Class (Windows.UI.ViewManagement) - Windows UWP applications Microsoft Docs
- CoreApplicationViewTitleBar Class (Windows.ApplicationModel.Core) - Windows UWP applications Microsoft Docs
13. 源码
- DinoChan_ExtendViewIntoTitleBarDemo How to handle titlebar when ExtendViewIntoTitleBar
- OnePomodoro_TitleBar.xaml at master
来源: https://www.cnblogs.com/dino623/p/uwp-title-bar.html