管理应用窗口

Windows 应用 SDK 提供 Microsoft.UI.Windowing.AppWindow 类,该类表示 HWND 的高级抽象。 应用中的 AppWindow 与顶级 HWND 之间存在 1:1 映射关系。 AppWindow 及其相关类提供 API,让你无需直接访问 HWND 即可管理应用的顶级窗口的许多方面。

Note

本文演示如何在应用中使用 AppWindow API。 作为先决条件,我们建议阅读并了解 AppWindowWinUI 和 Windows 应用 SDK 的窗口化概述中的信息,无论使用 WinUI 还是其他 UI 框架都适用。

WinUI 3 示例集应用程序包括大多数 WinUI 3 控件、特性和功能的交互式示例。 通过 Microsoft Store 获取应用,或在 GitHub 上获取源代码

可以将 API 与 Windows 应用 SDK 支持的任何 UI 框架配合使用 AppWindow - WinUI 3、WPF、WinForms 或 Win32。 AppWindow API 与框架特定的窗口管理 API 一起工作:

通常会使用 AppWindow API 来:

  • 管理应用窗口的大小和位置。

  • 管理窗口标题、图标和标题栏颜色;或使用 AppWindowTitleBar API 创建完全自定义的标题栏。

    有关详细信息和示例,请参阅 标题栏自定义

  • 使用 AppWindowPresenter 派生的 API 管理窗口的外观和行为。

响应 AppWindow 更改

通过处理AppWindow事件来响应对的更改,然后检查事件参数(AppWindowChangedEventArgs),以识别发生了哪种更改。 如果你感兴趣的更改发生,你可以对此做出响应。 潜在的更改包括位置、大小、演示者、可见性和 z 顺序。

下面是一个AppWindow事件处理程序的示例,用于处理“变更”事件。

private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
    // ConfigText and SizeText are TextBox controls defined in XAML for the page.
    if (args.DidPresenterChange == true)
    {
        ConfigText.Text = sender.Presenter.Kind.ToString();
    }

    if (args.DidSizeChange == true)
    {
        SizeText.Text = sender.Size.Width.ToString() + ", " + sender.Size.Height.ToString();
    }
}

Window 大小和位置

AppWindow 类具有多个属性和方法,可用于管理窗口的大小和位置。

Category 属性
只读属性 PositionSizeClientSize
Events 已更改DidPositionChangeDidSizeChange
大小和位置方法 MoveResizeResizeClientMoveAndResize
Z 顺序方法 MoveInZOrderAtBottomMoveInZOrderAtTopMoveInZOrderBelow

调用 Resize 以指定新的窗口大小。

在此示例中,代码位于 MainWindow.xaml.cs 中,因此你可以使用 Window.AppWindow 属性获取 AppWindow 实例。

public MainWindow()
{
    InitializeComponent();
    AppWindow.Resize(new Windows.Graphics.SizeInt32(1200, 800));
}

调用 Move 方法以更改窗口的位置。

本示例在用户单击按钮时将窗口居中移动在屏幕上。

这发生在 Page 类的代码文件中,因此你不能自动访问 WindowAppWindow 对象。 你可以通过多种方式来获取 AppWindow。

private void MoveWindowButton_Click(object sender, RoutedEventArgs e)
{
    AppWindow appWindow = AppWindow.GetFromWindowId(XamlRoot.ContentIslandEnvironment.AppWindowId);
    RectInt32? area = DisplayArea.GetFromWindowId(appWindow.Id, DisplayAreaFallback.Nearest)?.WorkArea;
    if (area == null) return;
    appWindow.Move(new PointInt32((area.Value.Width - appWindow.Size.Width) / 2, (area.Value.Height - appWindow.Size.Height) / 2));
}

AppWindowPresenter 类和子类

