Поделиться через


Создание элементов управления XAML с использованием C++/WinRT

В этой статье описывается, как создать шаблонный элемент управления XAML для WinUI 3 с помощью C++/WinRT. Шаблонные элементы управления наследуют свойства от Microsoft.UI.Xaml.Controls.Control и имеют визуальную структуру и поведение, которые можно настроить с помощью шаблонов элементов управления XAML. В этой статье описывается такой же сценарий, что и в статье Создание пользовательских (на основе шаблона) элементов управления XAML с помощью C++/WinRT, но с адаптацией для WinUI 3.

Необходимые компоненты

  1. Начало работы с WinUI
  2. Скачайте и установите последнюю версию расширения C++/WinRT для Visual Studio (VSIX)

Создайте пустое приложение (BgLabelControlApp)

Начните с создания проекта в Microsoft Visual Studio. В диалоговом окне Create a new project выберите шаблон проекта Blank App (WinUI in UWP) (Пустое приложение (WinUI в UWP)) и убедитесь, что выбрана версия для языка C++. Задайте для проекта имя BgLabelControlApp, чтобы имена файлов соответствовали коду в приведенных ниже примерах. Выберите для параметра Целевая версия значение "Windows 10, версия 1903 (сборка 18362)", а для параметра Минимальная версия — значение "Windows 10, версия 1803 (сборка 17134)". Это пошаговое руководство также применимо к классическим приложениям, созданным на основе шаблона проекта Blank App, Packaged (WinUI in Desktop) (Пустое, упакованное приложение (WinUI в классических приложениях)), но не забудьте выполнить для них все шаги в проекте BgLabelControlApp (Desktop).

Шаблон проекта пустого приложения

Добавление шаблонного элемента управления в приложение

Чтобы добавить шаблонный элемент управления, откройте меню Проект на панели инструментов или щелкните правой кнопкой мыши по проекту в обозревателе решений и выберите Добавить новый элемент. В разделе Visual C++->WinUI выберите шаблон Пользовательский элемент управления (WinUI). Присвойте этому элементу управления имя BgLabelControl и щелкните Добавить. В проект будут добавлены три новых файла. Заголовок BgLabelControl.h содержит объявления элементов управления, а BgLabelControl.cpp — реализацию элемента управления на C++/WinRT. BgLabelControl.idl — это определение интерфейса, которое позволяет создавать экземпляр элемента управления в виде класса среды выполнения.

Реализация пользовательского класса элемента управления BgLabelControl

В следующих действиях вы измените код в файлах BgLabelControl.idl, BgLabelControl.h и BgLabelControl.cpp в каталоге проекта, чтобы реализовать класс среды выполнения.

Экземпляр класса шаблонного элемента управления будет создан из разметки XAML, а значит это будет класс среды выполнения. При сборке готового проекта компилятор MIDL (midl.exe) применит файл BgLabelControl.idl для создания файла метаданных среды выполнения Windows (.winmd) для этого элемента управления, и потребители компонента будут ссылаться на него. Дополнительные сведения о создании классов среды выполнения см. в статье Создание интерфейсов API с помощью C++/WinRT.

Шаблонный элемент управления, который мы здесь создаем, будет предоставлять одно свойство строкового типа, которое будет использоваться в качестве метки для элемента управления. Замените все содержимое BgLabelControl.idl следующим кодом.

