标题栏自定义

Windows 为每个窗口提供一个默认标题栏,并支持对其进行自定义以匹配应用的个性。 默认标题栏附带了一些标准组件和核心功能,例如拖动窗口和调整窗口大小。

A Windows app showing the title bar

有关自定义应用标题栏、可接受的标题栏区域内容和推荐的 UI 模式的指导,请参阅标题栏设计一文。

重要

本文介绍如何为使用 Windows 应用 SDK(无论是否通过 WinUI 3)的应用自定义标题栏。 有关使用 UWP 和 WinUI 2 的应用,请参阅适用于 UWP 的标题栏自定义

标题栏组件

此列表描述了标准标题栏的组件。

  • 标题栏矩形
  • 标题文本
  • 系统图标
  • 系统菜单 - 通过单击应用图标或右键单击标题栏来访问
  • 标题控件
    • 最小化按钮
    • 最大化/还原按钮
    • “关闭”按钮

窗口化

Windows 应用 SDK 中的窗口功能是通过基于 Win32 HWND 模型的 Microsoft.UI.Windowing.AppWindow 类来实现的。 应用中的 AppWindow 与顶级 HWND 之间存在 1:1 映射关系。 AppWindow 及其相关类提供的 API 可支持你管理应用顶级窗口的许多方面,其中包括自定义标题栏。 你可以修改 Windows 提供的默认标题栏,以使其与 UI 的其余部分混合,或将应用画布扩展到标题栏区域并提供你自己的标题栏内容。

WinUI 3 中的窗口功能是通过 Microsoft.UI.Xaml.Window 类来实现的,而它也是基于 Win32 HWND 模型。 对于使用 WinUI 3 的 XAML 应用,XAML 窗口 API 提供了一种更简单的方式来自定义标题栏,同时仍支持你在需要时访问 AppWindow API。

如何使用 AppWindow

可以将 AppWindow API 与 Windows 应用 SDK 支持的任何 UI 框架(Win32、WPF、WinForms 或 WinUI 3)配合使用,并且只能使用所需的 API 以增量方式采用它们。

如果使用 WinUI 3 XAML 作为应用的 UI 框架,则 WindowAppWindow API 均可供你使用。 从 Windows 应用 SDK 1.4 开始,XAML 窗口和 AppWindow 可使用相同的 AppWindowTitleBar 对象进行标题栏自定义。 请使用 Window.AppWindow 属性从现有 XAML 窗口获取 AppWindow 对象。 使用此 AppWindow 对象,可以访问标题栏自定义 API。 要访问标题栏的其他功能,可以从 XAML 窗口使用 AppWindow API,如下所示:AppWindow.TitleBar.ForegroundColor = Colors.White;

如果不使用 WinUI 3 1.3 或更高版本,请使用互操作 API 获取 AppWindow 并使用 AppWindow API 自定义标题栏。 有关互操作 API 的详细信息,请参阅管理应用窗口 - UI 框架和 HWND 互操作以及窗口化库示例

自定义标题栏的程度

可将两个级别的自定义应用于标题栏:对默认标题栏应用轻微修改,或将应用画布扩展到标题栏区域并提供完全自定义的内容。

简单

对于简单的自定义,例如更改标题栏颜色,可以在 AppWindowTitleBar 对象上设置属性,以指定要用于标题栏元素的颜色。 在此情况下,系统仍对标题栏的所有其他方面负责,例如绘制应用标题和定义拖动区域。

完全

你的其他选项是隐藏默认系统标题栏并将其替换为自己的自定义内容。 例如,你可以在标题栏区域中放置文本、搜索框或自定义菜单。 还需要使用此选项将材料背景(如 Mica)扩展到标题栏区域中。

当你选择完全自定义时,你将负责将内容放在标题栏区域中,并且可以定义自己的拖动区域。 标题控件(系统关闭、最小化和最大化按钮)仍然可用并由系统处理,但应用标题等元素则不可用。 你需要根据应用的需求自行创建这些元素。

简单自定义

如果只想自定义标题栏标题、颜色或图标,则可以在应用窗口的标题栏对象上设置属性。

Title

默认情况下,标题栏将应用类型显示为窗口标题(例如“WinUI Desktop”)。 你应更新窗口标题以显示应用的有意义显示名称。

XAML 应用具有在文件 Package.appxmanifest 中设置的显示名称。 可以获取此值,并使用它按如下所示设置 Title 属性。

Title = AppInfo.Current.DisplayInfo.DisplayName;

要更改窗口标题,请将 Window.Title 属性设置为单行文本值,如此处所示。

<Window
    ...
    Title="App title">
    ...
