Читатели могут обновить Visual Studio 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-разметки, предназначенный исключительно для создания макетов пользовательского интерфейса. Он отвечает только за визуальное отображение интерфейса и не содержит бизнес-логики.
В нём можно описывать контейнеры макета и все виды оконных элементов управления: 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.
- Задаёт открытые члены класса окна: пользовательские свойства, кастомные события и вызываемые методы
- Компилятор автоматически генерирует стандартный WinRT ABI-код интерфейсов по файлу IDL, чтобы C++, C#, JS могли обращаться к данному окну
- Привязки данных и кастомные элементы управления в интерфейсе XAML работают только при объявлении свойств в IDL
Почему файл .idl обязателен для WinUI3 C++/WinRT?
Потому что 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для автообновления интерфейса)
Только члены, объявленные в IDL, можно привязывать в XAML через конструкцию {x:Bind MyProperty}; если объявить обычную переменную только в файле .h, движок XAML её не увидит, привязка выдаст ошибку.
Можно правой кнопкой мыши скомпилировать IDL-файл отдельно, обработку выполняет утилита midl.exe. Она генерирует метаданные для C# и заголовочные файлы интерфейсов для C++.
Проще говоря, IDL создаёт интерфейсы для межъязыковых вызовов и поставляет метаданные, нужные для привязок данных XAML. Почему WinUI 3 на C# не требует IDL? Потому что C# имеет встроенную рефлексию и метаданные, это управляемый язык. Версия WinRT на C++ не имеет собственных механизмов рефлексии и метаданных.
Если объявить член-переменную только в файле .h, движок XAML её совершенно не увидит;
Только при объявлении Int32 MyProperty; в IDL компилятор создаёт:
- Интерфейсы чтения/записи свойства Get/Set
- Событие уведомления об изменении свойства
MyPropertyChanged - Метаданные, обеспечивающие проверку статических привязок в XAML
Даже если проект использует исключительно C++ и не взаимодействует с C#/JS и другими языками, IDL обязателен при использовании XAML
WinRT создан вокруг единого ABI, чтобы C++ / C# / JS / Python могли взаимно вызывать компоненты:
IDL — нейтральный описательный язык, не привязанный к какому-либо языку программирования. Компиляторы каждого языка считывают генерируемые IDL файлы метаданных .winmd для распознавания ваших классов, свойств и методов;
Нажмите правой кнопкой мыши по 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;
Здесь прописываются поля класса, приватные переменные и опережающие объявления, как в обычных .h файлах синтаксиса C++.
Перечисляет все переменные и функции без их реализации; другие классы могут обращаться к нему через директиву include.
// Защита заголовка: предотвращает многократное подключение файла и ошибки повторного объявления при компиляции
#pragma once
// Подключение связующего заголовка, автоматически генерируемого при компиляции MainWindow.idl
// В .g.h содержится интерфейс IMainWindow из IDL, шаблон базового класса CRTP MainWindowT<> и определения метаданных WinRT
// Все свойства MyProperty и интерфейсы конструктора, объявленные в IDL, прописаны здесь
#include "MainWindow.g.h"
// 【Пространство имён реализации】Хранит класс реализации бизнес-логики окна, здесь мы реализуем собственные свойства и функции
namespace winrt::WinuiCppDemo::implementation
{
// MainWindowT<MainWindow> — CRTP базовый класс из файла .g.h
// 1. Автоматически наследует интерфейс IMainWindow из IDL и требует реализации всех членов, объявленных в IDL
// 2. Инкапсулирует низкоуровневую логику окон WinUI, инициализацию XAML-элементов и пересылку вызовов по ABI-интерфейсам
struct MainWindow : MainWindowT<MainWindow>
{
// Стандартный конструктор окна, соответствует MainWindow(); объявленному в IDL
MainWindow()
{
// Официальное правило: запрещено вызывать InitializeComponent() внутри конструктора
// Инициализация XAML-элементов выполняется фреймворком с задержкой; вызов в конструкторе приводит к падению жизненного цикла
// См. официальную документацию cppwinrt Microsoft
// See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent
}
// Автоматически генерируемый интерфейс чтения свойства Int32 MyProperty; из IDL
// Возвращает числовое значение свойства типа int32_t
int32_t MyProperty();
// Автоматически генерируемый интерфейс записи свойства Int32 MyProperty; из IDL
// Изменяет значение свойства и автоматически вызывает событие 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"
// Условная компиляция: проверяет наличие низкоуровневого связующего файла реализации MainWindow.g.cpp, генерируемого по IDL
// Файл .g.cpp создаётся утилитами midl+cppwinrt из .idl, инкапсулирует события изменения свойств и логику пересылки по низкоуровневому ABI
// При наличии подключаем его, чтобы уведомления об изменении свойств (MyPropertyChanged) и рантайм-привязки XAML работали корректно
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif
// Упрощение глобальных пространств имён, не нужно постоянно писать префикс winrt:: во всём коде
using namespace winrt;
// Пространство имён основных API элементов управления, окон и маршрутизируемых событий WinUI Xaml
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
{
// Реализация метода чтения свойства MyProperty, объявленного в IDL
// Возвращает значение свойства типа Int32; XAML x:Bind вызывает эту функцию для считывания данных
int32_t MainWindow::MyProperty()
{
// Стандартный шаблон выбрасывает исключение о нереализованном методе, замените этот код логикой возврата вашей реальной хранимой переменной
throw hresult_not_implemented();
}
// Реализация метода записи свойства MyProperty, объявленного в IDL
// value: новое значение для присваивания свойству; после изменения подсистема автоматически генерирует событие MyPropertyChanged для обновления привязанного интерфейса
void MainWindow::MyProperty(int32_t /* value */)
{
// Стандартный шаблон выбрасывает исключение о нереализованном методе, самостоятельно добавьте логику присваивания переменных
throw hresult_not_implemented();
}
}Code language: PHP (php)
Далее добавим кнопку на это главное окно. При нажатии кнопки свойство MyProperty автоматически увеличится на единицу, после чего откроется диалоговое окно с отображением текущего значения MyProperty.
Сначала добавим кнопку в файл XAML
<Grid>
<Button Content="Click Me" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"/>
</Grid>Code language: C++ (cpp)
Текст на кнопке — «Click Me», к ней привязано событие нажатия OnButtonClick.
Наведите курсор мыши на OnButtonClick и нажмите F12: Visual Studio автоматически создаёт исполняемую функцию для этого события.
После этого вы увидите дополнения в файлах 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
На этом данный урок закончен. На следующем уроке реализуем функционал нажатия кнопки и разберем принцип работы привязок свойств.