每个 AppWindow 都有一个 AppWindowPresenter(窗口展示器)与之关联。 呈现器由系统创建,并在创建 AppWindow 时进行应用。 AppWindowPresenter 的每个子类都提供适用于窗口的预定义配置。 提供了这些 AppWindowPresenter 派生的呈现器,并且可在所有受支持的 OS 版本上使用。

  • CompactOverlayPresenter

    配置一个固定大小的 always-on-top 窗口,纵横比为16:9,以便实现画中画式体验。 默认情况下, InitialSizeCompactOverlaySize.Small,但你可以将其 Medium 更改为或 Large。 还可以调用 AppWindow。重设大小 以更改 16:9 的纵横比,并将窗口调整为任意所需的大小。

  • FullScreenPresenter

    配置窗口以提供适合观看视频的全屏体验。 窗口没有边框或标题栏,并隐藏系统任务栏。

  • OverlappedPresenter

    默认情况下,标准窗口配置提供了一个带有调整大小句柄的边框,以及一个带有最小化、最大化和还原按钮的标题栏。

Note

作为 Win32 应用程序模型的新概念,演示者与窗口状态和 样式的组合类似(但不相同)。 某些呈现器中还定义了无法从经典窗口状态和样式属性中检查的行为(例如自动隐藏标题栏)。

默认演示者

当创建 AppWindow 时,应用的默认演示者是具有默认属性设置的 OverlappedPresenter 实例。 无需保留对其的引用,以便在应用其他呈现器后返回窗口的默认呈现器。 这是因为系统在为其创建的 AppWindow 的生命周期内保留了该演示者的同一实例;并且您可以通过调用 AppWindow.SetPresenter 方法,将 AppWindowPresenterKind.Default 作为参数,重新应用它。

Important

调用SetPresenter(AppWindowPresenterKind.Default)始终会重新应用使用AppWindow创建的默认呈现器实例。 如果创建并应用另一个呈现器,并希望稍后重新应用它,则需要保留对该呈现器的引用。

还可以获取对默认演示者实例的引用并对其进行修改。 如果已应用新的演示者,请先确保应用默认演示者,如下所示:

appWindow.SetPresenter(AppWindowPresenterKind.Default);
OverlappedPresenter defaultPresenter = (OverlappedPresenter)appWindow.Presenter;
defaultPresenter.IsMaximizable = false;
defaultPresenter.IsMinimizable = false;

修改重叠呈现器

OverlappedPresenter 是一种灵活的演示者,可以通过多种方式进行配置。

Create* 方法允许你创建一个具有默认属性设置的重叠式呈现器,或者创建一个针对特定用途预先配置了属性设置的重叠式呈现器。

下表显示了从每个方法创建一个 OverlappedPresenter 对象时如何设置配置属性。

Property Create CreateForContextMenu CreateForDialog CreateForToolWindow
HasBorder true true true true
HasTitleBar true false true true
IsAlwaysOnTop false false false false
是否可最大化 true false false true
是否可最小化 true false false true
IsModal false false false false
可调整大小 true false false true

应用的呈现器是一个实时对象。 对 AppWindow.Presenter 对象的任何属性更改会立即生效。 没有任何事件可通知你这些更改,但可以随时检查当前值的属性。

HasBorderHasTitleBar 属性是只读的。 可以通过调用 SetBorderAndTitleBar 方法(SetBorderAndTitleBar(bool hasBorder, bool hasTitleBar))来设置这些值。 OverlappedPresenter 不能有无边框的标题栏。 也就是说,如果 hasTitleBar 参数是 true,则 hasBorder 参数也必须是 true。 否则,将抛出异常,并附有以下消息:

The parameter is incorrect.
Invalid combination: Border=false, TitleBar=true.

IsMaximizable 设置为 false 隐藏工具栏中的最大化按钮。 如果设置 PreferredMaximumHeightPreferredMaximumWidth 属性,我们建议执行此作,因为这些属性约束窗口大小,即使处于最大化状态。 这不会影响对 Maximize 方法的调用。

IsMinimizable 设置为 false 隐藏工具栏中的最小化按钮。 这不会影响对 最小化 方法的调用。

