mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
3434 words
9 minutes
引入 WinUI3 C++/Winrt
2026-05-27

引入:使用 C++ 开发 WinUI 3 桌面应用#

概要#

WinUI 3 是 Microsoft 用于生成 Windows 桌面应用程序的现代本机用户界面框架。它为 C# 和 C++ 开发人员带来了 Fluent Design System、高性能呈现和基于 XAML 的强大编程模型。

WinUI 作为 Windows 应用 SDK 的一部分提供,可用于创建在 Windows 10 版本 1809(内部版本 17763)及更高版本(包括 Windows 11)上运行的现代、美观和响应式桌面体验。

本篇文章将指导你使用 C++ 编写现代 WinUI 应用。

何时使用 WinUI C++?#

在很多时候,你都不应当选择 WinUI C++ 的开发,因为其领域小、文档教程较少。如果你希望开发快速迭代的现代化 WinUI 应用,你应当选择 C#。

以下列出了适合使用 WinUI C++ 开发的情况:

  1. 项目要求使用 C++ 开发。
  2. 你有很多时间。
  3. 你有极致的性能需求,需要处理底层实时渲染任务或严格控制内存分配和 CPU 周期。
  4. 你希望发布包完全自托管原生 AOT 编译,不依赖 .NET 运行时。
  5. 你喜欢 Windows。
  6. 你喜欢 Microsoft。

教程:开发 WinUI C++ 桌面应用#

入门 | 安装 WinUI C++ 开发环境#

本篇文章使用 Visual Studio 2026 进行教程。打开下载好的 Visual Studio Installer 应用,选择安装或修改 Visual Studio 2026。

在打开的窗口中,确保顶部菜单栏处于 “工作负荷” 选项卡。

勾选 “使用 C++ 的桌面开发” 和 “WinUI 应用程序开发” 选项。

然后,在右侧安装详细信息栏中,展开 “WinUI 应用程序开发” 选项,展开 “可选” 选项,勾选 “C++ WinUI 应用开发工具”。

要安装的程序组件体积较大,你可以自行配置要安装的路径。选择顶部菜单栏 “安装位置” 选项卡以配置安装路径。(确保不要将安装路径放到嵌套过深或带有中文名称的文件夹中)

你可能发现部分程序组件仍处于 C 盘中,这是因为出于一些原因,他们必须安装在系统盘下。

等待程序下载并安装。

入门 | 创建你的第一个 WinUI C++ 应用#

如果你已熟悉在 Visual Studio 中创建项目的基本流程,可跳过此步骤。

打开安装好的 Visual Studio 2026,选择创建新项目。

Visual Studio 启动窗口

选择 C++ 语言,搜索选择 WinUI 空白应用(已打包)。

Visual Studio 创建项目窗口

配置项目的名称、路径。

Visual Studio 创建项目配置窗口

创建完成。

入门 | 代码设计原则#

  1. 请使用完全限定名。严禁在头文件中使用 using namespace 语句引用命名空间。请使用完全限定名,或将 using namespace 语句限制在源文件中。
  2. 遵循单一职责原则。一个类只做一件事,不应承担过多的职责。将职责划分到多个类总是要比集成到单个类更有利于代码组织。在实际项目中,请尽量创建多个类分担功能。
  3. 实现 C++ 标准类型与 WinRT 类型之间的转换。WinRT 类型是 WinRT 特有的,许多库并不认识 WinRT。请创建 WinRT 类型对应的 C++ 标准类型,然后自行实现转换逻辑,在必要时进行通用 C++ 开发。

入门 | WinUI 项目结构#

WinUI 的用户界面核心是 XAML。XAML (eXtensible Application Markup Language)“可扩展应用程序标记语言” 是一种声明性标记语言,类似于 HTML,本质上是一个 XML 文件。可以在声明性 XAML 标记中创建可见的 UI 元素,然后使用与标记通过分部类定义连接的后台代码文件,将 UI 定义与运行时逻辑分开。

项目新创建时,将生成一些预置文件。参考以下信息,了解 WinUI C++ 的项目结构。

