1. 前言

每一个有理想的UWP应用都会打标题栏的主意,尤其当微软提供 将 Acrylic 扩展到标题栏 这个功能后,大部分Windows 10的原生应用都不乖了,纷纷占领了标题栏的一亩三分地。这篇博客将介绍在UWP中如何自定义标题栏。

2.示例代码

UWP的限制很多,标题栏的自定义几乎全部内容集中在 这篇文档 里面。但只参考这篇文章做起来还不够顺手,我参考了微软开源的计算器应用中的 TitleBar 写了一个示例应用,可以在 这里 查看它的源码。我也把TitleBar实际应用到了我的 OnePomodoro 应用里面了。

3. 简单的颜色自定义

如果只想简单地自定义标题栏的颜色可以通过ApplicationViewTitleBar,ApplicationViewTitleBar表示应用程序的标题栏,它提供了一些颜色属性用于控制标题栏的颜色,示例代码如下:

  1. // using Windows.UI.ViewManagement;
  2. var titleBar = ApplicationView.GetForCurrentView().TitleBar;
  3. // Set active window colors
  4. titleBar.ForegroundColor = Windows.UI.Colors.White;
  5. titleBar.BackgroundColor = Windows.UI.Colors.Green;
  6. titleBar.ButtonForegroundColor = Windows.UI.Colors.White;
  7. titleBar.ButtonBackgroundColor = Windows.UI.Colors.SeaGreen;
  8. titleBar.ButtonHoverForegroundColor = Windows.UI.Colors.White;
  9. titleBar.ButtonHoverBackgroundColor = Windows.UI.Colors.DarkSeaGreen;
  10. titleBar.ButtonPressedForegroundColor = Windows.UI.Colors.Gray;
  11. titleBar.ButtonPressedBackgroundColor = Windows.UI.Colors.LightGreen;
  12. // Set inactive window colors
  13. titleBar.InactiveForegroundColor = Windows.UI.Colors.Gray;
  14. titleBar.InactiveBackgroundColor = Windows.UI.Colors.SeaGreen;
  15. titleBar.ButtonInactiveForegroundColor = Windows.UI.Colors.Gray;
  16. titleBar.ButtonInactiveBackgroundColor = Windows.UI.Colors.SeaGreen;

有几点需要注意:

  • 悬停和按下状态的Background定义对关闭按钮无效
  • Foreground不能设置透明

4. 将内容扩展到标题栏

若要隐藏默认标题栏并将你的内容扩展到标题栏区域中,请将 CoreApplicationViewTitleBar.ExtendViewIntoTitleBar 属性设置为 true。CoreApplicationViewTitleBar允许应用定义在应用窗口中显示的自定义标题栏。示例代码如下:

  1. // using Windows.ApplicationModel.Core;
  2. // Hide default title bar.
  3. var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
  4. coreTitleBar.ExtendViewIntoTitleBar = true;

5. 将内容扩展到标题栏时自定义标题按钮颜色