</Window>
public MainWindow()
{
    InitializeComponent();
    Title = "App title";
}

要使用 AppWindow API 更改窗口标题,请将 AppWindow.Title 属性设置为单行文本值,如下所示。 此示例演示如何使用互操作 API 获取 AppWindow,如果你的应用使用 WinUI 3 1.3 或更高版本,则会需要使用它。

using Microsoft.UI;           // Needed for WindowId.
using Microsoft.UI.Windowing; // Needed for AppWindow.
using WinRT.Interop;          // Needed for XAML/HWND interop.

private AppWindow m_AppWindow;

public MainWindow()
{
    this.InitializeComponent();

    m_AppWindow = GetAppWindowForCurrentWindow();
    m_AppWindow.Title = "App title";
}

private AppWindow GetAppWindowForCurrentWindow()
{
    IntPtr hWnd = WindowNative.GetWindowHandle(this);
    WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
    return AppWindow.GetFromWindowId(wndId);
}

颜色

要自定义默认标题栏颜色或更改默认窗口图标,需要使用 AppWindow API 或选择完全自定义标题栏。

此示例显示了如何获取 AppWindowTitleBar 的实例以及如何设置其颜色属性。

重要

当应用在 Windows 10 上运行时,将忽略颜色自定义。

// Assumes "this" is a XAML Window. In projects that don't use 
// WinUI 3 1.3 or later, use interop APIs to get the AppWindow.
AppWindow m_AppWindow = this.AppWindow;

private bool SetTitleBarColors()
{
    // Check to see if customization is supported.
    // The method returns true on Windows 10 since Windows App SDK 1.2,
    // and on all versions of Windows App SDK on Windows 11.
    if (AppWindowTitleBar.IsCustomizationSupported())
    {
        AppWindowTitleBar m_TitleBar = m_AppWindow.TitleBar;

        // Set active window colors.
        // Note: No effect when app is running on Windows 10
        // because color customization is not supported.
        m_TitleBar.ForegroundColor = Colors.White;
        m_TitleBar.BackgroundColor = Colors.Green;
        m_TitleBar.ButtonForegroundColor = Colors.White;
        m_TitleBar.ButtonBackgroundColor = Colors.SeaGreen;
        m_TitleBar.ButtonHoverForegroundColor = Colors.Gainsboro;
        m_TitleBar.ButtonHoverBackgroundColor = Colors.DarkSeaGreen;
        m_TitleBar.ButtonPressedForegroundColor = Colors.Gray;
        m_TitleBar.ButtonPressedBackgroundColor = Colors.LightGreen;

        // Set inactive window colors.
        // Note: No effect when app is running on Windows 10
        // because color customization is not supported.
        m_TitleBar.InactiveForegroundColor = Colors.Gainsboro;
        m_TitleBar.InactiveBackgroundColor = Colors.SeaGreen;
        m_TitleBar.ButtonInactiveForegroundColor = Colors.Gainsboro;
        m_TitleBar.ButtonInactiveBackgroundColor = Colors.SeaGreen;
        return true;
    }
    return false;
}

设置标题栏颜色时需要注意以下几点:

  • 按钮背景颜色不会应用于关闭按钮的悬停按下状态。 对于这些状态,关闭按钮始终使用系统定义的颜色。
  • 将颜色属性设置为 null 会将其重置为默认系统颜色。
  • 无法设置透明颜色。 将忽略颜色的 alpha 通道。

Windows 的用户可以选择将所选主题色应用于标题栏。 如果要设置任何标题栏颜色,我们建议显式设置所有颜色。 这可确保不会因为用户定义的颜色设置而出现意外的颜色组合。

图标和系统菜单

可以隐藏系统图标,或将其替换为自定义图标。 系统图标在右键单击或点击一次时会显示系统菜单。 双击/点击时,它会关闭窗口。

要显示或隐藏系统图标和相关行为,请设置标题栏 IconShowOptions 属性。

m_TitleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu;

要使用自定义窗口图标,请调用 AppWindow.SetIcon 方法之一来设置新图标。

  • SetIcon(String)

    SetIcon(String) 方法当前仅适用于 .ico 文件。 传递给此方法的字符串是 .ico 文件的完全限定路径。

    m_AppWindow.SetIcon("iconPath/iconName.ico");
    
  • SetIcon(IconId)

    如果已经从一个图标函数(如 CreateIcon)中获得一个图标(HICON)的图柄,则可以使用 GetIconIdFromIcon 互操作 API 来获取 IconId。 然后,可以将 IconId 传递给 SetIcon(IconId) 方法来设置窗口图标。

    m_AppWindow.SetIcon(iconId));
    

完全自定义

