Август 2015

Том 30 выпуск 8

Windows 10 - Современная поддержка «drag-and-drop» для универсальных Windows-приложений

Ален Цанкетта

Эта статья основана на общедоступной предварительной версии Windows 10 и Visual Studio 2015.

Продукты и технологии:

Windows 10, XAML, универсальные Windows-приложения

В статье рассматриваются:

  • концепции перетаскивания (drag-and-drop);
  • реализация источников (drag sources) и мишеней (drop targets) в операции перетаскивания;
  • настройка визуальной обратной связи;
  • применение асинхронных операций.

Исходный код можно скачать по ссылке msdn.microsoft.com/magazine/

Перетаскивание (операция «drag-and-drop») — интуитивно понятный способ переноса данных в рамках приложения или между приложения на рабочем столе Windows. Дебют этой функциональности состоялся в Windows 3.1 с File Manager, а затем она была распространена на все приложения, поддерживающие OLE 2, например на Microsoft Office. Когда вышла Windows 8, Microsoft ввела новый тип Windows-приложений — приложения Windows Store, которые были рассчитаны на планшеты и всегда отображались в полноэкранном режиме. Поскольку в любой момент на экране было видно только одно приложение, перетаскивание между приложениями потеряло смысл, и были разработаны другие способы обмена данными, такие как Share Charm. Однако в Windows 10 приложения на настольных ПК вновь выполняются в оконном режиме, а значит, на экране отображается несколько окон, и поэтому функциональность перетаскивания вернулась на сцену в виде Universal Windows App API с добавлением новых средств, улучшающих пользовательскую среду (UX).

Концепции перетаскивания

Перетаскивание дает возможность пользователю перемещать данные между приложениями или внутри одного приложения с помощью стандартного жеста («нажать-удерживать-сдвигать» пальцем или «нажать-сдвигать» мышью или пером).

Источник, которым является приложение или область, где инициирован жест перетаскивания, предоставляет данные для перемещения, заполняя объект пакета данных. Этот объект может содержать данные как в стандартных форматах, включая текст, RTF, HTML, битовые карты, элементы хранилища, так и в собственных форматах. Источник также указывает вид поддерживаемых им операций: копирование, перемещение или связывание. Когда указатель освобождается, происходит отпускание объекта (drop). Мишень, которой является приложение или область под указателем, обрабатывает этот пакет данных и возвращает тип выполненной ею операции.

При операции «drag-and-drop» UI перетаскивания обеспечивает визуальное обозначение типа этой операции. Визуальная обратная связь изначально предоставляется источником, но может изменяться мишенями, по которым движется указатель.

Современные операции «drag-and-drop» доступны на настольных компьютерах, планшетах и смартфонах. Они позволяют перемещать данные между любыми видами приложений или внутри них, включая традиционные Windows-приложения, хотя в этой статье основное внимание уделяется XAML API для современных операций «drag-and-drop».

Реализация перетаскивания

Источник и мишень играют разные роли. В приложении могут быть UI-компоненты, которые выступают только в роли источников, только в роли мишеней или и тех, и других одновременно, например как в приложении-примере Photo Booth (рис. 1).

Источники и мишени
Рис. 1. Источники и мишени

The photomontage is a drag source which can be dropped as an image in Wordpad and other apps Фотомонтаж — это источник, который можно перетащить как изображение в Wordpad и другие приложения
Each image becomes a drag source when it contains an image Каждое изображение становится источником, когда оно содержит некое изображение
Each image placeholder is a drop target accepting images Любое место для изображения является мишенью, принимающей изображения
The photomontage is a drop target accepting text to add a caption Фотомонтаж является мишенью, которая принимает текст, добавляемый как надпись

Операция «drag-and-drop» полностью управляется пользовательским вводом, поэтому ее реализация основывается почти всегда на событиях, как показано на рис. 2.

События «drag-and-drop»
Рис. 2. События «drag-and-drop»