将内容扩展到标题栏,标题按钮的颜色就变复杂了。因为应用内容的颜色可能和按钮的颜色冲突。这种情况下有几种方案,其中最简单的一种方案是写死为一个不会冲突的颜色,但切换主题时可能会让这些颜色出问题。计算器应用中订阅UISettings的ColorValuesChanged事件,动态地根据ThemeResources的值改变标题栏颜色,并且更进一步地考虑到使用高对比度主题的情况,所以订阅了AccessibilitySettings的HighContrastChanged事件:

  1. if (_accessibilitySettings.HighContrast)
  2. {
  3. // Reset to use default colors.
  4. applicationTitleBar.ButtonBackgroundColor = null;
  5. applicationTitleBar.ButtonForegroundColor = null;
  6. applicationTitleBar.ButtonInactiveBackgroundColor = null;
  7. applicationTitleBar.ButtonInactiveForegroundColor = null;
  8. applicationTitleBar.ButtonHoverBackgroundColor = null;
  9. applicationTitleBar.ButtonHoverForegroundColor = null;
  10. applicationTitleBar.ButtonPressedBackgroundColor = null;
  11. applicationTitleBar.ButtonPressedForegroundColor = null;
  12. }
  13. else
  14. {
  15. Color bgColor = Colors.Transparent;
  16. Color fgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlPageTextBaseHighBrush"]).Color;
  17. Color inactivefgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundChromeDisabledLowBrush"]).Color;
  18. Color hoverbgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlBackgroundListLowBrush"]).Color;
  19. Color hoverfgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundBaseHighBrush"]).Color;
  20. Color pressedbgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlBackgroundListMediumBrush"]).Color;
  21. Color pressedfgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundBaseHighBrush"]).Color;
  22. applicationTitleBar.ButtonBackgroundColor = bgColor;
  23. applicationTitleBar.ButtonForegroundColor = fgColor;
  24. applicationTitleBar.ButtonInactiveBackgroundColor = bgColor;
  25. applicationTitleBar.ButtonInactiveForegroundColor = inactivefgColor;
  26. applicationTitleBar.ButtonHoverBackgroundColor = hoverbgColor;
  27. applicationTitleBar.ButtonHoverForegroundColor = hoverfgColor;
  28. applicationTitleBar.ButtonPressedBackgroundColor = pressedbgColor;
  29. applicationTitleBar.ButtonPressedForegroundColor = pressedfgColor;
  30. }

这段代码中,当使用高对比度主题时将标题栏的按钮颜色还原成默认值,否则设置成ThemeResource中对应的颜色,运行效果如下:

但现在的UWP应用常常在Dark和Light主题之间反复横跳,而Application.Current.Resources只能拿到程序加载时的ThemeResource的值,所以这段代码在应用内的主题切换后无效。我暂时不清楚怎么在代码里拿到最新的ThemeResource,为解决这个问题只好让TitleBar自己在XAML中获取当前的ThemeResource,代码如下:

  1. <UserControl.Resources>
  2. <SolidColorBrush x:Key="ButtonForegroundColor"
  3. Color="{ThemeResource SystemBaseHighColor}" />
  4. <SolidColorBrush x:Key="ButtonInactiveForegroundBrush"
  5. Color="{ThemeResource SystemChromeDisabledLowColor}" />
  6. <SolidColorBrush x:Key="ButtonHoverBackgroundBrush"
  7. Color="{ThemeResource SystemListLowColor}" />
  8. <SolidColorBrush x:Key="ButtonHoverForegroundBrush"
  9. Color="{ThemeResource SystemBaseHighColor}" />
  10. <SolidColorBrush x:Key="ButtonPressedBackgroundBrush"
  11. Color="{ThemeResource SystemListMediumColor}" />
  12. <SolidColorBrush x:Key="ButtonPressedForegroundBrush"
  13. Color="{ThemeResource SystemBaseHighColor}" />
  14. </UserControl.Resources>
  1. Color fgColor = ((SolidColorBrush)Resources["ButtonForegroundColor"]).Color;
  2. Color inactivefgColor = ((SolidColorBrush)Resources["ButtonInactiveForegroundBrush"]).Color;
  3. Color hoverbgColor = ((SolidColorBrush)Resources["ButtonHoverBackgroundBrush"]).Color;
  4. Color hoverfgColor = ((SolidColorBrush)Resources["ButtonHoverForegroundBrush"]).Color;
  5. Color pressedbgColor = ((SolidColorBrush)Resources["ButtonPressedBackgroundBrush"]).Color;
  6. Color pressedfgColor = ((SolidColorBrush)Resources["ButtonPressedForegroundBrush"]).Color;

6. 可拖动区域