当你选择进行标题栏完全自定义时,应用的客户端区域会进行扩展以覆盖整个窗口,包括标题栏区域。 你负责绘制整个窗口和对其进行输出处理进行绘制和输入处理(标题按钮除外,它们仍由窗口提供)。

要隐藏系统标题栏并将内容扩展到标题栏区域,对于将应用内容扩展到标题栏区域的属性,请将其设置为 true。 在 XAML 应用中,可以在应用的 OnLaunched 方法 (App.xaml.cs) 或应用的首页中设置此属性。

提示

请参阅“完全自定义示例”部分,以一次性查看所有代码

此示例演示如何将 Window.ExtendsContentIntoTitleBar 属性设置为 true

public MainWindow()
{
    this.InitializeComponent();

    // Hide system title bar.
    ExtendsContentIntoTitleBar = true;
}

注意

ExtendsContentIntoTitleBar 会为 Window 显示在 XAML IntelliSense 中,但在 XAML 中设置它会导致错误。 请改为在代码中设置此属性。

此示例演示如何获取 AppWindowTitleBar,并将 AppWindow.ExtendsContentIntoTitleBar 属性设置为 true。 此示例演示如何使用互操作 API 获取 AppWindow,如果你的应用不使用 WinUI 3 1.3 或更高版本,则会需要它。

using Microsoft.UI;           // Needed for WindowId.
using Microsoft.UI.Windowing; // Needed for AppWindow.
using WinRT.Interop;          // Needed for XAML/HWND interop.

private AppWindow m_AppWindow;

public MainWindow()
{
    this.InitializeComponent();

    m_AppWindow = GetAppWindowForCurrentWindow();
    var titleBar = m_AppWindow.TitleBar;
    // Hide system title bar.
    titleBar.ExtendsContentIntoTitleBar = true;
}

private AppWindow GetAppWindowForCurrentWindow()
{
    IntPtr hWnd = WindowNative.GetWindowHandle(this);
    WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
    return AppWindow.GetFromWindowId(wndId);
}

标题栏内容和默认拖动区域

当应用扩展到标题栏区域时,你将负责定义和管理标题栏的 UI。 这通常至少包括指定标题文本和拖动区域。 标题栏的拖动区域定义了用户可以单击和拖动以移动窗口的位置。 这也是用户可以右键单击以显示系统菜单的位置。

要详细了解可接受的标题栏内容和建议的 UI 模式,请参阅标题栏设计

此示例演示不带交互式内容的自定义标题栏 UI 的 XAML。

<Grid x:Name="AppTitleBar"  
      Height="32">
    <Grid.ColumnDefinitions>
        <ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
        <ColumnDefinition/>
        <ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
    </Grid.ColumnDefinitions>
    <Image x:Name="TitleBarIcon" Source="ms-appx:///Assets/StoreLogo.png"
           Grid.Column="1"
           HorizontalAlignment="Left"
           Width="16" Height="16"
           Margin="8,0,0,0"/>
    <TextBlock x:Name="TitleBarTextBlock" 
               Text="App title" 
               Style="{StaticResource CaptionTextBlockStyle}"
               Grid.Column="1"
               VerticalAlignment="Center"
               Margin="28,0,0,0"/>
</Grid>

重要

LeftPaddingColumnRightPaddingColumn 用于保留标题按钮的空间。 这些列的 Width 值是在代码中设置的,如稍后部分所示。 有关代码和说明,请参阅系统标题按钮部分。

XAML 应用的显示名称是在 Package.appxmanifest 文件中设置的。 你可以获取此值并将其用于自定义标题栏中,如下所示。

TitleBarTextBlock.Text = AppInfo.Current.DisplayInfo.DisplayName;

将内容扩展到标题栏区域时,系统会隐藏系统标题栏,并创建一个默认的 AppWindowTitleBar,它可以提供与系统标题栏相同的屏幕宽度标题按钮和拖动区域。 如果未在标题栏中放置交互式内容,则可以按原样保留此默认拖动区域。 如果在标题栏中放置交互式内容,则需要指定交互式区域,我们将在下一部分中介绍这些区域。

注意

定义自定义拖动区域时,它们不必位于默认标题栏区域中的窗口顶部;你可以将 UI 的任何部分定义为拖动区域。 但将拖动区域放置在不同的位置可能会导致用户难以发现它们。

交互式内容

可以将交互式控件(如按钮、菜单或搜索框)放在应用顶部,以便它们显示在标题栏中。 但需要指定哪些区域是交互式的,以确保交互式元素接收用户输入,同时仍允许用户移动窗口。

A Windows app with a search box in the title bar

