• [UWP]占领标题栏


    1. 前言

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

    2.示例代码

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

    3. 简单的颜色自定义

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

    // using Windows.UI.ViewManagement;
    
    var titleBar = ApplicationView.GetForCurrentView().TitleBar;
    
    // Set active window 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 window 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内容获取鼠标,可以用Window.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>
    
    Window.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="&#xE10C;"
                    ToolTipService.ToolTip="Options" />
            <Button Content="&#xE11C;"
                    ToolTipService.ToolTip="Options" />
            <Button Content="&#xE13C;"
                    ToolTipService.ToolTip="Options" />
            <Button Content="&#xE12C;"
                    ToolTipService.ToolTip="Options" />
        </local:TitleBar.Buttons>
    </local:TitleBar>
    

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

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

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

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

  • 相关阅读:
    Python教程(2.2)——数据类型与变量
    Python教程(2.1)——控制台输入
    Python教程(1.2)——Python交互模式
    (译)割点
    Python教程(1.1)——配置Python环境
    Python教程(0)——介绍
    [HDU1020] Encoding
    [HDU1004] Let the balloon rise
    扩展中国剩余定理 exCRT 学习笔记
    51nod 1943 联通期望 题解【枚举】【二进制】【概率期望】【DP】
  • 原文地址:https://www.cnblogs.com/dino623/p/uwp-title-bar.html
Copyright © 2020-2023  润新知