Элементы управления XAML; привязка к свойству C++/WinRT

Свойство, которое может быть эффективно привязано к элементу управления XAML, называется отслеживаемым. Эта идея основана на шаблоне проектирования программного обеспечения, известном как шаблон наблюдателя. В этом разделе показано, как реализовывать отслеживаемые свойства в C++/WinRT и привязывать к ним элементы управления XAML (вводные сведения см. в разделе Привязка данных).

Важно!

Основные понятия и термины, которые помогают понять, как использовать и создавать классы среды выполнения с помощью C++/WinRT, описаны в разделах Использование интерфейсов API с помощью C++/WinRT и Создание интерфейсов API с помощью C++/WinRT.

Что означает понятие отслеживаемое для свойства?

Предположим, что класс среды выполнения BookSku имеет свойство Title. Если BookSku вызывает событие INotifyPropertyChanged::PropertyChanged всякий раз, когда меняется значение Title, это означает, что Title является отслеживаемым свойством. Поведение BookSku (вызов или не вызов события) определяет, является ли какое-либо из его свойств отслеживаемым, и если да, то какое.

Текстовый элемент (или элемент управления) XAML может привязываться к таким событиям и обрабатывать их путем получения обновленных значений и последующего самообновления для отображения новых значений.

Примечание.

Сведения об установке и использовании расширения C++/WinRT для Visual Studio (VSIX) и пакета NuGet (которые вместе обеспечивают поддержку шаблона проекта и сборки) см. в разделе о поддержке C++/WinRT в Visual Studio.

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

Начните с создания проекта в Microsoft Visual Studio. Создайте проект Пустое приложение (C++/WinRT) и назовите его Bookstore. Убедитесь, что снят флажок Поместить решение и проект в одном каталоге. В качестве цели выберите последнюю общедоступную (то есть не предварительную) версию Windows SDK.

Мы создадим новый класс, представляющий книгу, которая имеет отслеживаемое свойство названия. Класс создается и используется в рамках одной и той же единицы компиляции. Однако мы хотим иметь возможность привязывать этот класс из XAML, поэтому это будет класс среды выполнения. Кроме того, мы применим C++/WinRT для его создания и использования.

Первый шаг при создании нового класса среды выполнения — добавление в проект нового элемента Файл Midl (.idl). Присвойте новому элементу имя BookSku.idl. Удалите содержимое BookSku.idl по умолчанию вставьте в это объявление класса среды выполнения.

// BookSku.idl
namespace Bookstore
{
    runtimeclass BookSku : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        BookSku(String title);
        String Title;
    }
}

Примечание.

Классы модели представления (фактически, как и любой класс среды выполнения, который объявляется в приложении) не обязательно должны быть производными от базового класса. Примером этого является класс BookSku, объявленный выше. Он реализует интерфейс, но он не является производным от базового класса.

Любой класс среды выполнения, объявленный в приложении, который является производным от базового класса, называется составным. На составные классы накладываются определенные ограничения. Чтобы приложение прошло тесты комплекта сертификации приложений для Windows, используемые Visual Studio и Microsoft Store для проверки отправляемого кода (и, следовательно, чтобы оно было успешно принято в Microsoft Store), составляемый класс в конечном счете должен быть производным от базового класса Windows. Это означает, что класс с самого корня иерархии наследования должен иметь тип, полученный в пространстве имен Windows.*. Если вам нужно создать класс среды выполнения, производный от базового класса (например, чтобы реализовать класс BindableBase для всех моделей представления для создания производных объектов), то он может быть производным от Windows.UI.Xaml.DependencyObject.

Модель представления — это абстракция представления, поэтому она привязана непосредственно к представлению (разметке XAML). Модель данных — это абстракция данных, которая используется только из моделей представления и не привязывается непосредственно к XAML. Поэтому вы можете объявлять модели данных не как классы среды выполнения, а как структуры или классы C++. Их не нужно объявлять в MIDL, и вы можете использовать любую иерархию наследования.

Сохраните файл и выполните сборку проекта. Сборка не будет завершена полностью, но будут выполнены некоторые нужные нам действия. А именно: во время сборки будет запущен инструмент midl.exe для создания файла метаданных среды выполнения Windows, который описывает класс среды выполнения (файл, размещенный на диске по адресу \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). Затем запускается инструмент cppwinrt.exe для создания файлов исходного кода для поддержки создания и использования вашего класса среды выполнения. Эти файлы содержат заглушки для реализации класса среды выполнения BookSku, объявленного в IDL. Эти заглушки на диске — \Bookstore\Bookstore\Generated Files\sources\BookSku.h и BookSku.cpp.