在标题栏区域中添加交互式内容时,需要使用 InputNonClientPointerSource 类指定将输入传递到交互式控件的区域,而不是由标题栏处理。 要设置交互式区域,请调用 InputNonClientPointerSource.SetRegionRects 方法。 此方法采用的值指定了要设置的区域类型(在本例中为 Passthrough)以及一个矩形数组,其中每个矩形定义一个 Passthrough 区域。 当标题栏的大小发生更改时,需要重新计算交互式区域以匹配新的大小,并使用新值调用 SetRegionRects

本示例显示了一个带有搜索框和 PersonPicture 帐户控件的自定义标题栏 UI。 它演示了如何计算和设置这些控件的交互式矩形,以便将输入传递给它们。

下面是有关此代码需注意的一些要点:

  • AppTitleBar 网格高度设置为 48,以遵循适用于交互式内容的标题栏设计指导。
  • PreferredHeightOption 设置为 Tall,以便标题按钮的高度与标题栏相同。
  • 要简化控件的大小调整和区域的计算,请对布局使用 Grid 和多个命名列。
  • 对包含 AutoSuggestBox 的列使用星形标记 (*) 大小调整和 MinWidth,以便它自动调整窗口的大小。
  • RightDragColumn 上设置 MinWidth,以保留一个始终可拖动的小区域,即使调整窗口大小也是如此。
  • 在 MainWindow 构造函数中将 ExtendsContentIntoTitleBar 设置为 true。 如果在稍后调用的代码中设置它,则可能会先显示默认系统标题栏,然后再将其隐藏。
  • 在加载 AppTitleBar 元素后进行初始调用以计算交互式区域。 否则,无法保证用于计算的元素会具有其正确的值。
  • 仅在 AppTitleBar 元素更改大小(AppTitleBar_SizeChanged)之后更新交互式矩形计算。 如果依赖于窗口 Changed 事件,则在某些情况下(如窗口最大化/最小化),事件会发生在调整 AppTitleBar 大小之前,并且计算将使用不正确的值。
  • 仅在检查 ExtendsContentIntoTitleBar 以确认正在使用自定义标题栏后设置自定义拖动/交互区域。
<Grid x:Name="AppTitleBar"
      Height="48">
    <Grid.ColumnDefinitions>
        <ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
        <ColumnDefinition x:Name="IconColumn" Width="Auto"/>
        <ColumnDefinition x:Name="TitleColumn" Width="Auto"/>
        <ColumnDefinition x:Name="LeftDragColumn" Width="*"/>
        <ColumnDefinition x:Name="SearchColumn" Width="4*" MinWidth="220"/>
        <ColumnDefinition x:Name="RightDragColumn" Width="*" MinWidth="48"/>
        <ColumnDefinition x:Name="AccountColumn" Width="Auto"/>
        <ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
    </Grid.ColumnDefinitions>
    <Image x:Name="TitleBarIcon" 
           Source="ms-appx:///Assets/StoreLogo.png"
           Grid.Column="1"
           Width="16" Height="16"
           Margin="8,0,4,0"/>
    <TextBlock x:Name="TitleBarTextBlock"
               Text="App title" 
               Style="{StaticResource CaptionTextBlockStyle}"
               Grid.Column="2"
               VerticalAlignment="Center">
    </TextBlock>
    <AutoSuggestBox x:Name="TitleBarSearchBox" 
                    Grid.Column="4" 
                    QueryIcon="Find"
                    PlaceholderText="Search"
                    VerticalAlignment="Center"
                    MaxWidth="600"/>
    <PersonPicture x:Name="PersonPic" 
                   Grid.Column="6" 
                   Height="32" Margin="0,0,16,0"/>
</Grid>

此代码演示如何计算和设置对应于 AutoSuggestBoxPersonPicture 控件的交互区域。