都将内容扩展到标题栏了,肯定是想在标题栏上放置自己需要的UI元素,默认情况下标题栏的范围为拖动、点击等Windows的窗体行为保留,在这个范围的自定义UI内容没办法获取鼠标点击。 为了让自定义的UI内容获取鼠标,可以用Window.SetTitleBar方法指定某一元素能用于窗体的拖动和点击。

  1. <Grid x:Name="LayoutRoot"
  2. Height="32"
  3. HorizontalAlignment="Stretch">
  4. <Grid x:Name="BackgroundElement"
  5. Height="32"
  6. Background="Transparent" />
  7. <StackPanel Orientation="Horizontal">
  8. <StackPanel x:Name="ItemsPanel" Orientation="Horizontal">
  9. </StackPanel>
  10. <TextBlock x:Name="AppName"
  11. x:Uid="AppName"
  12. Text="ExtendViewIntoTitleBarDemo"
  13. </StackPanel>
  14. </Grid>
  1. Window.Current.SetTitleBar(BackgroundElement);

上面的代码指定TitlaBar中的BackgroundElement元素为可拖动区域,而下面的StackPanel则用于放置交互内容,例如标题或后退按钮。这个StackPanel必须比BackgroundElement具有较高的Z顺序才能接收到用户的鼠标输入。

7. 标题的系统保留区域

标题栏的右边有188像素的系统保留区域,用于系统标题按钮(“后退”、“最小化”、“最大化”、“关闭”)。其实这几个按钮也就占用了141像素的控件,还有一小块空间是默认的可拖动区域,这小块空间确保了无论怎么设置都总有一个用户可拖动的区域。

上面说的188像素是100%缩放的情况,通过上面的截图可以看到实际上可能不一样,通常来说会在窗体加载时,或者订阅CoreApplicationViewTitleBar.LayoutMetricsChanged事件,然后通过CoreApplicationViewTitleBar获取具体的值。

  1. _coreTitleBar.LayoutMetricsChanged += OnLayoutMetricsChanged;
  2. private void OnLayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args)
  3. {
  4. LayoutRoot.Height = _coreTitleBar.Height;
  5. SetTitleBarPadding();
  6. }
  7. private void SetTitleBarPadding()
  8. {
  9. double leftAddition = 0;
  10. double rightAddition = 0;
  11. if (FlowDirection == FlowDirection.LeftToRight)
  12. {
  13. leftAddition = _coreTitleBar.SystemOverlayLeftInset;
  14. rightAddition = _coreTitleBar.SystemOverlayRightInset;
  15. }
  16. else
  17. {
  18. leftAddition = _coreTitleBar.SystemOverlayRightInset;
  19. rightAddition = _coreTitleBar.SystemOverlayLeftInset;
  20. }
  21. LayoutRoot.Padding = new Thickness(leftAddition, 0, rightAddition, 0);
  22. }

8. 可交互区域的内容

上面的StackPanel是可交互区域,详细的内容如下:

  1. <StackPanel Orientation="Horizontal">
  2. <StackPanel x:Name="ItemsPanel" Orientation="Horizontal">
  3. <StackPanel.Resources>
  4. <Style TargetType="Button"
  5. BasedOn="{StaticResource NavigationBackButtonNormalStyle}">
  6. <Setter Property="Foreground"
  7. Value="{StaticResource TitleBarForeground}" />
  8. <Setter Property="FontSize"
  9. Value="10" />
  10. <Setter Property="Width"
  11. Value="46" />
  12. <Setter Property="Height"
  13. Value="32" />
  14. <Setter Property="IsTabStop"
  15. Value="False" />
  16. </Style>
  17. </StackPanel.Resources>
  18. </StackPanel>
  19. <TextBlock x:Name="AppName"
  20. x:Uid="AppName"
  21. Text="ExtendViewIntoTitleBarDemo"
  22. Margin="12,0,12,0"
  23. HorizontalAlignment="Left"
  24. VerticalAlignment="Center"
  25. Foreground="{ThemeResource SystemControlPageTextBaseHighBrush}"
  26. FontSize="12"
  27. IsHitTestVisible="False"
  28. TextAlignment="Left"
  29. TextTrimming="CharacterEllipsis" />
  30. </StackPanel>

