Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Свойство, которое может быть эффективно привязано к элементу управления XAML, называется наблюдаемым свойством. Эта идея основана на шаблоне проектирования программного обеспечения, известном как шаблон наблюдателя. В этом разделе показано, как реализовать наблюдаемые свойства в C++/WinRT и как привязать элементы управления XAML к ним (для фоновой информации см. привязку данных).
Это важно
Основные понятия и термины, которые поддерживают понимание использования и разработки классов среды выполнения с помощью C++/WinRT, см. в статье "Использование API с помощью C++/WinRT и разработки API с помощью C++/WinRT".
Что такое наблюдаемое значение для свойства?
Предположим, что класс среды выполнения с именемBookSku имеет свойство Title. Если BookSku вызывает событие INotifyPropertyChanged::P ropertyChanged при каждом изменении значения Title , то это означает, что Title является наблюдаемым свойством. Именно поведение BookSku (генерирует событие или нет) определяет, какие из его свойств, если вообще какие-либо, являются наблюдаемыми.
Текстовый элемент XAML или элемент управления может привязаться к этим событиям и обрабатывать их. Такой элемент или элемент управления обрабатывает событие, извлекая обновленные значения, а затем обновляя его, чтобы отобразить новое значение.
Note
Сведения об установке и использовании расширения C++/WinRT Visual Studio (VSIX) и пакета NuGet (которые вместе предоставляют поддержку шаблона проекта и сборки), см. Visual Studio поддержку C++/WinRT.
Создайте пустое приложение («Книжный магазин»)
Начните с создания проекта в Microsoft Visual Studio. Создайте проект C++ Пустое упакованное приложение (WinUI 3 на рабочем столе) и назовите его Bookstore. Убедитесь, что флажок «Разместить решение и проект в одном каталоге» снят. Нацелимся на последнюю общедоступную версию (т. е. не предварительную) пакета SDK Windows.
Мы создадим новый класс для представления книги с наблюдаемым свойством заголовка. Мы создадим и потребляем класс в одном модуле компиляции. Но мы хотим иметь возможность привязаться к этому классу из XAML, и по этой причине он будет классом среды выполнения. И мы будем использовать C++/WinRT как для его создания, так и для его использования.
Первым шагом в создании нового класса среды выполнения является добавление нового элемента Midl File (IDL) в проект. Присвойте новому элементу имя BookSku.idl. Удалите содержимое BookSku.idl по умолчанию и вставьте следующее объявление класса среды выполнения.
// BookSku.idl
namespace Bookstore
{
runtimeclass BookSku : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
BookSku(String title);
String Title;
}
}
Note
Классы модели представления ( на самом деле, любой класс среды выполнения, объявленный в приложении), не должны быть производными от базового класса. Класс BookSku , объявленный выше, является примером этого. Он реализует интерфейс, но не является производным от базового класса.
Любой класс среды выполнения, объявленный в приложении, который является производным от базового класса, называется составным классом. Существуют ограничения для составных классов. Чтобы приложение успешно проходило тесты приложение для Windows Certification Kit, используемые Visual Studio и Microsoft Store для проверки отправляемых приложений (а значит, чтобы приложение могло быть успешно принято в Microsoft Store), компонуемый класс в конечном итоге должен наследоваться от базового класса Windows. Это означает, что класс в самом корне иерархии наследования должен быть типом, определённым в пространстве имён Windows.* или Microsoft.*. Если вам действительно нужно унаследовать класс среды выполнения от базового класса — например, чтобы реализовать класс BindableBase, от которого будут наследоваться все ваши модели представления, — то можно унаследовать его от Microsoft.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\) и вставьте два скопированных файла. Наконец, выберите узел проекта в Обозревателе решений и убедитесь, что переключатель Показать все файлы включен. Щелкните правой кнопкой мыши по скопированным файлам заглушек, затем выберите Include In Project.
Реализуйте BookSku
Теперь давайте открываем \Bookstore\Bookstore\BookSku.h и BookSku.cpp реализуем класс среды выполнения. Во-первых, вы увидите static_assert в верхней части BookSku.h и BookSku.cpp, который нужно удалить.
Затем в 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(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& value);
void PropertyChanged(winrt::event_token const& token);
private:
winrt::hstring m_title;
winrt::event<Microsoft::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, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
}
}
winrt::event_token BookSku::PropertyChanged(Microsoft::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::P ropertyChanged с аргументом, равным имени свойства, которое изменилось. Таким образом, пользовательский интерфейс будет знать, какое значение свойства следует повторно запрашивать.
Проект будет построен снова, если вы хотите проверить это.
Объявите и реализуйте BookstoreViewModel
Основная страница XAML будет привязана к основной модели представления. И эта модель представления будет иметь несколько свойств, включая один из типов BookSku. На этом шаге мы объявим и реализуем класс среды выполнения модели основного представления.
Добавьте новый элемент Midl File (.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 из конструктора по умолчанию.
Note
В приведённых ниже листингах для 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;
}
}
Note
Типом m_bookSku является проецируемый тип (winrt::Bookstore::BookSku), а параметр шаблона, используемый с winrt::make , является типом реализации (winrt::Bookstore::implementation::BookSku). Тем не менее, make возвращает экземпляр проецируемого типа.
Проект будет построен снова.
Добавление свойства типа BookstoreViewModel в MainPage
Open MainPage.idl, который объявляет класс среды выполнения, представляющий главную страницу пользовательского интерфейса.
- Добавьте директиву
importдля импортаBookstoreViewModel.idl. - Добавьте свойство только для чтения с именем MainViewModel типа BookstoreViewModel.
- Удалите свойство MyProperty .
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
Сохраните файл. Проект еще не будет полностью успешным в построении, но теперь сборка является полезной задачей, так как она повторно создает файлы исходного кода, в которых реализуется класс среды выполнения MainPage (\Bookstore\Bookstore\Generated Files\sources\MainPage.h и MainPage.cpp). Так что приступайте к созданию прямо сейчас. Ошибка сборки, которую можно увидеть на этом этапе, — MainViewModel: не является членом winrt::Bookstore::implementation::MainPage.
Если опустить директиву include для 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 .
Note
В паре листингов ниже для 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&, Microsoft::UI::Xaml::RoutedEventArgs const&);
private:
Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
};
}
...
В \Bookstore\Bookstore\MainPage.cppприведенном ниже списке внесите следующие изменения.
- Вызовите winrt::make (с использованием типа реализации BookstoreViewModel), чтобы назначить m_mainViewModel новый экземпляр спроецированного типа BookstoreViewModel. Как мы видели выше, конструктор BookstoreViewModel создает новый объект BookSku в качестве члена частных данных, задав его название изначально
L"Atticus". - В обработчике событий кнопки (ClickHandler) обновите название книги до его опубликованного названия.
- Реализуйте аксессор для свойства MainViewModel.
- Удалите свойство MyProperty .
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"
using namespace winrt;
using namespace Microsoft::UI::Xaml;
namespace winrt::Bookstore::implementation
{
MainPage::MainPage()
{
m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
InitializeComponent();
}
void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Microsoft::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.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 изменилось; и кнопка повторно запрашивает значение этого свойства для обновления собственного значения содержимого .
Использование расширения разметки {Binding} с C++/WinRT
Для текущей выпущенной версии C++/WinRT, чтобы иметь возможность использовать расширение разметки {Binding}, необходимо реализовать интерфейсы ICustomPropertyProvider и ICustomProperty.
Привязка элемента к элементу
Свойство одного элемента XAML можно привязать к свойству другого элемента XAML. Ниже приведен пример того, как это выглядит в разметке.
<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />
Необходимо объявить именованную сущность myTextBox XAML как свойство только для чтения в файле Midl (IDL).
// MainPage.idl
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
MainPage();
Microsoft.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 также есть обработчик события click {x:Bind}, который также должен быть включён в IDL.
runtimeclass MyPage : Microsoft.UI.Xaml.Controls.Page
{
MyPage();
// These members are consumed by binding.
void ChangeColorButton_OnClick();
Microsoft.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
Связанные темы
Windows developer