Щелкните правой кнопкой мыши узел проекта в Visual Studio и выберите команду Открыть папку в проводнике. Папка проекта откроется в проводнике. Теперь вы должны увидеть содержимое папки \Bookstore\Bookstore\. Затем перейдите в папку \Generated Files\sources\ и скопируйте файлы заглушки BookSku.h и BookSku.cpp в буфер обмена. Вернитесь в папку проекта (\Bookstore\Bookstore\) и вставьте два скопированных файла. Наконец, выберите узел проекта в Обозревателе решений и убедитесь, что переключатель Показать все файлы включен. Щелкните правой кнопкой мыши скопированные файлы заглушек и выберите Включить в проект.

Реализуйте BookSku

Теперь давайте откроем \Bookstore\Bookstore\BookSku.h и BookSku.cpp и реализуем класс среды выполнения. Сначала вы увидите в верхней части BookSku.h и BookSku.cpp декларацию static_assert, которую нужно удалить.

Затем внесите эти изменения в BookSku.h.

  • В конструкторе по умолчанию измените = default на = delete. Это обусловлено тем, что нам не нужен конструктор по умолчанию.
  • Добавьте закрытый член для хранения строки заголовка. Обратите внимание, что у нас есть конструктор, принимающий значение winrt::hstring. Это значение является строкой заголовка.
  • Добавьте еще один закрытый член для события, которое будет создаваться при изменении заголовка.

После внесения этих изменений BookSku.h выглядит следующим образом.

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

namespace winrt::Bookstore::implementation
{
    struct BookSku : BookSkuT<BookSku>
    {
        BookSku() = delete;
        BookSku(winrt::hstring const& title);

        winrt::hstring Title();
        void Title(winrt::hstring const& value);
        winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookSku : BookSkuT<BookSku, implementation::BookSku>
    {
    };
}

В BookSku.cpp реализуйте функции следующим образом.

// BookSku.cpp
#include "pch.h"
#include "BookSku.h"
#include "BookSku.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookSku::BookSku(winrt::hstring const& title) : m_title{ title }
    {
    }

    winrt::hstring BookSku::Title()
    {
        return m_title;
    }

    void BookSku::Title(winrt::hstring const& value)
    {
        if (m_title != value)
        {
            m_title = value;
            m_propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

    winrt::event_token BookSku::PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return m_propertyChanged.add(handler);
    }

    void BookSku::PropertyChanged(winrt::event_token const& token)
    {
        m_propertyChanged.remove(token);
    }
}

В функции-мутаторе Title мы проверяем, задано ли значение, которое отличается от текущего значения. Если это так, мы обновляем название, а также вызываем событие INotifyPropertyChanged::PropertyChanged с аргументом, соответствующим имени свойства, которое было изменено. Это делается для того, чтобы пользовательскому интерфейсу было известно, значение какого свойства следует запрашивать повторно.

Для проекта будет снова выполнена сборка, если вы хотите проверить это.

Объявление и реализация BookstoreViewModel

Наша главная страница XAML будет привязываться к модели главного представления. И модель этого представления будет иметь несколько свойств, включая свойство типа BookSku. На этом шаге мы объявим и реализуем класс среды выполнения модели главного представления.

Добавьте новый элемент Файл Midl (.idl) с именем BookstoreViewModel.idl. Также см. раздел о разделении классов среды выполнения на файлы Midl (.idl).

// BookstoreViewModel.idl
import "BookSku.idl";

namespace Bookstore
{
    runtimeclass BookstoreViewModel
    {
        BookstoreViewModel();
        BookSku BookSku{ get; };
    }
}

Сохраните и выполните сборку (сборка не будет полностью успешной, но мы выполняем ее, чтобы снова создать файлы заглушек).

Скопируйте BookstoreViewModel.h и BookstoreViewModel.cpp из папки Generated Files\sources в папку проекта и добавьте их в проект. Откройте эти файлы (снова удалив static_assert) и реализуйте класс среды выполнения следующим образом. Обратите внимание на то, как в BookstoreViewModel.h мы добавляем BookSku.h, который объявляет тип реализации для BookSku (то есть winrt::Bookstore::implementation::BookSku). И мы удаляем = default из конструктора по умолчанию.

Примечание.

В приведенных ниже листингах для BookstoreViewModel.h и BookstoreViewModel.cpp код демонстрирует способ создания элемента данных m_bookSku по умолчанию. Это метод, который был реализован еще в первом выпуске C++ /WinRT, поэтому рекомендуется хотя бы ознакомиться с этим шаблоном. Начиная с версии C++/WinRT 2.0 вам доступна оптимизированная форма создания, известная как универсальное создание (см. раздел Новости и изменения в C++/WinRT 2.0). Далее в этой статье мы приведем пример универсального создания.

// BookstoreViewModel.h
#pragma once
#include "BookstoreViewModel.g.h"
#include "BookSku.h"

namespace winrt::Bookstore::implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
    {
        BookstoreViewModel();