WinUI C++ 项目结构概览

文件功能
app.manifest应用程序清单,用于描述和标识应用程序应在运行时绑定到的共享和专用并行程序集。通常情况下,无需修改此文件内容。
packages.config程序项目的 nuget 包清单。由 Visual Studio 自动管理。通常情况下,无需修改此文件内容。
pch.h 和 pch.cppMSVC 的预编译配置文件。内部引用了一系列 Windows 运行时组件库。其作用是优化编译的性能、减少编译时间。通常情况下,不应当修改此文件内容。
module.g.cpp与 pch.h 预编译文件共同参与预编译过程。在任何情况下,不应当修改此文件内容。
readme.txt预生成的使用 IDL 文件生成 Windows 运行时类型的简要介绍。在此文档中已有详细教程,程序实际运行无需依赖此文件。你可以大致浏览并自行选择保留或删除。
App.xamlWinUI 程序的入口点,用于定义全局资源和启动配置。它与后台代码文件(App.xaml.cppApp.xaml.h)一起工作,提供了应用程序的初始化逻辑和事件处理能力。你将修改此文件,用于管理应用的生命周期。
MainWindow.xaml预生成用于定义主窗口的 XAML 文件。你将修改此文件,用于定义窗口的内容。

在 WinUI C++ 中,可视化容器参与定义显示在窗口上的内容和其内部的交互逻辑。可视化容器可分为四种:窗口(Window)、页面(Page)、模板化控件(TemplatedControl)、用户控件(UserControl)。

每个可视化容器将对应四个代码文件:

文件功能
XAML定义 UI 控件、排版
IDL连接 XAML 与 C++ 代码
cpp定义该容器的类型的源文件
h定义该容器的类型的头文件

点击 XAML 代码文件左侧的箭头来展开其对应的另外三个代码文件。

入门 | 开启云母材质窗口背景#

云母(Mica)是一种不透明的动态材质,它将主题和桌面壁纸相结合来绘制应用和设置等长期存在的窗口的背景。WinUI 的特色之一即为云母材质窗口背景。要在 WinUI 中启用窗口云母材质背景,请参考下列代码:

MainWindow.xaml
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="App1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="App1">
<Window.SystemBackdrop>
<MicaBackdrop Kind="Base"/>
</Window.SystemBackdrop>
<Grid>
</Grid>
</Window>

效果如图:

开启云母材质背景

WinUI 窗口标题栏默认不会启用云母材质,但你大概率也会想要将云母材质扩展至窗口标题栏。

在 MainWindow 的后台代码中添加以下代码,可以将内容扩展至窗口标题栏:

MainWindow.xaml.h
namespace winrt::App1::implementation {
struct MainWindow : MainWindowT<MainWindow> {
MainWindow() {
ExtendsContentIntoTitleBar(true);
}
};
}

效果如图:

扩展窗口内容至标题栏

入门 | 使用 WinRT 类型#

基础类型:

C++ 标准C++ 映射WinRT说明
std::stringwinrt::hstringStringUTF-16 格式编码
intint32_tInt32固定 32 位整数
doubledoubleDouble-
boolboolBoolean-
std::vector<T>IVector<T>Windows.Foundation.Collections.IVector<Object>基础集合
std::vector<T>IObservableVector<IInspectable>Windows.Foundation.Collections.IObservableVector<Object>可观察集合

IVectorwinrt::Windows::Foundation::Collections::IVector
IObservableVectorwinrt::Windows::Foundation::Collections::IObservableVector
IInspectablewinrt::Windows::Foundation::IInspectable

IObservableVector<IInspactable> 是可观察动态数组,其中的 IInspactable 可以是任意 winrt 类型。此类型自带通知功能,调用它的 AppendRemoveAtSetAt 等方法将自动触发 UI 层更新,无需手动替换 UI 内容。

基础 | 创建自定义 Windows 运行时类型#

仅 WinRT 类型对象可以绑定到 UI 层。请创建自定义 WinRT 类型,用于实现在 XAML 中的数据绑定以及其他 UI 层业务逻辑。