DragStarting event is raised on source В источнике генерируется событие DragStarting
DragEnter event is raised on Target #1 В мишени 1 генерируется событие DragEnter
DragOver events are raised on Target #1 В мишени 1 генерируются события DragOver
DragLeave event is raised on Target #1 В мишени 1 генерируется событие DragLeave
DragEnter event is raised on Target #2 В мишени 2 генерируется событие DragEnter
DragOver events are raised on Target #2 В мишени 2 генерируются события DragOver
Drop event is raised on Target #2 В мишени 2 генерируется событие Drop
Source Источник
Target #1 Мишень 1
Target #2 Мишень 2
DropCompleted event is raised on source В источнике генерируется событие DropCompleted
Left button is pressed Левая кнопка нажата
Pointer is moved while button is still pressed Указатель перемещается при удерживаемой левой кнопке
Button is released when on a target Кнопка отпускается, когда указатель попадает на мишень

Реализация источника  В Windows 8.1 элемент управления ListView может быть источником операции «drag-and-drop» внутри приложения, если его свойство CanDragItems установлено в true:

<ListView CanDragItems="True"
  DragItemsStarting="ListView_DragItemsStarting"
  ItemsSource="{Binding Pictures}"
  ItemTemplate="{StaticResource PictureTemplate}"
  />

Приложение может обрабатывать событие DragItemsStarting в источнике. Это по-прежнему поддерживается в Windows 10 с добавлением события DragItemsCompleted, которое не требовалось приложениям в Windows 8.1, где мишень и источник должны были относиться к одному и тому же процессу.

Основной источник в современной функциональности «drag-and-drop» — это UIElement, который открывает доступ ко всем средствам современной функциональности «drag-and-drop» и находится в центре внимания этой статьи.

Один из способов сделать UIElement перетаскиваемым — установить его свойство CanDrag в true. Это можно сделать в разметке или в отделенном коде (codebehind). Инфраструктура XAML обрабатывает распознавание жестов и генерирует событие DragStarting, сообщая о начале операции перетаскивания. Приложение должно сконфигурировать DataPackage, заполнив его содержимое и указав, какие операции поддерживаются. Приложение-источник может помещать в DataPackage данные разных форматов, которые сделают его совместимым с большим количеством мишеней (рис. 3). Поддерживаемые операции определяются в типе DataPackageOperation и могут быть Copy, Move, Link или любой их комбинацией.

Рис. 3. Обработка DragStarting и заполнение DataPackage

