Analisis kelas jendela dan implementasi aksi klik tombol

Читатели могут обновить 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.

  1. Задаёт открытые члены класса окна: пользовательские свойства, кастомные события и вызываемые методы
  2. Компилятор автоматически генерирует стандартный WinRT ABI-код интерфейсов по файлу IDL, чтобы C++, C#, JS могли обращаться к данному окну
  3. Привязки данных и кастомные элементы управления в интерфейсе 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 (срабатывает автоматически при изменении значения свойства, используется XAML x: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 компилятор создаёт:

  1. Интерфейсы чтения/записи свойства Get/Set
  2. Событие уведомления об изменении свойства MyPropertyChanged
  3. Метаданные, обеспечивающие проверку статических привязок в 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 — каталог автоматически генерируемого кода

  1. Все файлы, автоматически создаваемые midl.exe, cppwinrt.exe и компилятором XAML при сборке проекта.
  2. Не редактируйте файлы этой папки вручную — при повторной сборке они полностью перезапишутся
  3. В стандартной конфигурации проекта 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

На этом данный урок закончен. На следующем уроке реализуем функционал нажатия кнопки и разберем принцип работы привязок свойств.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *