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


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

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

Предпосылки

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

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

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

Пустой шаблон проекта приложения

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

Чтобы добавить элемент управления по шаблону, щелкните меню Project на панели инструментов или щелкните правой кнопкой мыши на проект в обозревателе решений и выберите Добавить новый элемент. В разделе 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). Для каждого DP есть две части. Во-первых, вы объявляете статическое свойство типа DependencyProperty только для чтения. У него есть имя вашего DP и Property. Это статическое свойство будет использоваться в реализации. Во-вторых, вы объявляете свойство экземпляра для чтения и записи с типом и именем вашего DP. Если вы хотите создать присоединенное свойство (а не DP), ознакомьтесь с примерами кода в настраиваемых присоединенных свойств.

Обратите внимание, что классы XAML, на которые ссылается приведенный выше код, находятся в пространствах имен Microsoft.UI.Xaml. Это то, что отличает их как элементы управления WinUI в отличие от элементов управления XAML UWP, которые определены в пространствах имен 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>
    {
    };
}

Приведенный выше код реализует свойства метка и 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добавьте новый элемент типа Visual C++ > WinUI Resource Dictionary (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 Control) и текстового элемента (текст которого привязан к свойству зависимости BgLabelControl::Label).

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

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

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

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

//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 они не являются ни виртуальными, ни защищёнными, но вы всё равно можете переопределить их и предоставить собственную реализацию, как показано выше.

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

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

Сначала добавьте в проект новый элемент Midl File (.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, чтобы предотвратить компиляцию созданных файлов. При реализации элемента управления следует удалить эти строки из файлов, помещенных в каталог проекта. В этом пошаговом руководстве можно просто перезаписать все содержимое файлов с приведенным выше кодом.

См. также