在微软官方网站查看官方教程文档以了解更多详细有关 C++/WinRT 的信息:C++/WinRT - UWP applications | Microsoft Learn

这一部分的内容在项目根目录下随项目创建时自动生成的 readme.txt 文件中已有简要阐述,你可以自行打开查看。

以下步骤展示了如何创建一个自定义 WinRT 类型,名为 MyCustomType,包含一个 32 位整数属性 “Id” 和一个 hstring 字符串属性 “Name”。

  1. 定义 IDL。
    在 WinUI 项目中,IDL(Interface Definition Language)“接口描述语言”,被用于生成自定义 WinRT 类型的源代码文件,供开发者使用;以及生成一系列中间代码文件。它是连接 C++ 代码与 XAML 的桥梁。IDL 的语法类似于 C#/TypeScript。

    在微软官方网站中查阅官方教程文档以了解更多详细有关 MIDL 的信息:Microsoft 接口定义语言 3.0 参考 - Windows UWP applications | Microsoft Learn

    现在,请新建一个 IDL 文件,以声明你的自定义结构类型。(Visual Studio 不支持 IDL 文件的语法高亮和代码智能提示。你可能需要注意检查代码缩进格式和单词拼写)

    Models.idl
    namespace App1.Models
    {
    [default_interface]
    runtimeclass MyCustomType
    {
    MyCustomType();
    Int32 Id;
    String Name;
    }
    }

    属性默认为 { get; set; } 读写属性,即 Int32 Id; 等价于 Int32 Id { get; set; };

  2. 复制生成的 C++ 代码文件。
    编写完 IDL 文件后,编译一次这个程序。在你完成之后的步骤之前,编译不可通过。

    编译提示报错并终止后,使用文件资源管理器打开项目文件夹,打开 Generated Files\sources 文件夹,找到名为: [命名空间].[声明的类型名].h[命名空间].[声明的类型名].cpp 的两个文件。在本示例中,他们分别为:Models.MyCustomType.hModels.MyCustomType.cpp

    以下展示了这两个文件的内容。

    生成的源文件:

    Models.MyCustomType.h
    #pragma once
    #include "Models.MyCustomType.g.h"
    // WARNING: This file is automatically generated by a tool. Do not directly
    // add this file to your project, as any changes you make will be lost.
    // This file is a stub you can use as a starting point for your implementation.
    //
    // To add a copy of this file to your project:
    // 1. Copy this file from its original location to the location where you store
    // your other source files (e.g. the project root).
    // 2. Add the copied file to your project. In Visual Studio, you can use
    // Project -> Add Existing Item.
    // 3. Delete this comment and the 'static_assert' (below) from the copied file.
    // Do not modify the original file.
    //
    // To update an existing file in your project:
    // 1. Copy the relevant changes from this file and merge them into the copy
    // you made previously.
    //
    // This assertion helps prevent accidental modification of generated files.
    static_assert(false, "This file is generated by a tool and will be overwritten. Open this error and view the comment for assistance.");
    namespace winrt::App1::Models::implementation
    {
    struct MyCustomType : MyCustomTypeT<MyCustomType>
    {
    MyCustomType() = default;
    int32_t Id();
    void Id(int32_t const& value);
    winrt::hstring Name();
    void Name(winrt::hstring const& value);
    };
    }
    namespace winrt::App1::Models::factory_implementation
    {
    struct MyCustomType : MyCustomTypeT<MyCustomType, implementation::MyCustomType>
    {
    };
    }

    生成的头文件:

    Models.MyCustomType.cpp
    #include "pch.h"
    #include "Models.MyCustomType.h"
    #include "Models.MyCustomType.g.cpp"
    // WARNING: This file is automatically generated by a tool. Do not directly
    // add this file to your project, as any changes you make will be lost.
    // This file is a stub you can use as a starting point for your implementation.
    //
    // To add a copy of this file to your project:
    // 1. Copy this file from its original location to the location where you store
    // your other source files (e.g. the project root).
    // 2. Add the copied file to your project. In Visual Studio, you can use
    // Project -> Add Existing Item.
    // 3. Delete this comment and the 'static_assert' (below) from the copied file.
    // Do not modify the original file.
    //
    // To update an existing file in your project:
    // 1. Copy the relevant changes from this file and merge them into the copy
    // you made previously.
    //
    // This assertion helps prevent accidental modification of generated files.
    static_assert(false, "This file is generated by a tool and will be overwritten. Open this error and view the comment for assistance.");
    namespace winrt::App1::Models::implementation
    {
    int32_t MyCustomType::Id()
    {
    throw hresult_not_implemented();
    }
    void MyCustomType::Id(int32_t const& value)
    {
    throw hresult_not_implemented();
    }
    winrt::hstring MyCustomType::Name()
    {
    throw hresult_not_implemented();
    }
    void MyCustomType::Name(winrt::hstring const& value)
    {
    throw hresult_not_implemented();
    }
    }

    可以看到,生成的文件内容中已有对此步骤操作的简要描述。
    请将这两个文件复制到项目目录下,并在 Visual Studio 中右键项目,然后点击添加现有项,打开 Generated Files\sources,并找到这两个文件,将他们包含进项目。
    现在,在 Visual Studio 中打开这两个文件。删除中间所有的注释内容以及 static_assert 语句。

  3. 实现类的具体功能。