        Bookstore::BookSku BookSku();

    private:
        Bookstore::BookSku m_bookSku{ nullptr };
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel, implementation::BookstoreViewModel>
    {
    };
}
// BookstoreViewModel.cpp
#include "pch.h"
#include "BookstoreViewModel.h"
#include "BookstoreViewModel.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookstoreViewModel::BookstoreViewModel()
    {
        m_bookSku = winrt::make<Bookstore::implementation::BookSku>(L"Atticus");
    }

    Bookstore::BookSku BookstoreViewModel::BookSku()
    {
        return m_bookSku;
    }
}

Примечание.

Тип m_bookSku является проецируемым типом (winrt::Bookstore::BookSku), а параметр шаблона, который используется с winrt::make, является типом реализации (winrt::Bookstore::implementation::BookSku). Даже в этом случае make возвращает экземпляр проецируемого типа.

Для проекта снова будет выполнена сборка.

Добавьте свойство типа BookstoreViewModel в MainPage

Откройте MainPage.idl, где объявляется класс среды выполнения, представляющий собой главную страницу пользовательского интерфейса.

  • Добавьте директиву import для импорта BookstoreViewModel.idl.
  • Добавьте свойство только для чтения с именем MainViewModel типа BookstoreViewModel.
  • Удалите свойство MyProperty.
// MainPage.idl
import "BookstoreViewModel.idl";

namespace Bookstore
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

Сохраните файл. Сборка проекта не будет выполнена до конца, но выполнить сборку будет необходимо, так как будут повторно созданы файлы исходного кода, в которых реализуется класс среды выполнения MainPage (\Bookstore\Bookstore\Generated Files\sources\MainPage.h и MainPage.cpp). Так что действуйте и выполните сборку. На этом этапе должна появиться ошибка сборки 'MainViewModel': не является членом 'winrt::Bookstore::implementation::MainPage'.

Если опустить добавление BookstoreViewModel.idl (см. список MainPage.idl выше), то появится ошибка ожидается < рядом с MainViewModel. Еще один совет: убедитесь, что все типы остаются в одном и том же пространстве имен — пространстве имен, которое показано в листингах кода.

Чтобы устранить эту ожидаемую ошибку, необходимо скопировать заглушки метода доступа для свойства MainViewModel из созданных файлов (\Bookstore\Bookstore\Generated Files\sources\MainPage.h и MainPage.cpp) в \Bookstore\Bookstore\MainPage.h и MainPage.cpp. Как это сделать, описано далее.

В \Bookstore\Bookstore\MainPage.h выполните такие действия.

  • Включите файл BookstoreViewModel.h, который объявляет тип реализации для BookstoreViewModel (то есть winrt::Bookstore::implementation::BookstoreViewModel).
  • Добавьте частный член для хранения модели представления. Обратите внимание на то, что функция доступа свойства (и член m_mainViewModel) реализуется как проецируемый тип для BookstoreViewModel (то есть Bookstore::BookstoreViewModel).
  • Тип реализации находится в том же проекте (единице компиляции), что и приложение, поэтому мы создаем m_mainViewModel через перегрузку конструктора, которая принимает std::nullptr_t.
  • Удалите свойство MyProperty.

Примечание.

В приведенных ниже листингах для MainPage.h и MainPage.cpp код демонстрирует способ создания элемента данных m_mainViewModel по умолчанию. В следующем разделе мы приведем вариант с использованием универсального создания.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
namespace winrt::Bookstore::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        Bookstore::BookstoreViewModel MainViewModel();

        void ClickHandler(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&);

    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
    };
}
...

В \Bookstore\Bookstore\MainPage.cpp, как показано в приведенном ниже списке, внесите следующие изменения.

  • Вызовите winrt::make (с типом реализации BookstoreViewModel) для назначения нового экземпляра проецируемого типа BookstoreViewModel для m_mainViewModel. Как было показано выше, конструктор BookstoreViewModel создает объект BookSku в качестве закрытого элемента данных, устанавливая для его заголовка значение L"Atticus".
  • В обработчике событий кнопки (ClickHandler) измените заголовок книги на опубликованный заголовок.
  • Реализуйте метод доступа для свойства MainViewModel.
  • Удалите свойство MyProperty.
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

using namespace winrt;
using namespace Windows::UI::Xaml;

namespace winrt::Bookstore::implementation
{
    MainPage::MainPage()
    {
        m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
        InitializeComponent();
    }

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Windows::UI::Xaml::RoutedEventArgs const& /* args */)
    {
        MainViewModel().BookSku().Title(L"To Kill a Mockingbird");
    }

    Bookstore::BookstoreViewModel MainPage::MainViewModel()
    {
        return m_mainViewModel;
    }
}

Универсальное создание

