标题栏自定义

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

一个Windows应用程序显示标题栏

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

Important

Windows 应用 SDK 1.7 中添加了新的 Title bar 控件。 它简化了标题栏自定义过程。

标题栏组件

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

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

Windowing

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

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

如何使用 AppWindow

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

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

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

自定义标题栏的程度

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

Simple

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

Full

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

Colors

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

此示例演示如何获取 AppWindowTitleBar 的实例并设置其颜色属性。

Important

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

// Assumes "this" is a XAML Window. In projects that don't use 
// WinUI 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)

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

    m_AppWindow.SetIcon(iconId));
    

完全自定义

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

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

Tip

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

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

public MainWindow()
{
    this.InitializeComponent();

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

Caution

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

此示例演示如何获取 AppWindowTitleBar 并将 AppWindowTitleBar.ExtendsContentIntoTitleBar 属性设置为 true。 此示例演示如何使用互作 API 获取 AppWindow,如果你的应用不使用 WinUI 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>

Important

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

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

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

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

Caution

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

交互式内容

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

Windows 应用程序的标题栏中的搜索框

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

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

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

  • 将 AppTitleBar 网格高度设置为 48,以遵循交互式内容的 标题栏 设计指南。
  • 将 PreferredHeightOption 设置为 Tall 使标题按钮的高度与标题栏相同。
  • 要简化控件的大小调整和区域的计算,请对布局使用 Grid 和多个命名列。
  • 对包含 的列使用MinWidth (*) 大小调整和 AutoSuggestBox,以便它自动调整窗口的大小。
  • 在 MinWidth 上设置 RightDragColumn,以保留一个始终可拖动的小区域,即使调整窗口大小也是如此。
  • 在 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>

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

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

        // Assumes "this" is a XAML Window. In projects that don't use 
        // WinUI 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)
        );
    }
}

Warning

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

系统标题按钮

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

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

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

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

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

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

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

Important

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

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

在标题栏中添加交互式内容(如搜索框或人物图片)时,建议增加标题栏的高度,以便为这些元素提供更多空间。 更高的标题栏也会让触摸操作更容易。 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;
}

Caution

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

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

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

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

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

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

Tip

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 以在应用运行时切换到新的标题栏元素。
  • 使用 SetTitleBar 作为参数调用 null,以重置为默认 AppWindowTitleBar 拖动区域。
  • 调用SetTitleBar,并以null作为参数,将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;
        }
    }
}

Note

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

应做事项和禁止事项

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

完全自定义示例

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

<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 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);
            }
        }
    }
}