private void DropGrid_DragStarting(UIElement sender,
  DragStartingEventArgs args)
{
  if (Picture == null)
  {
    args.Cancel = true;
  }
  else if (_fileSource != null)
  {
    args.Data.RequestedOperation =
      DataPackageOperation.Copy | DataPackageOperation.Move;
    args.Data.SetStorageItems(new IStorageItem[] { _fileSource });
  ...
}

Вы можете отменять операцию Drag в обработчике событий, установив свойство Cancel параметра DragStartingEventArgs. Так, в нашем приложении-примере в месте для размещения изображений операция Drag начнется, только если на него перетаскивают изображение или файл.

Обработчик DragStartingEvent также является местом, где приложение-источник может настраивать UI перетаскивания, но об этом позже.

В некоторых случаях приложению может потребоваться использовать особый жест для запуска операции «drag-and-drop» или разрешить перетаскивание какого-то элемента управления, который при обычном взаимодействии препятствует использованию стандартного жеста перетаскивания, например TextBox, реагирующий на события, связанные с движением указателя вниз, изменением выделенной в нем области. В таких случаях приложение может реализовать распознавание собственного жеста, а затем инициировать операцию «drag-and-drop» вызовом метода StartDragAsync. Заметьте, что этот метод ожидает передачи идентификатора указателя (pointer identifier) и поэтому вы не можете начать операцию «drag-and-drop» с помощью нестандартных устройств вроде датчика Kinect. После вызова StartDragAsync остальная часть операции «drag-and-drop» следует тому же шаблону, что и при использовании CanDrag=True, включая событие DragStarting.

Как только пользователь освобождает указатель, операция «drag-and-drop» завершается, и источник уведомляется через событие DropCompleted, содержащее DataPackageOperation, возвращаемое мишенью, на которой пользователь отпустил указатель, или DataPackageOperation.None, если указатель был отпущен на мишени, которая не принимает данные, или если была нажата кнопка отмена (Cancel):

private void DropGrid_DropCompleted(UIElement sender,
  DropCompletedEventArgs args)
{
  if (args.DropResult == DataPackageOperation.Move)
  {
    // Перемещение означает, что мы должны очистить изображение
    Picture = null;
    _bitmapSource = null;
    _fileSource = null;
  }
}

StartDragAsync возвращает IAsyncOperation<DataPackageOperation>; приложение-источник может обработать завершение операции либо ожидая на IAsyncOperation, либо обрабатывая событие DropCompleted. Программная отмена после события DragStarting возможна через интерфейс IAsyncOperation, но может озадачить пользователя.

Заметьте: хотя операции «drag-and-drop» как ListView, так и UIElement реализуются одними и теми же системными сервисами и полностью совместимы, они генерируют разные события на стороне источника. То есть, если у ListView свойство CanDragItems установлено в true, генерируются лишь DragItemsStarting и DragItemsCompleted. DragStarting и DropCompleted — события, связанные со свойством CanDrag в UIElement.

Реализация мишени Любой UIElement может быть мишенью при условии, что его свойство AllowDrop установлено в true. В ходе операции «drag-and-drop» в мишени могут генерироваться следующие события: DragEnter, DragOver, DragLeave и Drop. Эти события уже есть в Windows 8.1, но класс DragEventArgs был расширен в Windows 10, чтобы обеспечить приложениям доступ ко всем средствам современной поддержки «drag-and-drop». При обработке события «drag-and-drop» приложение-мишень должно сначала проверить содержимое DataPackage через свойство DataView аргумента события; в большинстве случаев проверка на наличие типа данных достаточна, и это можно делать синхронно. В некоторых случаях, например при передаче файлов, приложению может понадобиться проверка типа доступных файлов до приема или игнорирования DataPackage. Эта операция асинхронная (данный шаблон будет подробно изложен далее в этой статье).

Определив, можно ли обработать эти данные, мишень должна установить свойство AcceptedOperation экземпляра DragEventArgs, чтобы дать возможность системе обеспечить пользователя корректную обратную связь.

Заметьте: если приложение возвращает DataTransferOperation.None из обработчика событий (или операция не принимается источником), перетаскивание будет безрезультатным, даже когда пользователь отпустит указатель прямо над мишенью; вместо этого будет сгенерировано событие DragLeave.

Приложение может обрабатывать либо DragEnter, либо DragOver; AcceptedOperation, возвращаемое DragEnter, сохраняется, если DragOver не обрабатывается. Когда DragEnter вызывается лишь раз, его следует предпочесть в сравнении с DragOver по причинам большей производительности. Однако в случае вложенных мишеней необходимо возвращать корректное значение из DragOver, чтобы родительская мишень могла переопределить его (задание Handled в true предотвращает передачу события вверх к родителю). В приложении-примере в каждом месте для изображений делаются проверки на изображения в DataPackage, и событие направляется родительской сетке, только если изображения нет; это позволяет сетке принимать Text, даже если физически он помещается на место для изображения (рис. 4).

Рис. 4. Обработка DragEnter и проверка DataPackage

private async void DropGrid_DragEnter(object sender,
  DragEventArgs e)
{
  if (!App.IsSource(e.DataView))
  {
    bool forceMove = ((e.Modifiers &
      DragDropModifiers.Shift) == DragDropModifiers.Shift);
    if (e.DataView.Contains(StandardDataFormats.Bitmap))
    {
      _acceptData = true;
      e.AcceptedOperation = (forceMove ?
        DataPackageOperation.Move : DataPackageOperation.Copy);
      e.DragUIOverride.Caption =
        "Drop the image to show it in this area";
      e.Handled = true;
    }
    else if (e.DataView.Contains(
      StandardDataFormats.StorageItems))
    {
      // Уведомляем XAML, что окончание DropGrid_Enter не
      // означает, что мы завершили обработку события
      var def = e.GetDeferral();
      _acceptData = false;
      e.AcceptedOperation = DataPackageOperation.None;
      var items = await e.DataView.GetStorageItemsAsync();
      foreach (var item in items)
      {
        try
        {
          StorageFile file = item as StorageFile;
          if ((file != null) &&
            file.ContentType.StartsWith("image/"))
          {
            _acceptData = true;
            e.AcceptedOperation = (forceMove ?
              DataPackageOperation.Move :
              DataPackageOperation.Copy);
            e.DragUIOverride.Caption =
              "Drop the image to show it in this area";
            break;
          }
        }
        catch (Exception ex)
        {
          Debug.WriteLine(ex.Message);
        }
      }
      e.Handled = true;
      // Уведомляем XAML, что теперь мы закончили
      def.Complete();
    }
  }
  // Иначе мы позволяем событию подниматься
  // к возможной родительской мишени
}

Более сложные концепции

Настройка визуальной обратной связи В OLE 2 операция «drag-and-drop» обеспечивала обратную связь лишь изменение курсора мыши согласно ответу мишени на событие DragOver. Современный функционал «drag-and-drop» допускает более сложные сценарии, например более богатую визуальную обратную связь с пользователем. UI перетаскивания состоит из трех частей: визуального контента, глифа и надписи (caption).

Визуальный контент представляет перетаскиваемые данные. Это могут быть перетаскиваемый UIElement (если источником является XAML-приложение), стандартный значок, выбранный системой на основе содержимого DataPackage или пользовательское изображение, заданное приложением.

Глиф отражает тип операции, принятой мишенью. Он может принимать одну из четырех форм в соответствии со значением типа DataPackageOperation. Глиф нельзя настраивать из приложения, но можно скрывать.

Надпись (caption) — это описание, предоставляемое мишенью. В зависимости от приложения-мишени операция Copy может быть, например, добавлением песни в список воспроизведения, загрузкой файла в OneDrive или обычным копированием файла. Надпись дает более точную обратную связь, чем глиф, и играет роль, очень похожую на всплывающую подсказку.

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

Табл. 1. Настройки, доступные источнику и мишени

Источник

Мишень

Визуальный контент

По умолчанию = перетаскиваемый элемент
Может использовать генерируемый системой контент на основе DataPackage
Может использовать любую битовую картуCan use any bitmap

По умолчанию = то, что задано источником
Нельзя использовать контент, генерируемый системой
Может использовать любую битовую карту
Можно показывать или скрыватьCan show or hide

Глиф

Нет доступа

Аспект, основанный на AcceptedOperation
Можно показывать или скрывать

Надпись

Нет доступа

Может использовать любую строку
Можно показывать или скрывать

Когда начинается операция «drag-and-drop» и приложение-источник не пытается настроить UI перетаскивания в обработчике событий DragStarting, XAML делает снимок перетаскиваемого UIElement, который используется в качестве контента UI перетаскивания. Начальный UIElement по-прежнему показывается в своей исходной позиции, чем отличается от поведения ListView, где перетаскиваемые ListViewItem скрываются в начальной позиции. Поскольку снимок перетаскиваемого UIElement создается после генерации события DragStarting, в ходе обработки этого события можно инициировать изменение визуального состояния для изменения снимка. (Заметьте, что состояние UIElement тоже изменяется, и, даже если оно восстанавливается, возможно небольшое мигание.)

При обработке события DragStarting источник может настраивать визуальную обратную связь через свойство DragUI класса DragStartingEventArgs. Например, запрос к системе использовать контент DataPackage для генерации визуального контента осуществляется через SetContentFromDataPackage (рис. 5).

Рис. 5. Использование SetContentFromDataPackage для генерации визуального контента

private void DropGrid_DragStarting(UIElement sender,
  DragStartingEventArgs args)
{
  ...
  if (_fileSource != null)
  {
    args.Data.RequestedOperation = DataPackageOperation.Copy |
      DataPackageOperation.Move;
    args.Data.SetStorageItems(
      new IStorageItem[] { _fileSource });
    args.DragUI.SetContentFromDataPackage();
  }
  else if (_bitmapSource != null)
  {
    args.Data.RequestedOperation = DataPackageOperation.Copy |
      DataPackageOperation.Move;
    args.Data.SetBitmap(_bitmapSource);
    args.DragUI.SetContentFromDataPackage();
  }
}

Вы можете задать собственную битовую карту в качестве контента для UI перетаскивания, используя два разных класса: общеизвестный XAML-класс BitmapImage или новый класс в Windows 10, SoftwareBitmap. Если эта битовая карта является ресурсом приложения, проще использовать BitmapImage и инициализировать его значением URI ресурса:

private void SampleBorder_DragStarting(
  UIElement sender, DragStartingEventArgs args)
{
  args.Data.SetText(SourceTextBox.SelectedText);
  args.DragUI.SetContentFromBitmapImage(
    new BitmapImage(new Uri("ms-appx:///Assets/cube.png",
    UriKind.RelativeOrAbsolute)));
}

Если битовую карту нужно генерировать «на лету», когда начинается операция перетаскивания или когда указатель попадает в границы мишени, то SoftwareBitmap можно создать из буфера, генерируемого XAML-классом RenderTargetBitmap, и вы получаете битовую карту с визуальным представлением UIElement, как показано на рис. 6. Этот UIElement должен находиться в визуальном дереве XAML, но не обязан быть видимой частью страницы. Поскольку RenderTargetBitmap выполняет рендеринг асинхронно, здесь нужно указать объект отсрочки (deferral), чтобы XAML знал, что битовая карта по завершении обработчика события может быть не готова, и ожидал заданное время для окончания обновления контента UI перетаскивания. (Механизм отсрочки мы подробно поясним в следующем разделе этой статьи.)

Рис. 6. Настройка контента UI перетаскивания с помощью RenderTargetBitmap и SoftwareBitmap

private async void PhotoStripGrid_DragStarting(
  UIElement sender, DragStartingEventArgs args)
{
  if ((Picture1.Picture == null)
    || (Picture2.Picture == null)
    || (Picture3.Picture == null)
    || (Picture4.Picture == null))
  {
    // Photo Montage не готов
    args.Cancel = true;
  }
  else

  {
    args.Data.RequestedOperation = DataPackageOperation.Copy;
    args.Data.SetDataProvider(StandardDataFormats.Bitmap,
      ProvideContentAsBitmap);
    App.SetSource(args.Data);
    var deferral = args.GetDeferral();
    var rtb = new RenderTargetBitmap();
    const int width = 200;
    int height = (int)(.5 + PhotoStripGrid.ActualHeight /
      PhotoStripGrid.ActualWidth * (double)width);
    await rtb.RenderAsync(PhotoStripGrid, width, height);
    var buffer = await rtb.GetPixelsAsync();
    var bitmap = SoftwareBitmap.CreateCopyFromBuffer(buffer,
      BitmapPixelFormat.Bgra8, width, height,
      BitmapAlphaMode.Premultiplied);
    args.DragUI.SetContentFromSoftwareBitmap(bitmap);
    deferral.Complete();
  }
}

Конечно, если SoftwareBitmap уже сгенерирован (и он может кешироваться для последующих операций «drag-and-drop»), никакой отсрочки не нужно.

Как для SetContentFromBitmapImage, так и для SetContentFromSoftwareBitmap можно задать точку привязки (anchor point), которая указывает, как позиционировать UI перетаскивания относительно позиции указателя. Если вы используете перегрузку без параметра точки привязки, за указателем будет следовать левый верхний угол вашей битовой карты. Метод GetPosition класса DragStartingEventArgs возвращает позицию указателя относительно какому-либо UIElement, который можно использовать, чтобы задать начальную позицию UI перетаскивания именно там, где расположен перетаскиваемый UIElement.

На стороне мишени различные части перетаскиваемого визуального контента можно настраивать в обработчике либо события DragEnter, либо события DragOver. Настройка осуществляется через свойство DragUIOverride класса DragEventArgs, который предоставляет четыре метода SetContentFrom, идентичные таковым в DragUI на стороне источника, а также четыре свойства, которые позволяют скрывать разные части DragUI и изменять надпись. Наокнец, DragUIOverride также предоставляет метод Clear, сбрасывающий все переопределения DragUI, осуществленные мишенью.

Асинхронные операции Windows Universal Applications API вводит в действие асинхронный шаблон для всех операций, занимающих более нескольких миллисекунд. Это особенно важно в случае перетаскивания, поскольку такие операции полностью управляются пользователем. Ввиду богатства функционала поддержка «drag-and-drop» использует три асинхронных шаблона: асинхронные вызовы, объекты отсрочки (deferrals) и обратные вызовы.

Асинхронные вызовы применяются, когда приложение вызывает системный API, выполнение которого может потребовать некоторого времени. Этот шаблон хорошо известен разработчикам для Windows и реализуется ключевыми словами async и await в C# (или create_task и then в C++). Все методы, извлекающие данные из DataPackage, следуют этому шаблону, например GetBitmapAsync, который используется нашим приложением для получения ссылки на поток изображения (рис. 7).

Рис. 7. Применение асинхронных вызовов для чтения DataPackage

private async void DropGrid_Drop(
  object sender, DragEventArgs e)
{
  if (!App.IsSource(e.DataView))
  {
    bool forceMove = ((e.Modifiers &
      DragDropModifiers.Shift) == DragDropModifiers.Shift);

    if (e.DataView.Contains(StandardDataFormats.Bitmap))
    {
      // Нам нужно получить объект отсрочки,
      // так как чтение данных выполняется асинхронно
      var def = e.GetDeferral();
      // Получаем данные
      _bitmapSource = await e.DataView.GetBitmapAsync();
      var imageStream = await _bitmapSource.OpenReadAsync();
      var bitmapImage = new BitmapImage();
      await bitmapImage.SetSourceAsync(imageStream);
      // Отображаем их
      Picture = bitmapImage;
      // Уведомляем источник
      e.AcceptedOperation = forceMove ?
        DataPackageOperation.Move : DataPackageOperation.Copy;
      e.Handled = true;
      def.Complete();
    
    }
...
}

Объекты отсрочки (deferrals) используются, когда инфраструктура XAML выполняет обратный вызов кода приложения, который может сам выдать асинхронный вызов до возврата значения, ожидаемого инфраструктурой. Этот шаблон не был распространен в предыдущих версиях XAML, поэтому давайте остановимся и проанализируем его. Когда кнопка генерирует событие Click, это односторонний вызов в том смысле, что приложению не нужно возвращать никакого значения. Если приложением сделан асинхронный вызов, его результат будет доступен по завершении обработки события Click, но это совершенно нормально, потому что этот обработчик не возвращает значение.

С другой стороны, когда XAML генерирует событие DragEnter или DragOver, он ожидает, что приложение устанавливает свойство AcceptedOperation аргументов события, указывая, можно ли обработать содержимое DataPackage. Если приложение заинтересовано только в доступных типах данных внутри DataPackage, это можно по-прежнему делать синхронно, например:

private void DropTextBox_DragOver(object sender, DragEventArgs e)
{
  bool hasText = e.DataView.Contains(StandardDataFormats.Text);
  e.AcceptedOperation = hasText ? DataPackageOperation.Copy :
    DataPackageOperation.None;
}

Однако, если, например, приложение может принимать лишь некоторые типы файлов, оно должно не только проверять типы данных в DataPackage, но и обращаться к данным, что можно делать только асинхронно. Это означает, что выполнение кода приостанавливается, пока данные не будут считаны, и что обработчик события DragEnter (или DragOver) будет выполнен до того, как приложение узнает, может ли оно принять эти данные. Этот сценарий и является целью объекта отсрочки: получая объект отсрочки от объекта DragEventArg, приложение сообщает XAML, что оно отложит некоторую часть своей обработки, а завершая объект отсрочки, приложение уведомляет XAML, что эта обработка закончена и выходные свойства экземпляра DragEventArgs установлены. Вернитесь к рис. 4, чтобы увидеть, как наше приложение-пример проверяет на наличие StorageItem после получения объекта отсрочки.

Объект отсрочки также можно использовать, когда настройка контента UI перетаскивания на стороне мишени требует асинхронных операций, таких как выполнение метода RenderAsync класса RenderTargetBitmap.

На стороне источника операции «drag-and-drop» DragStartingEventArgs тоже предоставляет объект отсрочки, цель которого — обеспечить запуск операции сразу по завершении обработчика события (даже если объект отсрочки не был завершен), чтобы пользователь как можно быстрее получал обратную связь независимо от того, что создание битовой карты для настройки UI перетаскивания занимает некоторое время.

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

Заметьте, что во многих случаях предоставление реальных данных потребует асинхронного вызова, а значит, параметр DataProviderRequest этого обратного вызова передает объект отсрочки, чтобы приложения могли уведомлять о том, что им нужно больше времени на обеспечение данных и что эти данные доступны (рис. 8).

Рис. 8. Отложенное предоставление данных

private void DeferredData_DragStarting(UIElement sender,
  DragStartingEventArgs args)
{
  args.Data.SetDataProvider(StandardDataFormats.Text,
    ProvideDeferredText);
}

async void ProvideDeferredText(DataProviderRequest request)
{
  var deferral = request.GetDeferral();
  var file = await KnownFolders.DocumentsLibrary.
    GetFileAsync(fileName);
  var content = await FileIO.ReadTextAsync(file);
  request.SetData(content);
  deferral.Complete();
}

Заключение

При написании приложения, которое манипулирует стандартными форматами данных, например файлами, изображениями или текстом, вы должны подумать о поддержке перетаскивания, так как для пользователей это естественная и хорошо знакомая операция. Основы перетаскивания уже известны тем, кто программировал с применением Windows Forms и Windows Presentation Foundation, что ускоряет освоение этого богатого функционала со своими специфическими концепциями вроде настройки UI перетаскивания и относительно редко применяемых шаблонов, таких как шаблон отсрочки. Если вам нужно поддерживать лишь базовые сценарии перетаскивания, вы можете положиться на свой прежний опыт и создавать простые реализации. Или при желании задействовать все возможности новых средств, чтобы обеспечить адаптированную под конкретных пользователей среду.


Анна Пай (Anna Pai) — инженер ПО в Xbox Team. Ранее работала над Silverlight, Silverlight для Windows Phone, а затем XAML для Windows и Windows Phone.

Ален Цанкетта (Alain Zanchetta) — инженер ПО в Windows XAML Team. Ранее был архитектором в консалтинговом подразделении Microsoft France.

{Для верстки: в строке благодарности не потеряйте две буквы с апострофами}

Выражаем благодарность за рецензирование статьи эксперту Microsoft Клементу Фуче (Clément Fauchère).