IsResizable 设置为 false 隐藏调整控件大小并阻止用户调整窗口大小。 这不会影响对AppWindow.Resize方法的调用。

IsAlwaysOnTop 设置为 true 将此窗口保留在其他窗口之上。 如果调用任何 AppWindow.MoveInZOrder* 方法,它们仍会生效以更改窗口的 z 顺序,即使此属性为 true

设置 PreferredMaximumHeightPreferredMaximumWidth 以限制用户可以将窗口拉伸到的最大大小。 如果设置最大大小属性,我们建议你设置为IsMaximizablefalse,因为这些属性约束窗口大小,即使处于最大化状态。 这些属性还会影响对AppWindow.Resize 的调用,窗口的大小不会调整得大于指定的最大高度和宽度。

设置 PreferredMinimumHeightPreferredMinimumWidth 以设置用户可以收缩窗口的最小大小。 这些属性还会影响对 AppWindow 方法的调用,窗口的大小不会小于指定的最小高度和宽度。

可以将 IsModal 设置为 true 创建 模式窗口。 模式窗口是一个单独的窗口,它阻止与其 所有者窗口 的交互,直到其关闭。 但是,若要创建模式窗口,还必须设置所有者窗口;否则,将引发此消息的异常:

The parameter is incorrect.

The window should have an owner when IsModal=true.

若要在 WinUI 应用中设置所有者窗口,需要使用 Win32 互操作。 有关详细信息和示例代码,请参阅 AppWindow WinUI 库示例应用中的页面。

应用呈现器

每次只能将一个呈现器应用到一个窗口。 如果尝试将同一个呈现器应用到其他窗口,则会引发异常。 因此,如果你有多个窗口并想要将每个窗口切换到特定的呈现模式,则需要创建多个相同类型的呈现器,然后将每个呈现器应用到相应的窗口。

当应用新的演示者时(即 AppWindow.Presenter 属性发生更改),你的应用会通过受影响的 AppWindow 上的 事件收到通知,其中 AppWindow 属性设置为

Tip

如果应用修改过的演示者,并允许在不同演示者之间切换,请务必保留对已修改过演示者的引用,以便可以重新应用于 AppWindow

此示例演示如何执行以下作:

在这里,表示层在窗口的构造函数中被创建、修改和应用。

OverlappedPresenter presenter = OverlappedPresenter.Create();
presenter.PreferredMinimumWidth = 420;
presenter.PreferredMinimumHeight = 550;
AppWindow.SetPresenter(presenter);

在作为窗口内容的 Page 中,你可以获得对 AppWindow 和所应用呈现器的引用。

AppWindow appWindow;
OverlappedPresenter modifiedPresenter;

private void AppWindowPage_Loaded(object sender, RoutedEventArgs e)
{
    appWindow = AppWindow.GetFromWindowId(XamlRoot.ContentIslandEnvironment.AppWindowId);
    modifiedPresenter = (OverlappedPresenter)appWindow.Presenter;

    appWindow.Changed += AppWindow_Changed;
}

private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
    if (args.DidPresenterChange)
    {
        // ConfigText is a TextBox control defined in XAML for the page.
        ConfigText.Text = appWindow.Presenter.Kind.ToString();
    }
}

private void CompactOverlayButton_Click(object sender, RoutedEventArgs e)
{
    if (appWindow.Presenter.Kind != AppWindowPresenterKind.CompactOverlay)
    {
        appWindow.SetPresenter(CompactOverlayPresenter.Create());
        fullScreenButton.IsChecked = false;
    }
    else
    {
        appWindow.SetPresenter(modifiedPresenter);
    }
}

private void FullScreenButton_Click(object sender, RoutedEventArgs e)
{
    if (appWindow.Presenter.Kind != AppWindowPresenterKind.FullScreen)
    {
        appWindow.SetPresenter(FullScreenPresenter.Create());
        compactOverlayButton.IsChecked = false;
    }
    else
    {
        appWindow.SetPresenter(modifiedPresenter);
    }
}