public sealed partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();

        // Assumes "this" is a XAML Window. In projects that don't use 
        // WinUI 3 1.3 or later, use interop APIs to get the AppWindow.
        m_AppWindow = this.AppWindow;
        AppTitleBar.Loaded += AppTitleBar_Loaded;
        AppTitleBar.SizeChanged += AppTitleBar_SizeChanged;
        ExtendsContentIntoTitleBar = true;
        TitleBarTextBlock.Text = AppInfo.Current.DisplayInfo.DisplayName;
    }

    private void AppTitleBar_Loaded(object sender, RoutedEventArgs e)
    {
        if (ExtendsContentIntoTitleBar == true)
        {
            // Set the initial interactive regions.
            SetRegionsForCustomTitleBar();
        }
    }

    private void AppTitleBar_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (ExtendsContentIntoTitleBar == true)
        {
            // Update interactive regions if the size of the window changes.
            SetRegionsForCustomTitleBar();
        }
    }

    private void SetRegionsForCustomTitleBar()
    {
        // Specify the interactive regions of the title bar.

        double scaleAdjustment = AppTitleBar.XamlRoot.RasterizationScale;

        RightPaddingColumn.Width = new GridLength(m_AppWindow.TitleBar.RightInset / scaleAdjustment);
        LeftPaddingColumn.Width = new GridLength(m_AppWindow.TitleBar.LeftInset / scaleAdjustment);

        GeneralTransform transform = TitleBarSearchBox.TransformToVisual(null);
        Rect bounds = transform.TransformBounds(new Rect(0, 0, 
                                                         TitleBarSearchBox.ActualWidth,
                                                         TitleBarSearchBox.ActualHeight));
        Windows.Graphics.RectInt32 SearchBoxRect = GetRect(bounds, scaleAdjustment);
        
        transform = PersonPic.TransformToVisual(null);
        bounds = transform.TransformBounds(new Rect(0, 0,
                                                    PersonPic.ActualWidth,
                                                    PersonPic.ActualHeight));
        Windows.Graphics.RectInt32 PersonPicRect = GetRect(bounds, scaleAdjustment);

        var rectArray = new Windows.Graphics.RectInt32[] { SearchBoxRect, PersonPicRect };

        InputNonClientPointerSource nonClientInputSrc =
            InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
        nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
    }

    private Windows.Graphics.RectInt32 GetRect(Rect bounds, double scale)
    {
        return new Windows.Graphics.RectInt32(
            _X: (int)Math.Round(bounds.X * scale),
            _Y: (int)Math.Round(bounds.Y * scale),
            _Width: (int)Math.Round(bounds.Width * scale),
            _Height: (int)Math.Round(bounds.Height * scale)
        );
    }
}

警告

AppWindow 使用物理像素,以兼容不使用逻辑坐标的 UI 框架。 如果使用 WPF 或 WinUI 3,则如果显示比例不是 100%,将需要调整 RightInsetLeftInset 和用于计算区域的值。 在此示例中,我们会获取一个 scaleAdjustment 值来考虑显示比例设置。

系统标题按钮

系统会为系统标题按钮(最小化、最大化/还原和关闭)保留应用窗口的左上角或右上角。 系统会保留对标题按钮区域的控制,以保证提供最少功能来拖动、最小化、最大化和关闭窗口。 系统为从左到右的语言在右上角绘制关闭按钮,为从右到左的语言在左上角绘制该按钮。

可以在标题控件区域(如应用背景)下绘制内容,但不应放置任何你期望用户能够与之交互的 UI。 它不会收到任何输入,因为标题控件的输入由系统处理。

上一示例中的这些行显示了 XAML 中定义标题栏的填充列。 使用填充列而不是边距可确保背景绘制标题控件按钮下的区域(适用于透明按钮)。 同时使用左右填充列可确保标题栏在从右到左和从左到右两种布局中都会正常运行。

<Grid.ColumnDefinitions>
    <ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
    <ColumnDefinition/>
    <ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
</Grid.ColumnDefinitions>

标题控件区域的维度和位置由 AppWindowTitleBar 类传达,以便你可以在标题栏 UI 的布局中将它考虑在内。 每侧保留区域的宽度由 leftInsetRightInset 属性提供,其高度由 Height 属性提供。

下面介绍计算和设置拖动区域时如何指定填充列的宽度。

RightPaddingColumn.Width = 
    new GridLength(m_AppWindow.TitleBar.RightInset / scaleAdjustment);
LeftPaddingColumn.Width = 
    new GridLength(m_AppWindow.TitleBar.LeftInset / scaleAdjustment);

重要

请参阅“交互式内容”部分中的重要信息,以了解显示缩放如何影响这些值。

对自定义标题栏的高标题栏支持

在标题栏中添加交互式内容(如搜索框或人物图片)时,建议增加标题栏的高度,以便为这些元素提供更多空间。 更高的标题栏也会让触摸操作更容易。 借助 AppWindowTitleBar.PreferredHeightOption 属性,可以选择将标题栏高度从标准高度(默认值)增加到更高的高度。 选择 Tall 标题栏模式时,系统在客户区中以覆盖层形式绘制的标题按钮会呈现得更高,并且其最小/最大/关闭字形会居中。 如果尚未指定拖动区域,系统将绘制一个,以扩展窗口宽度和由你设置的 PreferredHeightOption 值确定的高度。

此示例演示如何设置 PreferredHeightOption 属性。

// A taller title bar is only supported when drawing a fully custom title bar.
if (ExtendsContentIntoTitleBar == true)
{
    m_AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
}