其中AppName用于显示标题栏,ItemsPanel用于放其它按钮。TitleBar里定义了Buttons属性,调用TitleBar可以通过Buttons属性指定按钮(这部分代码我凌晨两点写的,写得十分敷衍,但写完又懒得改了)。

  1. public ObservableCollection<Button> Buttons { get; } = new ObservableCollection<Button>();
  2. private void OnButtonsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  3. {
  4. ItemsPanel.Children.Clear();
  5. foreach (var button in Buttons)
  6. {
  7. ItemsPanel.Children.Add(button);
  8. }
  9. }
  1. <local:TitleBar>
  2. <local:TitleBar.Buttons>
  3. <Button x:Name="OptionsButton"
  4. Content=""
  5. ToolTipService.ToolTip="Options" />
  6. <Button Content=""
  7. ToolTipService.ToolTip="Options" />
  8. <Button Content=""
  9. ToolTipService.ToolTip="Options" />
  10. <Button Content=""
  11. ToolTipService.ToolTip="Options" />
  12. </local:TitleBar.Buttons>
  13. </local:TitleBar>

按钮的样式来自NavigationBackButtonNormalStyle并稍作修改,大致上做到和标准的标题栏按钮一样。

9. 非激活状态的标题栏颜色

当窗体处于非激活状态应该让按钮和标题都变灰,可以订阅WindowActivated事件,在非激活状态时改变颜色:

  1. Window.Current.Activated += OnWindowActivated;
  2. private void OnWindowActivated(Object sender, WindowActivatedEventArgs e)
  3. {
  4. VisualStateManager.GoToState(
  5. this, e.WindowActivationState == CoreWindowActivationState.Deactivated ? WindowNotFocused.Name : WindowFocused.Name, false);
  6. }
  1. <UserControl.Resources>
  2. <SolidColorBrush x:Key="TitleBarForeground"
  3. x:Name="TitleBarForeground"
  4. Color="{ThemeResource SystemBaseHighColor}" />
  5. </UserControl.Resources>
  6. <Grid x:Name="LayoutRoot"
  7. Height="32"
  8. HorizontalAlignment="Stretch">
  9. <VisualStateManager.VisualStateGroups>
  10. <VisualStateGroup x:Name="WindowFocusStates">
  11. <VisualState x:Name="WindowFocused" />
  12. <VisualState x:Name="WindowNotFocused">
  13. <VisualState.Setters>
  14. <Setter Target="AppName.Foreground"
  15. Value="{ThemeResource SystemControlForegroundChromeDisabledLowBrush}" />
  16. <Setter Target="TitleBarForeground.Color"
  17. Value="{ThemeResource SystemChromeDisabledLowColor}" />
  18. </VisualState.Setters>
  19. </VisualState>
  20. </VisualStateGroup>
  21. </VisualStateManager.VisualStateGroups>

10. 全屏和平板模式

当应用在全屏或平板模式下运行时,系统将隐藏标题栏和标题控制按钮。 但是,用户可以调用标题栏,以使其以覆盖形式显示在应用的 UI 顶部。 你可以处理隐藏或调用标题栏时将通知的 CoreApplicationViewTitleBar.IsVisibleChanged 事件,并根据需要显示或隐藏你的自定义标题栏内容。

  1. 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