UI 框架和 HWND 互操作

AppWindow 类可用于应用中的任何顶级 HWND。 这意味着在使用桌面 UI 框架(包括 WinUI 3)时,可以继续使用该框架的入口点来创建窗口,并附加其内容。 使用该 UI 框架创建窗口后,可以使用 Windows 应用 SDK 中提供的开窗互作函数(请参阅下文),访问相应的 AppWindow 方法、属性和事件。

使用 AppWindow (即使在使用 UI 框架时)的一些优点包括:

  • 可轻松进行标题栏自定义,这在默认情况下会保留 Windows 11 UI 体验(圆角、贴靠组浮出控件)。
  • 系统提供的全屏和紧凑叠加(画中画)体验。
  • 适用于某些核心 Win32 窗口化概念的 Windows 运行时 (WinRT) API 外围应用。

获取适用于 1.3 之前的 Windows 应用 SDK 版本(或其他桌面应用框架)的 AppWindow

.WindowAppWindow属性在 Windows 应用 SDK 版本 1.3 及更高版本中可用。 对于早期版本,可以使用本部分中的对等功能代码示例。

C#. 窗口互作函数的 .NET 包装器作为 Microsoft.UI.Win32Interop 类的方法实现。 另请参阅从 .NET 应用调用互操作 API

C++。 互操作函数定义于 winrt/Microsoft.ui.interop.h 头文件。

下面的代码示例部分显示了实际的源代码;但下面是在给定现有窗口的情况下检索 AppWindow 对象的方法:

  1. 检索现有窗口对象的(对应于 UI 框架的)HWND(如果还没有)。
  2. 将 HWND 传递给 GetWindowIdFromWindow 互作函数以检索 WindowId
  3. 将 WindowId 传递给静态 AppWindow.GetFromWindowId 方法以检索 AppWindow。
// MainWindow.xaml.cs
private void myButton_Click(object sender, RoutedEventArgs e)
{
    // Retrieve the window handle (HWND) of the current (XAML) WinUI 3 window.
    var hWnd =
        WinRT.Interop.WindowNative.GetWindowHandle(this);

    // Retrieve the WindowId that corresponds to hWnd.
    Microsoft.UI.WindowId windowId =
        Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);

    // Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window.
    Microsoft.UI.Windowing.AppWindow appWindow =
        Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);

    if (appWindow != null)
    {
        // You now have an AppWindow object, and you can call its methods to manipulate the window.
        // As an example, let's change the title text of the window.
        appWindow.Title = "Title text updated via AppWindow!";
    }
}
// pch.h
#include "microsoft.ui.xaml.window.h" // For the IWindowNative interface.
#include <winrt/Microsoft.UI.Interop.h> // For the WindowId struct and the GetWindowIdFromWindow function.
#include <winrt/Microsoft.UI.Windowing.h> // For the AppWindow class.

// mainwindow.xaml.cpp
void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    // Retrieve the window handle (HWND) of the current (XAML) WinUI 3 window.
    auto windowNative{ this->m_inner.as<::IWindowNative>() };
    HWND hWnd{ 0 };
    windowNative->get_WindowHandle(&hWnd);

    // Retrieve the WindowId that corresponds to hWnd.
    Microsoft::UI::WindowId windowId = 
        Microsoft::UI::GetWindowIdFromWindow(hWnd);

    // Lastly, retrieve the AppWindow for the current (XAML) WinUI 3 window.
    Microsoft::UI::Windowing::AppWindow appWindow = 
        Microsoft::UI::Windowing::AppWindow::GetFromWindowId(windowId);

    if (appWindow)
    {
        // You now have an AppWindow object, and you can call its methods to manipulate the window.
        // As an example, let's change the title text of the window.
        appWindow.Title(L"Title text updated via AppWindow!");
    }
}

有关如何使用 AppWindow的更多示例,请参阅 窗口示例集锦

Limitations

Windows 应用 SDK 当前不提供用于将 UI 框架内容附加到AppWindow的方法。