注意

AppWindowTitleBar.ExtendsContentIntoTitleBar 属性必须为 true,然后才能设置 PreferredHeightOption 属性。 如果在 ExtendsContentIntoTitleBarfalse 时尝试设置 PreferredHeightOption,将会引发异常。

标题按钮中的颜色和透明度

将应用内容扩展到标题栏区域时,可以将标题按钮的背景设为透明,以便让应用背景显示出来。 通常将背景设置为 Colors.Transparent 才能实现完全透明度。 对于部分透明,请为你将属性设置的颜色设置 alpha 通道。

这些标题栏属性可以是透明的:

所有其他颜色属性将继续忽略 alpha 通道。 如果将 ExtendsContentIntoTitleBar 设置为 false,则所有 AppWindowTitleBar 颜色属性将始终忽略 alpha 通道。

按钮背景色不适用于“关闭”按钮的“悬停”和“按下”状态。 对于这些状态,关闭按钮始终使用系统定义的颜色。

提示

Mica 是一种令人愉快的材料,有助于区分位于焦点的窗口。 建议将其作为 Windows 11 中长期活动窗口的背景。 如果在窗口的工作区中应用了 Mica,则可以将其扩展到标题栏区域,并将标题按钮设为透明,以便 Mica 显示出来。 有关详细信息,请参阅 Mica 材料

当窗口处于非活动状态时,将标题栏变暗

应当清晰显示窗口何时处于活动状态,何时处于非活动状态。 至少应更改标题栏中文本、图标和按钮的颜色。

对于 XAML 应用,请处理 Window.Activated 事件以确定窗口的激活状态,并根据需要更新标题栏 UI。

public MainWindow()
{
    ...
    Activated += MainWindow_Activated;
}

private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
    if (args.WindowActivationState == WindowActivationState.Deactivated)
    {
        TitleBarTextBlock.Foreground =
            (SolidColorBrush)App.Current.Resources["WindowCaptionForegroundDisabled"];
    }
    else
    {
        TitleBarTextBlock.Foreground =
            (SolidColorBrush)App.Current.Resources["WindowCaptionForeground"];
    }
}

对于其他 UI 框架,请处理事件以确定窗口的激活状态,并根据需要更新标题栏 UI。 如何确定窗口的状态取决于用于应用的 UI 框架。

重置标题栏

要在应用运行时重置或切换到系统标题栏,可以调用 AppWindowTitleBar.ResetToDefault

m_AppWindow.TitleBar.ResetToDefault();

对于 XAML 应用,还可以通过以下方式重置标题栏:

  • 调用 SetTitleBar,以在应用运行时切换到新的标题栏元素。
  • 使用 null 作为参数调用 SetTitleBar,以重置为默认 AppWindowTitleBar 拖动区域。
  • 使用 null 作为参数调用 SetTitleBar,并将 ExtendsContentIntoTitleBar 设为 false 以还原到默认系统标题栏。

显示和隐藏标题栏

如果向应用添加对全屏紧凑覆盖模式的支持,则当应用在这些模式之间切换时可能需要更改标题栏。 XAML 窗口不提供任何 API 来支持全屏模式;为此,可以使用 AppWindow API。

当应用在全屏模式下运行时,系统将隐藏标题栏和标题控件按钮。 可以处理 AppWindow.Changed 事件,并检查事件参数 DidPresenterChange 属性,以确定是否应显示、隐藏或更改标题栏以响应新的窗口演示。

此示例演示如何处理 Changed 事件以显示和隐藏前面示例中的 AppTitleBar 元素。 如果窗口处于紧凑覆盖模式,则标题栏将重置为默认系统标题栏(或者可以提供专为紧凑覆盖而优化的自定义标题栏)。

public MainWindow()
{
    this.InitializeComponent();

    m_AppWindow = this.AppWindow;
    m_AppWindow.Changed += AppWindow_Changed;
}

private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
    if (args.DidPresenterChange)
    {
        switch (sender.Presenter.Kind)
        {
            case AppWindowPresenterKind.CompactOverlay:
                // Compact overlay - hide custom title bar
                // and use the default system title bar instead.
                AppTitleBar.Visibility = Visibility.Collapsed;
                sender.TitleBar.ResetToDefault();
                break;

            case AppWindowPresenterKind.FullScreen:
                // Full screen - hide the custom title bar
                // and the default system title bar.
                AppTitleBar.Visibility = Visibility.Collapsed;
                sender.TitleBar.ExtendsContentIntoTitleBar = true;
                break;

            case AppWindowPresenterKind.Overlapped:
                // Normal - hide the system title bar
                // and use the custom title bar instead.
                AppTitleBar.Visibility = Visibility.Visible;
                sender.TitleBar.ExtendsContentIntoTitleBar = true;
                break;

            default:
                // Use the default system title bar.
                sender.TitleBar.ResetToDefault();
                break;
        }
    }
}

