Введение в C++ и XAML

Автор: Энди Рич
Иcточник: MSDN Magazine
Опубликована: 17.01.2013

Code First отлично подходит для проектирования быстрого алгоритма сортировки, но это вовсе не лучший способ создания хороших UI, и технологии UI, предлагавшиеся Microsoft разработчикам на C++, отражали эту тенденцию. Последнее значимое обновление технологии UI от Microsoft для C++, выпущенное в 2008 г., было новым набором MFC-элементов управления в стиле ленты. До этого была Windows Forms, технология UI на основе .NET Framework, которая впервые появилась вместе с Visual Studio .NET в феврале 2002 г. Несмотря на свою мощь, обе эти технологии были в основном сфокусированы на нижележащем коде, который создавал UI-элементы и требовал тесного связывания с данными и UI.

В последние годы разработчиков на C# и Visual Basic радовали значительными обновлениями их технологии UI в виде инфраструктур UI на основе XAML — Windows Presentation Foundation (WPF) и Silverlight. Эти инфраструктуры открыли новые возможности в проектировании UI, дав свободу в кодировании алгоритмов, не беспокоясь о том, как будет представляться UI, и свободу в разработке UI без его кодирования. Наконец, с появлением Windows 8 разработчики на C++ могут использовать преимущества современной UI-инфраструктуры на основе XAML для создания приложений Windows 8.

Почему XAML?

XAML — язык разметки на базе XML, который позволяет определять внешний вид приложения, не требуя понимания того, как создается этот UI в коде. Windows Runtime (WinRT) разбирает эту информацию в период выполнения, конструирует соответствующие классы элементов управления и формирует дерево UI. Ваш код может манипулировать этими объектами, и при правильном проектировании внешний вид UI можно существенно изменять, вообще не затрагивая его отделенный код. Это способствует разделению труда между разработчиком, который занимается отделенным кодом, и проектировщиком UI, сосредоточенном на внешнем виде приложения.

Кроме того, Visual Studio 2012 тесно интегрируется с Blend — мощным инструментом проектирования, специально оптимизированным для работы с XAML и HTML. Blend обеспечивает более эффективную работу в стиле WYSIWYG (включая дизайн анимаций) и предоставляет больше свойств XAML-объектов, чем интегрированный в Visual Studio дизайнер.

Почему C++/CX?

При написании приложений Windows Store разработчики могут выбрать любую их нескольких технологий: HTML и JavaScript, C# или Visual Basic с XAML и C++. В случае C++ можно создавать такие приложения с использованием C++/CX и XAML или DirectX, или их комбинации. Кроме того, можно создать гибридное приложение, сочетающее эти технологии, например написать какой-то компонент на C++/CX, который потом будет использоваться из приложения Windows Store, созданного на JavaScript/HTML.

Выбор C++ для написания приложения Windows Store может быть обусловлен рядом причин. Во-первых, связка C++/CX с Windows Runtime на 100% является «родной», с неуправляемым кодом и учетом ссылок (reference-counted). А это подразумевает очень малые издержки. Во-вторых, C++ способен использовать существующие библиотеки (например, Boost). Наконец, C++ поддерживает неуправляемые поверхности Direct3D и Direct2D в приложениях Windows Store, что позволяет создавать игры и другие программы, интенсивно использующие графику, с высоким быстродействием. Малые издержки и высокая производительность означает не только более быструю работу приложения, но и его компактность — использование меньшего объема инструкций для выполнения тех же задач, а это уменьшает потребление электроэнергии и продлевает время работы от аккумуляторов.

«Hello World» от C++/CX и XAML

Visual Studio 2012 предоставляет ряд шаблонов, которые демонстрируют расширенные возможности приложений Windows Store. Шаблон Grid — многофункциональное (feature-packed) приложение со множеством представлений, поддержкой переходов в альбомный и портретную ориентации, расширенным связыванием с данными, а также с функциональностью приостановки и возобновления. Однако он не очень хорошо подходит для демонстрации базовой функциональности. Поэтому вместо этого создайте новое приложение C++ Windows Store с использованием шаблона Blank App (XAML).

