读者可以把vs 2022 升级到17.9.1或者以上版本后,创建的winui3 C++是默认的例子是有一个按钮的,点击按钮后台可以执行一些操作。
我们创建一个新的项目


上一节课,我们已经讲解了项目的各个文件和作用。这一节我们创建一个新的空白窗体,然后窗体上面添加一个按钮,点击按钮
我们前面说到,我们的入口类是APP这里
void App::OnLaunched([[maybe_unused]] LaunchActivatedEventArgs const& e)
{
window = make<MainWindow>();
window.Activate();
}Code language: C++ (cpp)
从这里开始,创建主窗体,主窗体就是我们程序打开看到的那个窗体。如下是我们的主窗口,下面先解析一下,一个窗体一共有四个文件。

窗体
MainWindow.xaml(界面描述文件)
纯 XML 标记语言,专门写 UI 布局,只负责界面视觉,不写业务逻辑。
里面你可以写布局控件,各种窗体控件,例如Button TextBox等等,这是一个从WPF开始就一直使用的界面描述文件,本质是一个XML.需要配套另外的同名的.h .cpp文件。来处理后台事件。
在官方默认的例子中,xaml是一个空白的,什么控件都没有
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="WinuiCppDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WinuiCppDemo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="WinuiCppDemo">
<Grid>
</Grid>
</Window>Code language: YAML (yaml)
MainWindow.idl
接口定义文件,C++/WinRT 独有核心
IDL = Interface Definition Language 接口定义语言,定义窗口对外暴露的 API、属性、事件,是 C++/WinRT 跨语言互操作的桥梁。
- 规定窗口类的公开成员:自定义属性、自定义事件、可调用方法
- 编译器会根据 idl 自动生成 WinRT 标准 ABI 接口代码,让 C++/C#/JS 都能调用这个窗口
- 界面 XAML 里的数据绑定、自定义控件依赖 idl 声明属性才能生效
为什么 WinUI3 C++/WinRT 必须要有 .idl?
因为,WinRT 是一套跨语言二进制 ABI 标准
Windows Runtime(WinRT)不是单纯 C++ 库,它是一套跨语言通用二进制接口规范: C++、C#、Python、JS 都能调用同一个 WinRT 组件,靠的就是统一接口描述,不能直接靠 C++ 头文件互通。
C++ 头文件只给 C++ 编译器识别,C#/JS 完全看不懂;而 IDL 是中立接口语言,编译器能把它翻译成所有语言都能识别的 ABI 胶水代码。
下面是我们的例子中的idl文件的内容
namespace WinuiCppDemo
{
[default_interface]
runtimeclass MainWindow : Microsoft.UI.Xaml.Window
{
MainWindow();
Int32 MyProperty;
}
}
我们说了 idl是描述语言,上面这个idl文件,前面是命名空间namespace WinuiCppDemo,然后[default_interface] 特性
给 runtimeclass 标记默认主接口,是 C++/WinRT 写窗口 / 自定义控件的强制标配
MIDL 编译器会自动生成一个 IMainWindow 纯虚接口,包含类所有成员(构造、属性、方法)
[default_interface] 指定这个 IMainWindow 是该类对外暴露的主 ABI 接口
大括号内的MainWindow();是无参构造函数声明,必须显式写出构造,XAML 引擎实例化窗口时会调用这个默认构造
对应 .xaml.h 里的构造函数实现。
Int32 MyProperty;
声明一个WinRT 依赖属性(可绑定属性),这就是 IDL 的核心价值体现:
Int32:WinRT 标准基础类型(对应 C++ int32_t);
MyProperty:自动生成一套完整属性体系:
- Getter:
int32_t MyProperty(); - Setter:
void MyProperty(int32_t value); - 属性变更事件:
MyPropertyChanged(属性修改自动触发,XAMLx:Bind靠它自动刷新 UI)
只有在 IDL 里写的这种成员,才能在 XAML 中用 {x:Bind MyProperty} 绑定; 如果你只在 .h 里写一个普通成员变量,XAML 完全识别不到,绑定直接报错。
右键idl可以单独编译这个idl,它是midl.exe这个工具来处理的,回编译成C#用的元数据文件,也回生成C++的接口头文件。
说白了,idl,就是用来生成接口,为了跨语言之间的调用,同时也为了我们的XAML能进行数据绑定提供元数据。为什么winui 3 C# 没有这个idl,因为C#自带反射和元数据。它是托管语言,而C++ 版本的winui 因为C++ 本身没有反射、没有元数据;
你只在 .h 里写一个成员变量,XAML 引擎完全看不见;
只有在 IDL 声明 Int32 MyProperty;,编译器才会生成:
- 属性 Get/Set 接口
- 属性变更通知事件
MyPropertyChanged - 元数据,让 XAML 静态绑定校验通过
哪怕你的项目从头到尾只用 C++,不跟 C#/JS 任何其他语言交互,只要你用 XAML,就必须写 IDL
WinRT 设计初衷是一套统一 ABI,让 C++ / C# / JS / Python 能互相调用组件:
IDL 是中立描述语言,不绑定任何编程语言; 不同语言编译器都能读取 IDL 生成的 .winmd 元数据,识别你的类、属性、方法;
右键IDL文件,可以编译这个idl

IDL 核心是给 XAML 提供识别 UI 绑定所需的元数据,顺带实现跨语言交互。
idl编译后回生成一个 xxxx.g.h 和xxxx.g.cpp 文件的,它就存放在项目目录下的Generated Files 文件里面