注意

仅当应用支持时,才能进入全屏紧凑覆盖模式。 有关详细信息,请参阅管理应用窗口FullScreenPresenterCompactOverlayPresenter

应做事项和禁止事项

  • 务必清楚地显示窗口何时处于活动状态,何时处于非活动状态。 至少要更改标题栏中文本、图标和按钮的颜色。
  • 请务必在应用画布的上边缘定义拖动区域。 匹配系统标题栏的位置会便于用户查找。
  • 务必定义一个拖动区域,以匹配应用画布上的可视标题栏(如果有)。

完全自定义示例

此示例显示了“完全自定义”部分中介绍的所有代码。

<Window
    x:Class="WinUI3_CustomTitleBar.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Grid x:Name="AppTitleBar"
      Height="48">
            <Grid.ColumnDefinitions>
                <ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
                <ColumnDefinition x:Name="IconColumn" Width="Auto"/>
                <ColumnDefinition x:Name="TitleColumn" Width="Auto"/>
                <ColumnDefinition x:Name="LeftDragColumn" Width="*"/>
                <ColumnDefinition x:Name="SearchColumn" Width="4*" MinWidth="220"/>
                <ColumnDefinition x:Name="RightDragColumn" Width="*" MinWidth="48"/>
                <ColumnDefinition x:Name="AccountColumn" Width="Auto"/>
                <ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
            </Grid.ColumnDefinitions>
            <Image x:Name="TitleBarIcon" 
           Source="ms-appx:///Assets/StoreLogo.png"
           Grid.Column="1"
           Width="16" Height="16"
           Margin="8,0,4,0"/>
            <TextBlock x:Name="TitleBarTextBlock"
                       Text="App title" 
                       Style="{StaticResource CaptionTextBlockStyle}"
                       Grid.Column="2"
                       VerticalAlignment="Center">
            </TextBlock>
            <AutoSuggestBox x:Name="TitleBarSearchBox" 
                            Grid.Column="4" 
                            QueryIcon="Find"
                            PlaceholderText="Search"
                            VerticalAlignment="Center"
                            MaxWidth="600"/>
            <PersonPicture x:Name="PersonPic" 
                           Grid.Column="6" 
                           Height="32" Margin="0,0,16,0"/>
        </Grid>

        <NavigationView Grid.Row="1"
                        IsBackButtonVisible="Collapsed"
                        IsSettingsVisible="False">
            <StackPanel>
                <TextBlock Text="Content" 
                           Style="{ThemeResource TitleTextBlockStyle}"
                           Margin="32,0,0,0"/>
                <StackPanel Grid.Row="1" VerticalAlignment="Center">
                    <Button Margin="4" x:Name="CompactoverlaytBtn"
                            Content="Enter CompactOverlay"
                            Click="SwitchPresenter"/>
                    <Button Margin="4" x:Name="FullscreenBtn" 
                            Content="Enter FullScreen"
                            Click="SwitchPresenter"/>
                    <Button Margin="4" x:Name="OverlappedBtn"
                            Content="Revert to default (Overlapped)"
                            Click="SwitchPresenter"/>
                </StackPanel>
            </StackPanel>
        </NavigationView>
    </Grid>
</Window>
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
using Windows.ApplicationModel;
using Rect = Windows.Foundation.Rect;

public sealed partial class MainWindow : Window
{
    private AppWindow m_AppWindow;

    public MainWindow()
    {
        this.InitializeComponent();

        // Assumes "this" is a XAML Window. In projects that don't use 
        // WinUI 3 1.3 or later, use interop APIs to get the AppWindow.
        m_AppWindow = this.AppWindow;
        m_AppWindow.Changed += AppWindow_Changed;
        Activated += MainWindow_Activated;
        AppTitleBar.SizeChanged += AppTitleBar_SizeChanged;
        AppTitleBar.Loaded += AppTitleBar_Loaded;

        ExtendsContentIntoTitleBar = true;
        if (ExtendsContentIntoTitleBar == true)
        {
            m_AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
        }
        TitleBarTextBlock.Text = AppInfo.Current.DisplayInfo.DisplayName;
    }

    private void AppTitleBar_Loaded(object sender, RoutedEventArgs e)
        {
            if (ExtendsContentIntoTitleBar == true)
            {
                // Set the initial interactive regions.
                SetRegionsForCustomTitleBar();
            }
        }