Создав приложение, найдите MainPage.xaml в Solution Explorer и откройте его. Этот файл открывается для редактирования в интегрированном дизайнере XAML, окно которого разделено на две секции: представление Design визуализирует UI, а представление XAML показывает код. Обновление любой из секций приводит к обновлению и другой. Это дает вам свободу в перетаскивании элементов управления для простоты редактирования или для точного управления разметкой при редактировании кода.

Откройте окно инструментария, раскройте Common XAML Controls и перетащите элемент управления TextBlock в рабочую область. Откройте окно Properties и вы увидите свойства для стилизации этого экземпляра TextBlock. В поле Name присвойте TextBlock имя «myText». Это соответствует атрибуту x:Name в XAML. Этот атрибут позволяет ссылаться на TextBlock по имени — как в XAML-файле, так и в отделенном коде. А поскольку это единственный элемент управления на странице, вы могли бы сделать его очень большим: раскройте категорию Text и смените размер шрифта на 72px. Результат должен выглядеть аналогично показанному на рис. 1. Вы заметите, что при внесении изменений в инспекторе свойств соответственно изменяется и XAML-разметка.

*
Рис. 1. Элемент управления TextBlock и связанный с ним XAML

Далее перетащите элемент управления Button из окна инструментария в левую часть TextBlock. В окне Properties просмотрите доступные события для элемента управления Button, щелкнув значок молнии справа от поля Name, а затем дважды щелкните поле для события Click. Visual Studio автоматически сгенерирует подходящий обратный вызов и переключит представление на отделенный код для этого события. В представлении исходного кода XAML сгенерированный тег должен выглядеть так:

<Button Click="Button_Click_1" />

То же самое можно было бы сделать в коде, добавив атрибут Click= для XAML-тега и указав нужное вам имя или положившись на функцию автоматического заполнения для выбора имени за вас. В любом случае вы можете щелкнуть правой кнопкой мыши событие и выбрать Navigate To Event Handler для перехода к отделенному коду.

Теперь добавьте в обратный вызов следующий код:

this->myText->Text =
    "Hello World from XAML!";

Скомпилируйте и запустите приложение для проверки того, что при щелчке кнопки обратный вызов модифицирует текст в TextBlock (рис. 2).

*
Рис. 2. Обновленный TextBlock

Здесь многое происходит «за кулисами», поэтому я потрачу немного времени на разбор этой магии.

Что такое C++/CX?

C++/CX — это языковое расширение, которое обеспечивает взаимодействие между неуправляемым C++ и Windows Runtime. Это полностью неуправляемая связка, позволяющая определять, создавать экземпляры и использовать объекты в исполняющей среде, но при этом по-прежнему обеспечивая доступ к «родной» функциональности C++ и синтаксису, с которым вы знакомы. Базовые концепции перечислены в табл. 1. (Хотя синтаксис C++/CX во многих отношениях похож на таковой в C++/Common Language Infrastructure, или CLI, это отдельное и полностью неуправляемое языковое расширение.)

Табл. 1. Концепции C++/CX

КонцепцияОписание
interface classАбстрактное объявление методов, вызовы которых будет поддерживать объект, реализующий этот интерфейс
ref classКлючевое слово в C++/CX для объявления класса с учетом ссылок на него (reference-counted class) в Windows Runtime; используется для реализации интерфейсов
^Называют «шапкой» («hat») или описателем (handle). Это смарт-указатель для использования с ref-классами и интерфейсами; автоматически увеличивает или уменьшает счетчики ссылок
ref newВыражение, которое «активирует» (конструирует) WinRT-класс
Пространство имен по умолчаниюСодержит определения фундаментальных типов (uint32, float64), сопоставляемых с WinRT-типами
Пространство имен PlatformСодержит определения типов, предоставляющих базовую модель взаимодействия с Windows Runtime (Object^, String^)
СвойствоПсевдополе класса; состоит из get-функции и дополнительно (не обязательно) set-функции и ведет себя как поле. (Например, у объекта TextBlock есть свойство Text.)