Чтобы использовать универсальное создание, а не winrt::make, в файле MainPage.h объявите и инициализируйте m_mainViewModel за один шаг, как показано ниже.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
struct MainPage : MainPageT<MainPage>
{
    ...
private:
    Bookstore::BookstoreViewModel m_mainViewModel;
};
...

В конструкторе MainPage в файле MainPage.cpp не требуется указывать код m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Дополнительные сведения об универсальном создании и примеры кода см. в статье о предоставлении согласия на использование универсального создания и прямого обращения к реализации.

Привязывание кнопки к свойству Title

Откройте файл MainPage.xaml, который содержит разметку XAML для главной страницы пользовательского интерфейса. Как показано в листинге иже, удалите имя с кнопки и измените значение ее свойства Content с литерала на выражение привязки. Обратите внимание на свойство Mode=OneWay в выражении привязки (односторонняя из модели представления к пользовательскому интерфейсу). Без этого свойства пользовательский интерфейс не будет реагировать на события изменения свойств.

<Button Click="ClickHandler" Content="{x:Bind MainViewModel.BookSku.Title, Mode=OneWay}"/>

Теперь выполните сборку и запустите проект. Нажмите кнопку, чтобы запустить обработчик события Click. Этот обработчик вызывает функция-мутатор названия книги. Этот мутатор порождает событие, чтобы сообщить пользовательскому интерфейсу о том, что свойство Title изменилось. А кнопка повторно запрашивает значение этого свойства, чтобы обновить свое собственное значение Content.

Использование расширения разметки {Binding} с C++/WinRT

Чтобы иметь возможность использовать расширение разметки {Binding} с текущей выпущенной версией C++/WinRT, необходимо реализовать интерфейсы ICustomPropertyProvider и ICustomProperty.

Привязка между элементами

Вы можете привязать свойство одного элемента XAML к свойству другого элемента XAML. Вот как это выглядит в разметке:

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

Вам потребуется объявить именованную сущность XAML myTextBox как свойство только для чтения в своем файле Midl (IDL-файле).

// MainPage.idl
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
    MainPage();
    Windows.UI.Xaml.Controls.TextBox myTextBox{ get; };
}

Вот почему это требуется. Все типы, которые компилятору XAML нужно проверить (включая те, которые используются в {x: Bind}) считываются из метаданных Windows (WinMD). Вам нужно всего лишь добавить свойство только для чтения в файл Midl. Не реализуйте это, так как автоматически созданный код XAML обеспечивает реализацию.

Использование объектов из разметки XAML

Все сущности, используемые при применении расширения разметки XAML {x:Bind}, должны быть общедоступными в IDL. Более того, если разметка XAML содержит ссылку на другой элемент, который также находится в разметке, метод получения для этой разметки должен присутствовать в IDL.

<Page x:Name="MyPage">
    <StackPanel>
        <CheckBox x:Name="UseCustomColorCheckBox" Content="Use custom color"
             Click="UseCustomColorCheckBox_Click" />
        <Button x:Name="ChangeColorButton" Content="Change color"
            Click="{x:Bind ChangeColorButton_OnClick}"
            IsEnabled="{x:Bind UseCustomColorCheckBox.IsChecked.Value, Mode=OneWay}"/>
    </StackPanel>
</Page>

Элемент ChangeColorButton ссылается на элемент UseCustomColorCheckBox с помощью привязки. Поэтому в IDL для этой страницы необходимо объявить свойство только для чтения с именем UseCustomColorCheckBox, чтобы это свойство было доступно для привязки.

В делегате обработчика событий щелчков для UseCustomColorCheckBox используется классический синтаксис делегата XAML. Поэтому запись в IDL не требуется. Свойство просто должно быть общедоступным в классе реализации. С другой стороны, ChangeColorButton также имеет обработчик событий щелчков {x:Bind}, который аналогичным образом должен войти в IDL.

runtimeclass MyPage : Windows.UI.Xaml.Controls.Page
{
    MyPage();

    // These members are consumed by binding.
    void ChangeColorButton_OnClick();
    Windows.UI.Xaml.Controls.CheckBox UseCustomColorCheckBox{ get; };
}

Вам не нужно предоставлять реализацию для свойства UseCustomColorCheckBox. Это сделает генератор кода XAML.

Привязка к логическому значению

Это можно сделать в режиме диагностики.

<TextBlock Text="{Binding CanPair}"/>

Отобразится значение true или false в C++/CX и значение Windows.Foundation.IReference`1<Boolean> в C++/WinRT.

Вместо этого при создании привязки к логическому значению используйте x:Bind.

<TextBlock Text="{x:Bind CanPair}"/>

Использование библиотек реализации Windows (WIL)

Библиотеки реализации Windows (WIL) предоставляют вспомогательные средства для упрощения написания привязываемых свойств. См . статью "Уведомления о свойствах " в документации ПО WIL.

Важные API