其他相关问题:

  1. 生成的代码文件中,导入的后缀为 .g.h.g.cpp 是什么文件?
    它们时编译器读取 IDL 并生成的中间代码文件,无需开发者关心。仅需在源代码文件中导入即可。
  2. 是否可以自行创建源代码文件?
    可以不依赖复制生成的代码文件,但你仍需从生成的代码文件中复制代码模板到自己创建的类文件中,或仿照另一个已经编写好的源代码文件自行编写 WinRT 类型实现。例如:导入 [类型名].g.h[类型名].g.cpp 文件、在头文件中添加 WinRT 的工厂实现类。对于初步接触 WinRT 的人来说,最好复制并直接使用生成的源代码文件。
  3. 构造函数需要对应吗?
    是的,IDL 中的构造函数需要与源代码文件中的构造函数对应。使用 [类型名]() = default; 来明确使用默认的无参数构造函数。

基础 | WinRT 类型的属性级别通知#

在更多情况下,自定义 WinRT 类型需要属性级别的通知更新。

实现自定义 WinRT 类型属性级别通知的方法是在已有类型 IDL 文件中添加类型继承对象 INotifyPropertyChanged

Models.idl
namespace App1.Models
{
[default_interface]
runtimeclass MyCustomType : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
MyCustomType();
Int32 Id;
String Name;
}
}

此操作将在生成代码文件中添加两个额外的函数。

Models.MyCustomType.h
namespace winrt::App1::Models::implementation {
struct Dorm : DormT<Dorm> {
public:
winrt::event_token PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
void PropertyChanged(winrt::event_token const& token) noexcept;
}
}
Models.MyCustomType.cpp
namespace winrt::App1::Models::implementation {
winrt::event_token Dorm::PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
throw hresult_not_implemented();
}
void Dorm::PropertyChanged(winrt::event_token const& token) noexcept
{
throw hresult_not_implemented();
}
}

现在,添加私有成员 propertyChanged

Models.MyCustomType.h
namespace winrt::App1::Models::implementation {
struct MyCustomType : MyCustomTypeT<MyCustomType> {
public:
winrt::event_token PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
void PropertyChanged(winrt::event_token const& token) noexcept;
private:
event<winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> propertyChanged{};
}
}

实现源文件中对应属性变更通知函数。

Models.MyCustomType.cpp
namespace winrt::App1::Models::implementation {
winrt::event_token MyCustomType::PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
return propertyChanged.add(handler);
}
void MyCustomType::PropertyChanged(winrt::event_token const& token) noexcept
{
propertyChanged.remove(token);
}
}

创建私有成员。

Models.MyCustomType.h
namespace winrt::App1::Models::implementation {
struct MyCustomType : MyCustomTypeT<MyCustomType> {
private:
int32_t id;
winrt::hstring name;
}
}

