讀者將 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 開頭命名空間為 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依靠此事件自動重新整理介面)
只有在 IDL 內宣告的成員,才能在 XAML 使用 {x:Bind MyProperty} 進行繫結;若僅在 .h 撰寫一般成員變數,XAML 完全無法辨識,繫結直接報錯。
對 IDL 檔案按右鍵可單獨編譯,底層由 midl.exe 工具處理,會產生 C# 使用的中繼資料檔,同時輸出 C++ 介面標頭檔。
簡單來說,IDL 用來產生介面以實現跨語言呼叫,同時提供 XAML 資料繫結所需的中繼資料。WinUI3 C# 專案不需要 IDL,因為 C# 屬於受控語言,原生支援反射與中繼資料;而 C++ 版 WinUI 原生不具備反射、中繼資料機制。
僅在 .h 宣告成員變數,XAML 引擎完全無法偵測;
唯有在 IDL 宣告 Int32 MyProperty;,編譯器才會自動產生:
- 屬性讀取/寫入介面
- 屬性變更通知事件
MyPropertyChanged - 中繼資料,確保 XAML 靜態繫結驗證通過
就算專案全程只使用 C++,完全不與 C#、JS 等其他語言互動,只要使用 XAML,就必須撰寫 IDL。
WinRT 設計初衷是一套統一 ABI 標準,讓 C++ / C# / JS / Python 可互相呼叫元件:
IDL 為中立描述語言,不綁定任何程式語言;各語言編譯器均可讀取 IDL 輸出的 .winmd 中繼資料,解析類別、屬性、方法;
對 IDL 檔按右鍵即可編譯該 IDL

IDL 的核心作用是提供 XAML 介面繫結所需的中繼資料,跨語言互通是附加功能。
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<> CRTP 樣版基底類別、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 數值,供 XAML x:Bind 讀取資料
int32_t MyProperty();
// 對應 IDL 中 Int32 MyProperty; 自動產生的屬性設值介面
// 修改屬性值,底層自動觸發 MyPropertyChanged 變更事件,供 x:Bind 重新整理介面
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 事件重新整理繫結介面
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 檔內的程式邏輯
本堂課到此結束,下一堂課我們實作這個按鈕點擊功能,並詳細認識屬性繫結。