    private void AppTitleBar_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (ExtendsContentIntoTitleBar == true)
            {
                // Update interactive regions if the size of the window changes.
                SetRegionsForCustomTitleBar();
            }
        }

    private void SetRegionsForCustomTitleBar()
    {
        // Specify the interactive regions of the title bar.

        double scaleAdjustment = AppTitleBar.XamlRoot.RasterizationScale;

        RightPaddingColumn.Width = new GridLength(m_AppWindow.TitleBar.RightInset / scaleAdjustment);
        LeftPaddingColumn.Width = new GridLength(m_AppWindow.TitleBar.LeftInset / scaleAdjustment);

        // Get the rectangle around the AutoSuggestBox control.
        GeneralTransform transform = TitleBarSearchBox.TransformToVisual(null);
        Rect bounds = transform.TransformBounds(new Rect(0, 0,
                                                         TitleBarSearchBox.ActualWidth,
                                                         TitleBarSearchBox.ActualHeight));
        Windows.Graphics.RectInt32 SearchBoxRect = GetRect(bounds, scaleAdjustment);

        // Get the rectangle around the PersonPicture control.
        transform = PersonPic.TransformToVisual(null);
        bounds = transform.TransformBounds(new Rect(0, 0,
                                                    PersonPic.ActualWidth,
                                                    PersonPic.ActualHeight));
        Windows.Graphics.RectInt32 PersonPicRect = GetRect(bounds, scaleAdjustment);

        var rectArray = new Windows.Graphics.RectInt32[] { SearchBoxRect, PersonPicRect };

        InputNonClientPointerSource nonClientInputSrc =
            InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
        nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
    }

    private Windows.Graphics.RectInt32 GetRect(Rect bounds, double scale)
    {
        return new Windows.Graphics.RectInt32(
            _X: (int)Math.Round(bounds.X * scale),
            _Y: (int)Math.Round(bounds.Y * scale),
            _Width: (int)Math.Round(bounds.Width * scale),
            _Height: (int)Math.Round(bounds.Height * scale)
        );
    }

    private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
    {
        if (args.WindowActivationState == WindowActivationState.Deactivated)
        {
            TitleBarTextBlock.Foreground =
                (SolidColorBrush)App.Current.Resources["WindowCaptionForegroundDisabled"];
        }
        else
        {
            TitleBarTextBlock.Foreground =
                (SolidColorBrush)App.Current.Resources["WindowCaptionForeground"];
        }
    }

    private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
    {
        if (args.DidPresenterChange)
        {
            switch (sender.Presenter.Kind)
            {
                case AppWindowPresenterKind.CompactOverlay:
                    // Compact overlay - hide custom title bar
                    // and use the default system title bar instead.
                    AppTitleBar.Visibility = Visibility.Collapsed;
                    sender.TitleBar.ResetToDefault();
                    break;

                case AppWindowPresenterKind.FullScreen:
                    // Full screen - hide the custom title bar
                    // and the default system title bar.
                    AppTitleBar.Visibility = Visibility.Collapsed;
                    sender.TitleBar.ExtendsContentIntoTitleBar = true;
                    break;

                case AppWindowPresenterKind.Overlapped:
                    // Normal - hide the system title bar
                    // and use the custom title bar instead.
                    AppTitleBar.Visibility = Visibility.Visible;
                    sender.TitleBar.ExtendsContentIntoTitleBar = true;
                    break;

                default:
                    // Use the default system title bar.
                    sender.TitleBar.ResetToDefault();
                    break;
            }
        }
    }

    private void SwitchPresenter(object sender, RoutedEventArgs e)
    {
        if (AppWindow != null)
        {
            AppWindowPresenterKind newPresenterKind;
            switch ((sender as Button).Name)
            {
                case "CompactoverlaytBtn":
                    newPresenterKind = AppWindowPresenterKind.CompactOverlay;
                    break;

                case "FullscreenBtn":
                    newPresenterKind = AppWindowPresenterKind.FullScreen;
                    break;

                case "OverlappedBtn":
                    newPresenterKind = AppWindowPresenterKind.Overlapped;
                    break;

                default:
                    newPresenterKind = AppWindowPresenterKind.Default;
                    break;
            }

            // If the same presenter button was pressed as the
            // mode we're in, toggle the window back to Default.
            if (newPresenterKind == AppWindow.Presenter.Kind)
            {
                AppWindow.SetPresenter(AppWindowPresenterKind.Default);
            }
            else
            {
                // Else request a presenter of the selected kind
                // to be created and applied to the window.
                AppWindow.SetPresenter(newPresenterKind);
            }
        }
    }
}