实现属性变更通知。

Models.MyCustomType.cpp
namespace winrt::App1::Models::implementation {
void MyCustomType::Id(int32_t const& value){
if (id != value){
id = value;
propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs(L"Id"));
}
}
int32_t MyCustomType::Id(){
return id;
}
void MyCustomType::Name(winrt::hstring const& value){
id (name != value){
name = value;
propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs(L"Name"));
}
}
winrt::hstring MyCustomType::Name(){
return name;
}
}

进阶 | 使用基类继承的 WinRT 属性变更通知#

在前面的操作中,许多代码有重复。我们实际上希望封装模板函数以减少重复代码。

在本示例中,我们将创建一个基类,让有需要的类型继承该基类。

要实现 WinRT 类型的继承,我们需要在 IDL 中声明基类及类型的继承。

Models.IDL
namespace App1.Models
{
[default_interface]
runtimeclass MyCustomType : BindableBase
{
...
}
[default_interface]
unsealed runtimeclass BindableBase : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
}
}

unsealed 用于标明该类型是需要被继承的基类。否则,普通 WinRT 类型无法被继承。

BindableBase 将承担属性变更通知的职责,因此 BindableBase 需要实现 Microsoft.UI.Xaml.Data.INotifyPropertyChanged 的接口,而非 MyCustomType

常规做法是从生成代码文件中获取生成的代码,但实现代码并不复杂。为了简洁,我们直接创建 BindableBase.h(内联)。

BindableBase.h
#pragma once
#include <winrt/Microsoft.UI.Xaml.Data.h>
namespace winrt::App1::Models {
struct BindableBase : winrt::implements<BindableBase, Microsoft::UI::Xaml::Data::INotifyPropertyChanged> {
public:
winrt::event_token PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler) {
return propertyChanged.add(handler);
}
void PropertyChanged(winrt::event_token const& token) noexcept {
propertyChanged.remove(token);
}
protected:
template<class T>
bool SetProperty(T& field, T const& value, winrt::hstring const& name) {
if (field != value) {
field = value;
propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs(name));
return true;
}
return false;
}
void RaisePropertyChanged(winrt::hstring const& name) {
propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs(name));
}
private:
winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> propertyChanged;
};
}

SetProperty 是我们自定义的函数,用于读写属性通知更新。他使用模板函数,检查原值与目标值是否相等,并通知更新。

RaisePropertyChanged 是我们自定义的函数,用于只读属性通知更新。在某个属性计算修改完成后,可调用此方法,手动通知更新。

SetProperty 实际上已经包含了触发属性变更通知的功能,为什么还要加一个 RaisePropertyChanged
因为计算属性(只读属性)需要在他的依赖属性变化时也触发更新。该属性不使用 SetProperty

在 C++ 代码中,让类型继承该基类。

Models.MyCustomType.h
namespace winrt::App1::Models::implementation {
struct MyCustomType : MyCustomTypeT<MyCustomType, winrt::App1::Models::implementation::BindableBase> {
private:
int32_t id;
winrt::hstring name;
}
}

使用基类提供的函数。

Models.MyCustomType.cpp
namespace winrt::App1::Models::implementation {
void MyCustomType::Id(int32_t const& value){
SetProperty(id, value, "Id");
}
int32_t MyCustomType::Id(){
return id;
}
void MyCustomType::Name(winrt::hstring const& value){
SetProperty(name, value, "Name");
}
winrt::hstring MyCustomType::Name(){
return name;
}
}

基础 | 创建类型转换器 Converters#

与创建自定义 WinRT 类型类似的,创建类型转换器的方法同样是使用 IDL 接口文件。

Share

If this article helped you, please share it with others!

引入 WinUI3 C++/Winrt
https://blog.vista233.top/posts/introducing-winuicpp/
Author
forer4433
Published at
2026-05-27
License
CC BY-NC-SA 4.0

Some information may be outdated

Heyiwei2 说明文档
即时欣赏:osu!
Cover
Sample Song
Sample Artist
Cover
Sample Song
Sample Artist
0:00 / 0:00