[UWP]占领标题栏的更多相关文章

  1. UWP更改标题栏颜色

    你是否因为UWP标题栏太丑而想过改变?那么这篇文章或许可以帮助你美化UWP界面,让你的UWP的标题栏也变好看 这里的代码,都要在MainPage的构造函数中.如果你在App类中更改了首页,则在该首页的 ...

  2. Win10的UWP之标题栏的返回键(二)

    原文:Win10的UWP之标题栏的返回键(二) 关于Win10的UWP的返回键的第二种处理的方法,是介于标题栏的强行修改,不是像上期的那样直接调用系统内置的API. - - - - - - - - - ...

  3. Win10的UWP之标题栏的返回键(一)

    原文:Win10的UWP之标题栏的返回键(一) 关于返回键,放在标题栏是目前较为完美的一种方案.继前一篇的Hello World,博主进行一些修改实现该方法. - - - - - - - - - - ...

  4. Windows 10 UWP程序标题栏设置

    在Windows 10程序中,以前只能用于全屏方式的Metro程序现在可以运行在窗口模式下了,并且改了个新名字,叫Windows 通用程序(Universal Windows app),简称UWP程序 ...

  5. WPF 应用完全模拟 UWP 的标题栏按钮

    WPF 自定义窗口样式有多种方式,不过基本核心实现都是在修改 Win32 窗口样式.然而,Windows 上的应用就应该有 Windows 应用的样子嘛,在保证自定义的同时也能与其他窗口样式保持一致当 ...

  6. UWP 扩展/自定义标题栏的方法,一些概念和一些注意事项

    原文 UWP 扩展/自定义标题栏的方法,一些概念和一些注意事项 在 Windows 10 的前几个版本中将页面内容扩展到标题栏上还算简单,主要是没什么坑.直到一些新控件的引入和一些外观设计趋势变化之后 ...

  7. win10 uwp 设置启动窗口大小 获取窗口大小

    本文主要说如何设置我们窗口的启动大小,UWP启动窗口大小. 设置启动窗口 设置窗口大小 ApplicationView.PreferredLaunchViewSize = new Size(1000, ...

  8. WPF 很少人知道的科技

    原文:WPF 很少人知道的科技 本文介绍不那么常见的 WPF 相关的知识. 本文内容 在 C# 代码中创建 DataTemplate 多个数据源合并为一个列表显示 使用附加属性做缓存,避免内存泄漏 使 ...

  9. 2018-2-13-win10-uwp-设置启动窗口大小--获取窗口大小

    title author date CreateTime categories win10 uwp 设置启动窗口大小 获取窗口大小 lindexi 2018-2-13 17:23:3 +0800 20 ...

随机推荐

  1. vue.js带复选框表单的增删改查

    近段时间由于公司项目要求,前端开始使用VUE框架进行开发,最近刚开始学习,做了一个表单的增删改查,和大家分享一下. 页面模型代码设计如下 <template> <div id=&qu ...

  2. 第一篇博客 安装open live writer

    第一篇博客安装open live writer http://openlivewriter.org/ 有的人可能会打不开,所以我准备了一个百度云的链接地址 链接:https://pan.baidu.c ...

  3. rabbitMQ_topic(五)

    主题转发器 发送到主题转发器的消息不能有任意的 routing_key - 它必须是由点分隔的单词列表.这些单词可以是任何东西,但通常它们指定与消息相关联的一些功能.几个有效的routeKey示例:“ ...

  4. Eclipse "R cannot be resolved"问题

    前两天 Eclipse 又遇到了这个问题.网上找了不少,不过最终还是没能解决我的问题,无奈重装了 Eclipse…… 搜索中找到了下面这几篇文章,常见的解决方法都在这里,还是不错的,分享一下: htt ...

  5. 基于zookeeper集群的云平台-配置中心的功能设计

    最近准备找工作面试,就研究了下基于zookeeper集群的配置中心. 下面是自己设想的关于开源的基于zookeeper集群的云平台-配置中心的功能设计.大家觉得哪里有问题,请提出宝贵的意见和建议,谢谢 ...

  6. NDK jni mk文件 so文件 巴啦啦 初体验

    概念JNI(Java Native Interface,Java本地接口),实现了Java和其他语言的交互(主要是C/C++),如:Java程序通过JNI调用C/C++编写的在Windows上运行的D ...

  7. 在线图片base64编码

    图片Base64编码https://oktools.net/image2base64 在线工具https://oktools.net JSON格式化https://oktools.net/json U ...

  8. Activity 使用详解

    极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以 ...

  9. Netty服务端启动过程相关源码分析

    1.Netty 是怎么创建服务端Channel的呢? 我们在使用ServerBootstrap.bind(端口)方法时,最终调用其父类AbstractBootstrap中的doBind方法,相关源码如 ...

  10. AndroidSDK的目录详解

    Tools 目录工具(必须的工具) Android SDK Tools(必须,只需下载一个版本,一般选最新版本):基础工具包,版本号带rc字样的是预览版. Android SDK Platform-t ...