// BgLabelControl.idl
namespace BgLabelControlApp
{
    runtimeclass BgLabelControl : Microsoft.UI.Xaml.Controls.Control
    {
        BgLabelControl();
        static Microsoft.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

Выше показан шаблон, которому необходимо следовать при объявлении свойства зависимостей (DP). Для каждой точки распространения существует два элемента. Сначала объявите статическое свойство, доступное только для чтения, типа DependencyProperty. Оно имеет имя вашего класса DP и свойство. Это статическое свойство будет использоваться в реализации. Во-вторых, объявите свойство экземпляра, доступного для чтения и записи, с типом и именем вашей DP. Если вы хотите создать присоединенное свойство (а не DP), см. примеры кода в статье Настраиваемые присоединенные свойства.

Обратите внимание, что классы XAML в коде выше принадлежат к пространствам имен Microsoft.UI.Xaml. Именно это отличает элементы управления WinUI от элементов управления UWP XAML, которые определены в пространствах имен Windows.UI.XAML.

Замените содержимое файла BgLabelControl.h следующим кодом.

// BgLabelControl.h
#pragma once
#include "BgLabelControl.g.h"

namespace winrt::BgLabelControlApp::implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl>
    {
        BgLabelControl() { DefaultStyleKey(winrt::box_value(L"BgLabelControlApp.BgLabelControl")); }

        winrt::hstring Label()
        {
            return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
        }

        void Label(winrt::hstring const& value)
        {
            SetValue(m_labelProperty, winrt::box_value(value));
        }

        static Microsoft::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

        static void OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const&, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const&);

    private:
        static Microsoft::UI::Xaml::DependencyProperty m_labelProperty;
    };
}
namespace winrt::BgLabelControlApp::factory_implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl, implementation::BgLabelControl>
    {
    };
}

Приведенный выше код реализует свойства Label и LabelProperty, добавляет обработчик статических событий с именем OnLabelChanged для обработки изменений в значении свойства зависимостей, а также добавляет закрытый элемент для хранения резервного поля для LabelProperty. Помните, что классы XAML в файле заголовка принадлежат к пространствам имен Microsoft.UI.Xaml, которые относятся к платформе WinUI 3, а не к пространствам имен Windows.UI.Xaml, используемым платформой пользовательского интерфейса UWP.

Затем замените содержимое файла BgLabelControl.cpp следующим кодом.

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#if __has_include("BgLabelControl.g.cpp")
#include "BgLabelControl.g.cpp"
#endif

namespace winrt::BgLabelControlApp::implementation
{
    Microsoft::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
        Microsoft::UI::Xaml::DependencyProperty::Register(
            L"Label",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
            Microsoft::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Microsoft::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
    );

    void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
    {
        if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
        {
            // Call members of the projected type via theControl.

            BgLabelControlApp::implementation::BgLabelControl* ptr{ winrt::get_self<BgLabelControlApp::implementation::BgLabelControl>(theControl) };
            // Call members of the implementation type via ptr.
        }
    }
}

В этом пошаговом руководстве не используется обратный вызов OnLabelChanged, но он предоставляется, чтобы вы могли понять, как зарегистрировать свойство зависимостей с помощью обратного вызова при изменении свойства. Реализация OnLabelChanged также демонстрирует, как получить производный проецируемый тип от базового (в данном случае базовый проецируемый тип DependencyObject). В ней также показано, как потом получить указатель на тип, реализующий тип проекции. Вторая операция естественно может быть доступной только в проекте, который реализует тип проекции (то есть проект, реализующий класс среды выполнения).

Функция xaml_typename предоставляется пространством имен Windows.UI.Xaml.Interop, которое не включено по умолчанию в шаблон проекта WinUI 3. Добавьте строку в предварительно скомпилированный файл заголовка для вашего проекта (pch.h), чтобы включить файл заголовка, связанный с этим пространством имен.

// pch.h
...
#include <winrt/Windows.UI.Xaml.Interop.h>
...

Определение стиля по умолчанию для BgLabelControl

В своем конструкторе BgLabelControl задает собственный стандартный ключ стиля. Шаблонный элемент управления должен иметь стиль по умолчанию (содержащий стандартный шаблон элемента управления), который можно использовать для отрисовки в случае, если потребитель элемента управления не задает стиль и (или) шаблон. В этом разделе мы добавим файл разметки в проект, содержащий наш стиль по умолчанию.

Убедитесь, что функция Показать все файлы все еще включена (в обозревателе решений). В узле проекта создайте папку (не фильтр, а папку) и назовите ее Themes. В разделе Themes, добавьте новый элемент с типом Visual C++ > WinUI> Словарь ресурсов (WinUI), и присвойте ему имя "generic.xaml". Имена папок и файлов должны быть такими, чтобы платформа XAML могла найти стиль по умолчанию для шаблонного элемента управления. Удалите содержимое по умолчанию файла Generic.xaml и вставьте приведенную ниже разметку.

<!-- \Themes\Generic.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BgLabelControlApp">

    <Style TargetType="local:BgLabelControl" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:BgLabelControl">
                    <Grid Width="100" Height="100" Background="{TemplateBinding Background}">
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Label}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