Частичные классы и генерация XAML-кода В Solution Explorer перейдите к MainPage.xaml.h, который содержит частичное определение XAML-класса MainPage:

public ref class MainPage sealed
{
public:
  MainPage();
protected:
  virtual void OnNavigatedTo(
    Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override;
private:
  void Button_Click_1(
    Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
};

Эту часть определения можно редактировать и модифицировать (например, если вам нужно добавить какие-то члены данных в класс MainPage). Заметьте, что объявление обработчика Button_Click_1 было вставлено в это определение класса средой Visual Studio, когда вы ранее дважды щелкнули событие Click.

Щелкните правой кнопкой мыши класс с именем MainPage и выберите Go To Definition. Вы должны увидеть два результата: определение в MainPage.xaml.h и частичное определение в MainPage.g.h. Второе определение генерируется при сборке, когда компилятор XAML разбирает MainPage.xaml и генерирует MainPage.g.h. MainPage.xaml.cpp включает MainPage.xaml.h, что в свою очередь приводит к включению этого сгенерированного файла. Эти определения класса объединяются и дают в итоге конечный класс. Такая методика позволяет вводить информацию в определение класса как пользователям, так и генератору кода.

Перейдите к частичному определению MainPage в MainPage.g.h:

partial ref class MainPage : public ::Windows::UI::Xaml::Controls::Page,
  public ::Windows::UI::Xaml::Markup::IComponentConnector
{
public:
  void InitializeComponent();
  virtual void Connect(int connectionId, ::Platform::Object^ target);
private:
  bool _contentLoaded;
  private: ::Windows::UI::Xaml::Controls::TextBlock^ myText;
};

Как видите, в этом сгенерированном файле атрибут x:Name="myText" в TextBlock заставил компилятор XAML сгенерировать член класса с именем myText. Здесь вы также можете наблюдать, что MainPage наследует от базового Windows XAML-класса Page и реализует интерфейс IComponentConnector. (IComponentConnector и связанный с ним метод Connect обеспечивают подключение события Click к обратному вызову Button_Click_1.)

Связывание с данными в C++/CX Связывание с данными — впечатляющая концепция, которая позволяет легко связывать код и данные в UI. Кроме того, она дает возможность использования высокоуровневых проектировочных шаблонов, таких как Model-View-ViewModel (MVVM), которая обеспечивает отличную абстракцию между данными, кодом и UI.

В C++/CX сделать класс поддерживающим связывание с данными можно несколькими способами:

Я сосредоточусь на атрибуте Bindable, но упомянул о других интерфейсах потому, что в некоторых ситуациях они подходят лучше. В частности, атрибут Bindable генерирует соответствующую информацию, только если класс с поддержкой связывания с данными помечен как открытый (public). Всякий раз, когда вам нужна поддержка связывания с данными в закрытом классе (private), вы должны реализовать один из перечисленных выше интерфейсов.

Добавьте в свое приложение новый класс, щелкнув проект правой кнопкой мыши и выбрав Add Class, чтобы вызвать мастер Add Class Wizard. В нет варианта для класса C++/CX (который вам нужен), поэтому добавьте неуправляемый класс с именем Person. Visual Studio должна сгенерировать класс в виде Person.h и Person.cpp, а затем добавить эти файлы в ваш проект.

В класс Person необходимо внести несколько изменений (рис. 3):

Рис. 3. Определение класса Person

// person.h
#pragma once
namespace App12 {
  [Windows::UI::Xaml::Data::Bindable]
  public ref class Person sealed
  {
  public:
    Person(void);
    property Platform::String^ Name;
    property Platform::String^ Phone;
  };
}
// person.cpp
#include "pch.h"
#include "person.h"
using namespace App12;
Person::Person(void)
{
  Name = "Maria Anders";
  Phone = "030-0074321";
}

Теперь в MainPage.xaml добавьте второй TextBlock под первый и придайте ему тот же внешний вид. В XAML измените атрибут Text двух элементов TextBlock на Text="{Binding Name}" и Text="{Binding Phone}" соответственно. Это сообщает UI найти свойства Name и Phone в объекте, связанном сданными, и применить их к полю Text класса. XAML должен выглядеть по аналогии с рис. 4. (Когда атрибуты Text изменяются и становятся связанными с данными, в дизайнере больше не показывается их содержимое; так и должно быть.)

*

Рис. 4. Связывание с данными в XAML

Далее в MainPage.xaml.h включите Person.h и добавьте закрытый член данных типа Person^ в класс MainPage. Назовите его m_Person. Наконец, в MainPage.xaml.cpp добавьте в метод OnNavigatedTo следующий код:

m_Person = ref new Person;
this->DataContext = m_Person;

Исполняющая среда XAML вызывает метод OnNavigatedTo перед тем, как страница показывается на экране, так что это подходящее место для подготовки контекста данных this. Этот код будет создавать новый экземпляр класса Person и связывать его с вашим объектом MainPage. Снова запустите приложение и вы должны увидеть, что свойства Name и Phone применены к атрибутам Text объектов TextBlock.

Инфраструктура связывания с данными также берет на себя уведомление связанных с данными объектов об изменении содержимого свойства. Замените код в методе Button_Click_1 на следующий:

this->m_Person->Name = "Anders, Maria";

Если вы вновь запустите приложение и щелкнете кнопку, вы увидите, что содержимое TextBlock не изменяется. Чтобы содержимое обновлялось, класс Person должен реализовать интерфейс INotifyPropertyChanged.

В INotifyPropertyChanged есть один элемент: событие PropertyChanged типа PropertyChangedEventHandler^. Свойство Name тоже нужно сменить с тривиального на полностью определенное (включая предоставляемое пользователем поддерживающее хранилище), чтобы при модификации данного свойства срабатывало это событие. Кроме того, я добавил функцию NotifyPropertyChanged, которая при вызове генерирует это событие. Это обычный рефакторинг, так как несколько свойств часто требует уведомления. Данная реализация показана на рис. 5. (В этом примере только свойство Name является уведомляемым. Аналогичные изменения нужно внести для свойства Phone.)

Рис. 5. Класс Person с уведомляемым свойством Name

// person.h
#pragma once
namespace App12 {
  namespace WUXD = Windows::UI::Xaml::Data;
  [WUXD::Bindable]
  public ref class Person sealed : WUXD::INotifyPropertyChanged
  {
  public:
    Person(void);
    property Platform::String^ Name {
      Platform::String^ get();
      void set(Platform::String^ value);
    }
    property Platform::String^ Phone;
    virtual event WUXD::PropertyChangedEventHandler^ PropertyChanged;
  private:
    void NotifyPropertyChanged(Platform::String^ prop);
    Platform::String^ m_Name;
  };
}
// person.cpp
#include "pch.h"
#include "person.h"
using namespace App12;
using namespace Platform;
using namespace WUXD;
Person::Person(void)
{
  Name = "Maria Anders";
  Phone = "030-0074321";
}
String^ Person::Name::get(){ return m_Name; }
void Person::Name::set(String^ value) {
  if(m_Name != value) {
    m_Name = value;
    NotifyPropertyChanged("Name");
  }
}
void Person::NotifyPropertyChanged(String^ prop) {
  PropertyChangedEventArgs^ args =
    ref new PropertyChangedEventArgs(prop);
  PropertyChanged(this, args);
}

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

ItemsControls и наборы в C++/CX и XAML

XAML предоставляет весьма функциональные элементы управления для UI, в том числе такие, которые можно связывать с набором объектов. Эти элементы управления собирательно называются ItemsControls. Я сосредоточусь на одном из них: ListView.

Откройте MainPage.xaml и удалите содержимое корневого объекта Grid, заменив его классом ListView. Присвойте этому классу имя myListView:

<!-- ListView внутри MainPage -->
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">*
  <ListView x:Name="myListView" />
</Grid>

Наборы в C++/CX Windows Runtime не предоставляет классы наборов — только интерфейсы для наборов. Каждая проекция (JavaScript, C#/Visual Basic и C++/CX) отвечает за предоставление собственных классов наборов. В C++/CX эти реализации находятся в заголовочном файле collection.h (который уже включен в файл pch.h шаблона Blank Application). В пространстве имен Platform::Collections определены два основных класса наборов: Platform::Collections::Vector<T> (представляет списки данных) и Platform::Collections::Map<T,U> (представляет словари). ItemsControls обычно ожидает перебираемого (iterable) списка объектов, поэтому в данном сценарии подходит Vector<T>.

В MainPage.xaml.cpp в методе OnNavigatedTo используйте Vector<Person^> с ref new и заполните его несколькими Person. Затем присвойте его свойству ItemsSource объекта myListView:

void MainPage::OnNavigatedTo(NavigationEventArgs^ e)
{
  (void) e; // неиспользуемый параметр
  auto vec =
    ref new Platform::Collections::Vector<Person^>;
  vec->Append(ref
    new Person("Maria Anders","030-0074321"));
  vec->Append(ref new Person("Ana Trujillo","(5) 555-4729"));
  // и т. д.
  myListView->ItemsSource = vec;
}

Стилизация ListView Наконец, нам нужно сообщить ListView, как стилизовать каждый индивидуальный элемент. Затем ListView будет перебирать свой ItemsSource и для каждого элемента в наборе генерировать соответствующий UI и связывать его DataContext с этим элементом. Чтобы стилизовать ListView, вы должны определить его ItemTemplate, как показано на рис. 6. Заметьте, что свойства Text внутри шаблона имеют привязки данных, подходящие для объекта Person.

Рис. 6. ListView внутри MainPage с ItemTemplate, обеспечивающим стилизацию

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
  <ListView x:Name="myListView">
    <ListView.ItemTemplate>
      <DataTemplate>
        <StackPanel Orientation="Vertical">
          <TextBlock Text="{Binding Name}" FontSize="20" />
          <TextBlock Text="{Binding Phone}" FontSize="12" />
        </StackPanel>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</Grid>

После компиляции и запуска приложение должно выглядеть примерно так, как показано на рис. 7.

*
Рис. 7. Законченный ListView

Где найти более подробные сведения

Ссылок на XAML для Windows 8 и C++/CX в настоящее время довольно мало. Чтобы узнать больше о XAML, советую поискать справочники, где говорится о XAML в Silverlight, поскольку многие концепции те же, многие элементы управления называются сходным образом или точно так же и имеют ту же функциональность. Отделенный код в них пишется на C#, поэтому вам потребуется перевести код на C++/CX. Эта операция в большей мере механическая, но там, где используется функциональность, предоставляемая .NET Base Class Library, вам нужно искать подходящее в C Runtime Libraries, Standard Template Library или другие библиотеки C++ (или Windows Runtime) для выполнения тех же задач. К счастью, все эти библиотеки прекрасно работают с C++/CX.

Другой отличный ресурс — шаблоны, доступные в Visual Studio 2012 для создания приложений Windows Store, в частности Grid App и Split App. Эти шаблоны демонстрируют многие расширенные средства профессионально разработанных приложений, и расшифровка их секретов может дать вам более хорошее понимание нижележащих инфраструктур.

Наконец, я рекомендую посмотреть проект Hilo — крупномасштабное приложение, написанное на C++ и XAML. Код и документация для этого приложения хранятся по ссылке hilo.codeplex.com. Этот проект был разработан группой Microsoft Patterns & Practices и демонстрирует современный стандартный C++, перенос существующий библиотек C++ для того, чтобы их можно было использовать в приложениях Windows Store, и многие передовые методики при применении C++/CX.

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


Ссылка: http://www.oszone.net/19817/XAML