Generated Files 是 自动生成代码目录
- 由 midl.exe、cppwinrt.exe、XAML编译器 在编译项目时自动生成的各种文件。
- 不要手动修改里面任何文件,重新编译会全部覆盖
- VS项目配置默认把该目录加入头文件包含路径,#include “MainWindow.g.h” 才能找到文件
MainWindow.xaml.h
C++ 标准头文件,存放 MainWindow 类的声明。
自动包含 idl 生成的基础接口;
类的字段、私有变量、前置定义都写在这里,它和我们学习C++语法中的.h头文件一样。
写明又那些变量和函数,但是不去实现,其他类可以通过include引用它。
// 头文件保护:防止该头文件被重复包含,避免重复定义编译错误
#pragma once
// 引入由 MainWindow.idl 编译自动生成的胶水头文件
// .g.h 内部包含 IDL 生成的 IMainWindow 接口、MainWindowT<> 模板基类、WinRT元数据相关定义
// 我们在idl声明的 MyProperty、构造函数接口都在这里定义
#include "MainWindow.g.h"
// 【实现命名空间】存放窗口业务逻辑的实现类,我们自己写的属性/函数都在这里实现
namespace winrt::WinuiCppDemo::implementation
{
// MainWindowT<MainWindow> 是.g.h提供的CRTP模板基类
// 1. 自动继承 IDL 定义的 IMainWindow 接口,强制实现idl声明的所有成员
// 2. 封装WinUI窗口底层逻辑、XAML组件初始化、ABI接口转发逻辑
struct MainWindow : MainWindowT<MainWindow>
{
// 窗口默认构造函数,对应idl里声明的 MainWindow();
MainWindow()
{
// 官方规范:构造函数内禁止调用 InitializeComponent()
// XAML控件初始化会由框架自动延迟执行,构造阶段调用会引发生命周期崩溃
// 参考微软cppwinrt官方说明文档
// See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent
}
// 对应 IDL 中 Int32 MyProperty; 自动生成的属性读取接口
// 返回属性的int32_t数值
int32_t MyProperty();
// 对应 IDL 中 Int32 MyProperty; 自动生成的属性写入接口
// 修改属性值,内部自动触发 MyPropertyChanged 变更事件,供x:Bind刷新UI
void MyProperty(int32_t value);
};
}
// 【工厂命名空间】WinRT对象工厂类,负责对外暴露、实例化MainWindow窗口对象
namespace winrt::WinuiCppDemo::factory_implementation
{
// MainWindowT第二个参数绑定上面implementation::MainWindow实现类
// 作用:提供统一ABI创建入口,给XAML引擎、外部代码(C#/其他组件)创建窗口实例
struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
{
};
}Code language: C++ (cpp)
MainWindow.xaml.cpp
C++ 实现文件,所有业务逻辑代码写在这里。
- 实现 MainWindow.xaml.h 里声明的所有函数
- 控件事件处理(按钮点击、窗口加载、输入响应)
- 数据处理、网络、文件、业务计算
- 操作 UI 控件、修改绑定属性、弹窗、页面跳转等交互逻辑
// 引入预编译头文件,统一包含项目公共头文件、WinRT基础投影头,加速编译
#include "pch.h"
// 引入当前窗口类的头文件,包含MainWindow实现类声明
#include "MainWindow.xaml.h"
// 条件编译:判断是否存在IDL自动生成的底层实现胶水文件MainWindow.g.cpp
// .g.cpp 由midl+cppwinrt工具根据.idl生成,封装属性变更事件、ABI底层转发逻辑
// 存在则引入,让属性变更通知(MyPropertyChanged)、XAML运行时绑定正常工作
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif
// 全局命名空间简化,后续代码无需重复写winrt::前缀
using namespace winrt;
// WinUI Xaml核心控件、窗口、路由事件等API命名空间
using namespace Microsoft::UI::Xaml;
// 官方注释:WinUI项目结构、模板文档地址
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
// implementation命名空间:存放MainWindow窗口业务逻辑实现,对应头文件里的类声明
namespace winrt::WinuiCppDemo::implementation
{
// IDL中声明的MyProperty属性【读方法】实现
// 返回Int32类型的属性值,XAML x:Bind读取数据时会调用此函数
int32_t MainWindow::MyProperty()
{
// 默认模板抛出未实现异常,需要自行替换为真实存储变量的返回逻辑
throw hresult_not_implemented();
}
// IDL中声明的MyProperty属性【写方法】实现
// value:传入要修改的新属性值,修改后底层自动触发MyPropertyChanged事件刷新绑定UI
void MainWindow::MyProperty(int32_t /* value */)
{
// 默认模板抛出未实现异常,需要自行补充变量赋值逻辑
throw hresult_not_implemented();
}
}Code language: PHP (php)
下面,我们在这个主窗体中,添加一个按钮,点击按钮,让里面的MyProperty属性自动+1,然后打开一个对话框,显示当前MyProperty的值。
首先是xaml中增加一个按钮
<Grid>
<Button Content="Click Me" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"/>
</Grid>Code language: C++ (cpp)
按钮显示的名字是Click Me, 然后还绑定过来点击事件OnButtonClick
鼠标放到OnButtonClick 按住F12,VS自动创建这个事件的执行函数。
然后回在MainWindow.xaml.h MainWindow.xaml.cpp中看到
void OnButtonClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);Code language: C++ (cpp)

增加了一个函数的定义。
在MainWindow.xaml.cpp中有它的实现

然后我们修改cpp中的代码
本节课就先到这里,我们下节课来实现这个点击按钮的功能。了解一下属性绑定。