В этом случае единственным свойством, которое задает стиль по умолчанию является шаблон элемента управления. Шаблон состоит из квадрата (его фон привязан к свойству Background, которое имеют все экземпляры типа Элемент управления в XAML) и текстового элемента (его текст привязан к свойству зависимости BgLabelControl::Label).

Добавление экземпляра BgLabelControl на главную страницу пользовательского интерфейса

Откройте файл MainWindow.xaml, который содержит разметку XAML для главной страницы пользовательского интерфейса. Сразу после элемента Кнопка (внутри StackPanel), добавьте следующую разметку.

<local:BgLabelControl Background="Red" Label="Hello, World!"/>

Кроме того, добавьте следующую директиву MainWindow.h include, чтобы тип MainWindow (сочетание разметки XAML и императивного кода) знал тип элемента управления BgLabelControl с шаблонным элементом управления. Если вы хотите использовать BgLabelControl с другой страницы XAML, тогда также добавьте эту включенную директиву в файл заголовка для этой страницы. Или просто поместите одну включенную директиву в файл предкомпилированного заголовка.

//MainWindow.h
...
#include "BgLabelControl.h"
...

Теперь выполните сборку и запустите проект. Вы увидите, что шаблон элемента управления по умолчанию привязан к кисти фона, а также метке экземпляра BgLabelControl в разметке.

Результат запуска шаблонного элемента управления

Реализация переопределяемых функций, например MeasureOverride и OnApplyTemplate

Вы получаете шаблонный элемент управления из класса среды выполнения Control, который в дальнейшем сам является производным от классов базовой среды выполнения. Существуют также переопределяемые методы Control, FrameworkElement и UIElement, которые можно переопределить в производном классе. Этот пример кода демонстрирует, как это сделать.

// Control overrides.
void OnPointerPressed(Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

// FrameworkElement overrides.
Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size const& /* availableSize */) const { ... };
void OnApplyTemplate() const { ... };

// UIElement overrides.
Microsoft::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() const { ... };

Переопределяемые функции в разных языковых проекциях представляются по-разному. В C#, например, переопределяемые функции обычно отображаются как защищенные виртуальные функции. В C++/WinRT они не являются ни виртуальными, ни защищенными, но их по-прежнему можно переопределить и предоставить собственную реализацию, как показано выше.

Создание файлов исходного кода для элемента управления без использования шаблона

В этом разделе показано, как создать необходимые файлы исходного кода для создания пользовательского элемента управления без применения шаблона элемента Пользовательский элемент управления.

Сначала добавьте в проект новый элемент "Файл Midl (.idl)". В меню Проект выберите Добавить новый элемент... и введите "MIDL" в поле поиска, чтобы найти элемент "Файл IDL". Присвойте новому файлу имя BgLabelControl.idl, чтобы оно не отличалось от имени, приведенного в этой статье. Удалите содержимое BgLabelControl.idl по умолчанию и вставьте предложенное выше объявление класса среды выполнения.

После сохранения нового файла IDL нужно создать файл метаданных среды выполнения Windows (.winmd) и заглушки для файлов реализации CPP и H, которые вы будете использовать для реализации шаблонного элемента управления. Создайте эти файлы, выполнив сборку решения, в процессе чего компилятор MIDL (midl.exe) скомпилирует созданный вами файл IDL. Обратите внимание, что сборка решения не будет выполнена успешно и Visual Studio отобразит ошибки сборки в окне вывода, но нужные файлы будут созданы.

Скопируйте файлы заглушек BgLabelControl.h и BgLabelControl.cpp из папки \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ в папку проекта. В обозревателе решений убедитесь, что переключатель "Показывать все файлы" включен. Щелкните правой кнопкой мыши скопированные файлы заглушек и выберите Включить в проект.

Компилятор помещает строку static_assert в начале файлов BgLabelControl.h и BgLabelControl.cpp, чтобы предотвратить компиляцию уже созданных файлов. В ходе реализации элемента управления удалите эти строки из файлов в каталоге проекта. Для целей этого пошагового руководства вы можете просто перезаписать все содержимое файлов приведенным выше кодом.

См. также