2015 年 8 月

Volume 30 Number 8

Windows 10 - Windows ユニバーサル アプリ向けのモダン ドラッグ アンド ドロップ

Alain Zanchetta

本稿は、Windows 10 および Visual Studio 2015 のパブリック プレビュー版に基づいています。

ドラッグ アンド ドロップとは、Windows デスクトップ上のアプリケーション内またはアプリケーション間でデータを転送する直感的な方法です。この機能は Windows 3.1 のファイル マネージャーで初めて採用され、その後 Microsoft Office など、OLE 2 をサポートするアプリケーションすべてに拡張されました。Windows 8 のリリースで、マイクロソフトは Windows ストア アプリという新しい種類の Windows アプリケーションを導入しました。Windows ストア アプリはタブレット向けに設計され、常に全画面表示モードで実行されます。アプリは常時 1 つしか表示されないため、アプリ間のドラッグ アンド ドロップは無意味になり、他の方法によるデータ共有 (共有チャームなど) が考案されました。しかし、Windows 10 ではデスクトップ PC のアプリケーションはまたもやウィンドウ モードで実行されるようになります。つまり、画面に複数のウィンドウが表示されます。そのためドラッグ アンド ドロップがユニバーサル Windows アプリの API として復活し、さらに Windows 10 のユーザー エクスペリエンス (UX) を強化する新機能も盛り込まれています。

ドラッグ アンド ドロップの考え方

ドラッグ アンド ドロップは、ユーザーが標準のジェスチャー (指で押しながら動かす、またはマウスやスタイラスで押して動かすこと) によって、アプリケーション間またはアプリケーション内でデータを転送できるようにします。

ドラッグ ソースとは、ドラッグ ジェスチャーがトリガーされるアプリケーションまたは領域のことです。このドラッグ ソースでは、データ パッケージ オブジェクトに転送対象のデータを設定することで転送の準備を行います。データ パッケージ オブジェクトには、テキスト、RTF、HTML、ビットマップ、ストレージ アイテム、カスタム データ フォーマットなど、標準フォーマットのデータを設定することができます。ドラッグ ソースではコピー、移動、リンクなど、サポートする操作の種類も指定します。ポインターが離されると、ドロップが発生します。ドロップ ターゲットとは、そのポインターの下にあるアプリケーションや領域のことです。ドロップ ターゲットでは、データ パッケージ オブジェクトを処理し、実行した操作の種類を返します。

ドラッグ アンド ドロップ中は、ドラッグ UI によって実行中のドラッグ アンド ドロップ操作の種類が視覚的に示されます。この視覚的フィードバックは最初はドラッグ ソースが用意しますが、ドロップ ターゲット上にポインターが移動すると、ドロップターゲットがこの視覚的フィードバックを変更できます。

モダン ドラッグ アンド ドロップは、デスクトップ、タブレット、およびスマートフォンで利用できます。モダン ドラッグ アンド ドロップは、従来の Windows アプリを含むあらゆる種類のアプリ間、またはアプリ内でのデータ転送を可能にします。ただし、今回注目するのはモダン ドラッグ アンド ドロップ向けの XAML API です。

ドラッグ アンド ドロップの実装

ドラッグ ソースとドロップ ターゲットの役割は異なります。アプリは、ドラッグ ソースにしかならない UI コンポーネント、ドロップ ターゲットにしかならない UI コンポーネント、ソースにもターゲットにもなる UI コンポーネントを含むことができます。その例としてサンプルのフォト ブース アプリを図 1に示します。

ドラッグ ソースとドラッグ ターゲット
図 1 ドラッグ ソースとドラッグ ターゲット

ドラッグ アンド ドロップ操作はすべて、ユーザーの入力が主導します。したがって、ドラッグ アンド ドロップの実装はほぼ間違いなくイベントベースになります (図 2 参照)。

ドラッグ アンド ドロップのイベント
図 2 ドラッグ アンド ドロップのイベント

ドラッグ ソースの実装: Windows 8.1 では ListView をアプリ内ドラッグ アンド ドロップ操作のドラッグ ソースにすることができます。そのためには、ListView の CanDragItems プロパティを true に設定します。

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

アプリは、ドラッグ ソースでの DragItemsStarting イベントを処理します。Windows 10 では、DragItemsStarting イベントも引き続きサポートされますが、DragItemsCompleted イベントが追加されます。Windows 8.1 アプリではターゲットとソースが同じプロセスに所属しなければならないため、DragItemsCompleted イベントは必要ありませんでした。

モダン ドラッグ アンド ドロップの主なドラッグ ソースは UIElement です。UIElement ではモダン ドラッグ アンド ドロップのすべての機能にアクセスできます。そこで、ここではこの UIElement に注目します。

UIElement をドラッグ可能にする場合、UIElement の CanDrag プロパティを true に設定するのが 1 つの方法です。この設定は、マークアップまたは分離コードで行います。XAML フレームワークがジェスチャーを認識し、DragStarting イベントを発生させ、ドラッグ操作が開始されたことを示します。アプリでは DataPackage のコンテンツを設定し、サポート対象の操作を示すことで DataPackage を構成する必要があります。ドロップ ソースでは DataPackage にさまざまなフォーマットのデータを設定できます。その結果、多くのドロップ ターゲットとの互換性を維持することができます (図 3 参照)。サポート対象の操作は DataPackageOperation 型で定義し、コピー、移動、リンクまたはこれらの任意の組み合わせを指定できます。

図 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 });
  ...
}

イベント ハンドラーでのドラッグ操作は DragStartingEventArgs パラメーターの Cancel プロパティを設定することでキャンセルできます。たとえば、今回のサンプル アプリでは、画像のプレースホルダーでドラッグ操作が開始されるのは、既に画像 (ファイル) を受け取っている場合のみです。

DragStartingEvent ハンドラーではドラッグ UI のカスタマイズもできますが、これについては後ほど説明します。

場合によっては、特別なジェスチャーを使用してドラッグ アンド ドロップ操作を開始したり、あるコントロールでの通常の操作がドラッグ アンド ドロップのジェスチャーに影響する場合にそのコントロールでドラッグを可能にすることもあります。たとえば、テキストボックスでは、ポインターのダウン イベントに対して選択項目の変更という反応が既に割り当てられています。このような場合は、アプリに独自のジェスチャー検知を実装して StartDragAsync メソッドを呼び出すことでドラッグ アンド ドロップ操作を開始することができます。StartDragAsync メソッドはポインター ID を必要とするため、Kinect センサーのような非標準のデバイスではドラッグ アンド ドロップ操作を開始できない点に注意してください。StartDragAsync を呼び出したら、残りのドラッグ アンド ドロップ操作は、DragStarting イベントへの対応など、CanDrag=True を使用するのと同じパターンに従います。

ユーザーがポインターを離したら、ドラッグ アンド ドロップ操作が完了し、DropCompleted イベントによって完了がドラッグ ソースに通知されます。DropCompleted イベントには、ユーザーがポインターを離した位置の下にあるドロップ ターゲットから返された DataPackageOperation が含まれます。ただし、ドロップ ターゲットがデータを受け取らなかった場合やキャンセルが押された場合は、DataPackageOperation.None が含まれます。

private void DropGrid_DropCompleted(UIElement sender, DropCompletedEventArgs args)
{
  if (args.DropResult == DataPackageOperation.Move)
  {
    // Move means that we should clear our own image
    Picture = null;
    _bitmapSource = null;
    _fileSource = null;
  }
}

StartDragAsync は IAsyncOperation<DataPackageOperation> を返します。ドロップ ソースは、IAsyncOperation を待機するか、DropCompleted イベントを処理することで、ドラッグ アンド ドロップ操作の終了を処理します。DragStarting イベントが発生した後に IAsyncOperation インターフェイスを使用して、プログラムでドラッグ アンド ドロップ操作をキャンセルすることができます。ただし、ユーザーが困惑する可能性があります。

ListView のドラッグ アンド ドロップと UIElement のドラッグ アンド ドロップの両方を同じシステム サービスに実装することができ、完全な互換性があります。ただし、ドロップ ソースで発生するイベントは異なります。つまり、ListView の CanDragItems プロパティを true に設定している場合は、DragItemsStarting イベントと DragItemsCompleted イベントのみが発生します。DragStarting イベントと DropCompleted イベントは UIElement の CanDrag プロパティに関連するイベントです。

ドロップ ターゲットの実装: すべての UIElement は、AllowDrop プロパティを true に設定することでドロップ ターゲットになります。ドラッグ アンド ドロップ操作中、ドロップ ターゲット上で発生する可能性のあるイベントは、DragEnter、DragOver、DragLeave、および Drop です。これらのイベントは Windows 8.1 にもありましたが、Windows 10 では DragEventArgs クラスが拡張され、アプリからモダン ドラッグ アンド ドロップのすべての機能にアクセスできるようになります。ドラッグ アンド ドロップ イベントを処理する場合、ドロップ ターゲットはまずイベント引数の DataView プロパティを利用して DataPackage のコンテンツを調べます。多くの場合はデータ型の存在を確認すれば十分で、このチェックは同期処理として実行できます。ファイルを使用する場合など、アプリはファイルの種類が利用可能なものかどうかをチェックしてから、DataPackage を受け取るか、無視するかを判断しなければならないこともあります。このチェックは非同期操作になるため、ドロップ ターゲットでは遅延を用いて完了を遅らせる必要があります (このパターンについては後ほど詳しく解説します)。

ドロップ ターゲットでは、データの処理が可能かどうかを判断してから、DragEventArgs インスタンスの AcceptedOperation プロパティを設定し、システムが適切なフィードバックをユーザーに提供できるようにします。

アプリのイベント ハンドラから DataTransferOperation.None が返された (ドロップ ソースが操作を受け入れなかった) 場合、たとえユーザーがポインターをターゲット上で離してもドロップは発生しません。この場合は、DragLeave イベントが発生することになります。

アプリでは DragEnter または DragOver のいずれかを処理します。DragOver を処理しない場合は、DragEnter から返された AcceptedOperation が保持されます。DragEnter が呼び出されるのは 1 回だけなので、パフォーマンスの観点から DragOver を処理することをお勧めします。ただし、ドロップ ターゲットが入れ子になっている場合は、DragOver からの値が親ターゲットによってオーバーライドされる可能性があるため、DragOver では正確な値を返す必要があります (Handled プロパティを true に設定すると、イベントは親ターゲットまでバブルアップしません)。サンプル アプリでは、各フォト プレースホルダーが DataPackage 内の画像をチェックし、利用可能な画像がない場合のみイベントを親グリッドにルーティングします。これにより、テキストが物理的にプレースホルダー上にドロップされたとしても、親グリッドがそのテキストを受け取ることができます (図 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))
    {
      // Notify XAML that the end of DropGrid_Enter does
      // not mean that we have finished to handle the event
      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;
      // Notify XAML that now we are done
      def.Complete();
    }
  }
  // Else we let the event bubble on a possible parent target
}

高度な概念

視覚的フィードバックのカスタマイズ: OLE 2 のドラッグ アンド ドロップのフィードバックは、DragOver イベントに対するドロップ ターゲットの応答に応じてマウスカーソルを変化させるだけでした。モダン ドラッグ アンド ドロップでは、ユーザーへの視覚的フィードバックが多彩になり、さらに高度なシナリオを実現できるようになります。ドラッグ UI は、ビジュアル コンテンツ、グリフ、キャプションの 3 つのパーツから構成されます。

ビジュアル コンテンツはドラッグ中のデータを表現します。これは、(ドラッグ ソースが XAML アプリの場合) ドラッグ中の UIElement を表すもので、DataPackage のコンテンツに応じて、システムが選択した標準アイコンまたはアプリが設定したカスタム画像になります。

グリフは、ドロップ ターゲットが受け入れた操作の種類を反映します。グリフは、DataPackageOperation 型の値に応じて異なる 4 つの形状になります。アプリからこのグリフをカスタマイズすることはできませんが、非表示にすることはできます。

キャプションは、ターゲットから提供される説明です。ドロップ ターゲットとなるアプリに応じて、たとえばコピー操作は、プレイリストへの曲の追加、OneDrive へのファイルのアップロード、単純なファイル コピーなどになります。キャプションはグリフよりも正確なフィードバックを可能にし、ツールヒントとよく似た役割を果たします。

図 5 の表は、ソースとターゲットからこの 3 つのパーツをどのようにカスタマイズできるかを示します。ポインターがドロップ ターゲット上にないときは、ドラッグ UI はドロップ ソースが構成した視覚的フィードバックをそのまま表示します。ポインターがドロップ ターゲット上にくると、視覚的フィードバック パーツの一部がターゲットによってオーバーライドされ、ポインターがターゲットから離れると、このオーバーライドがすべてクリアされます。

図 5 ソースとターゲットから利用できるカスタマイズ

  ソース ターゲット
ビジュアル コンテンツ

既定値はドラッグ中の要素

DataPackage に応じてシステム生成のコンテンツを利用可能

任意のビットマップを利用可能

既定値はソースが設定した内容

システム生成のコンテンツは利用不可

任意のビットマップを利用可能

表示/非表示を切り替え可能

グリフ アクセス不可

AcceptedOperation に応じた外観

表示/非表示を切り替え可能

キャプション アクセス不可

任意の文字列を使用可能

表示/非表示を切り替え可能

ドラッグ アンド ドロップ操作開始時にドロップ ソースの DragStarting イベント ハンドラーでドラッグ UI をカスタマイズしなかった場合は、ドラッグ中の UIElement のスナップショットが XAML によって取得され、ドラッグ UI のコンテンツとして使用されます。この場合、UIElement は最初本来の位置にそのまま表示されますが、ListView ではこの動作が異なり、ドラッグ中の ListViewItems は最初から非表示になります。ドラッグ中の UIElement のスナップショットが取得されるのは DragStarting イベント発生後なので、イベント中に表示状態に変化が起こり、スナップショットが変わる可能性があります (UIElement の状態もまた変化し、たとえ復元したとしても軽いちらつきが発生する可能性があることに注意してください)。

DragStarting イベントを処理するときに、ドラッグ ソースは DragStartingEventArgs クラスの DragUI プロパティを使用して視覚的フィードバックをカスタマイズします。たとえば、DataPackage のコンテンツを使用してビジュアル コンテンツを生成するようシステムに依頼する場合は、SetContentFromDataPackage を使用します (図 6 参照)。

図 6 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 で BitmapImage を初期化する方が簡単です。

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)));
}

ドラッグ操作を開始したタイミングや、ポインターがドロップ ターゲットの範囲内に入ったタイミングで、実行時にビットマップを生成する必要がある場合は、XAML の RenderTargetBitmap クラスによって作成されたバッファーから SoftwareBitmap を生成できます。XAML の RenderTargetBitmap クラスは UIElement のビジュアル表現を含むビットマップを生成します (図 7 参照)。この UIElement は XAML のビジュアル ツリー内には存在しなければなりませんが、ページ上に表示されていなくてもかまいません。RenderTargetBitmap クラスはビットマップのレンダリングを非同期に実行するため、ここでは遅延が必要です。この遅延により、イベント ハンドラーの完了時に XAML はビットマップの準備ができたかどうかがわかり、遅延の終了を待ってドラッグ UI のコンテンツを更新できます (遅延のメカニズムについてはこの後詳しく解説します)。

図 7 RenderTargetBitmap と SoftwareBitmap を使用したドラッグ UI コンテンツのカスタマイズ

private async void PhotoStripGrid_DragStarting(UIElement sender, DragStartingEventArgs args)
{
  if ((Picture1.Picture == null) || (Picture2.Picture == null)
    || (Picture3.Picture == null) || (Picture4.Picture == null))
  {
    // Photo Montage is not ready
    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 が生成されており、その後のドラッグ アンド ドロップ操作のために SoftwareBitmap をキャッシュできる場合は、遅延の必要はありません。

SetContentFromBitmapImage と SetContentFromSoftwareBitmap はどちらも、ポインターの位置に相対でドラッグ UI の位置を示すアンカー ポイントを指定できます。アンカー ポイント パラメーターを指定しないでこのオーバーロードを使用すると、カスタム ビットマップの左上隅がポインターに追従します。DragStartingEventArgs クラスの GetPosition メソッドは、UIEement に対するポインターの相対位置を返します。これを利用して、ドラッグ中の UIElement が配置されるドラッグ UI の開始位置を正確に設定できます。

ドラッグ時のさまざまなパーツをターゲット側でカスタマイズする場合は、DragEnter イベント ハンドラーまたは DragOver イベント ハンドラーのいずれかで行います。カスタマイズは DragEventArgs クラスの DragUIOverride プロパティを使って行います。このクラスは、ソース側の DragUI と同じ 4 つの SetContentFrom メソッドと 4 つのプロパティを公開します。このプロパティを使って DragUI のさまざまなパーツを非表示にしたり、キャプションを変更することができます。DragUIOverride は、ターゲットが行った DragUI のオーバーライドをすべてリセットする Clear メソッドも公開します。

非同期操作: Windows ユニバーサル アプリ API では、数ミリ秒以上を要する可能性がある操作すべてについて非同期パターンを使用しなければなりません。ドラッグ アンド ドロップのように完全にユーザーが主導する操作では、非同期処理が特に重要です。さまざまな場面が想定されるため、ドラッグ アンド ドロップでは、非同期呼び出し、遅延、コールバックという異なる 3 つの非同期パターンが使用されます。

非同期呼び出しを使用するのは、アプリが完了までにある程度時間のかかる可能性があるシステム API を呼び出す場合です。この非同期パターンは Windows 開発者にはよく知られているように、C# の async キーワードと await のキーワード (または C++ の create_task および then) を使用することで簡素化できます。DataPackage からデータを取得するメソッドはすべてこの非同期パターンに従います。その一例が GetBitmapAsync で、今回のサンプル アプリでは GetBitmapAsync を使用して、画像ストリーム参照を取得しています (図 8 参照)。

図 8 非同期呼び出しを使用した 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))
    {
      // We need to take a deferral as reading the data is asynchronous
      var def = e.GetDeferral();
      // Get the data
      _bitmapSource = await e.DataView.GetBitmapAsync();
      var imageStream = await _bitmapSource.OpenReadAsync();
      var bitmapImage = new BitmapImage();
      await bitmapImage.SetSourceAsync(imageStream);
      // Display it
      Picture = bitmapImage;
      // Notify the source
      e.AcceptedOperation = forceMove ? DataPackageOperation.Move :
        DataPackageOperation.Copy;
      e.Handled = true;
      def.Complete();
    }
...
}

遅延を使用するのは、XAML フレームワークからアプリのコードにコールバックされる時点です。このアプリのコードは、自身で非同期呼び出しを行ってから、フレームワークが待機する値を返しています。この非同期パターンは、以前のバージョンの XAML ではあまり使われていなかったため、少し時間を取って分析してみます。ボタンがクリックイベントを発生しても、アプリから値を返す必要がないためこれは一方向の呼び出しです。アプリが非同期呼び出しを行う場合、その結果が利用可能になるのはクリック イベント ハンドラーの完了後です。ただし、このハンドラーは値を返さないためまったく問題はありません。

一方、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 に一部の処理を延期するよう指示します。遅延終了時には、処理が完了し、DragEventArgs インスタンスの出力プロパティが設定されたことをアプリから XAML に通知します。図 4 に戻り、遅延が終わった後のサンプル アプリによる StorageItems の確認方法を参照してください。

ターゲット側でのドラッグ UI コンテンツのカスタマイズで、RenderTargetBitmap クラスの RenderAsync メソッドのような非同期操作が必要な場合にも遅延を使用できます。

ソース側のドラッグ アンド ドロップ操作では、DragStartingEventArgs クラスでも遅延が行われます。その目的は、イベント ハンドラーが終了次第操作を開始できるようにし、ドラッグ UI をカスタマイズするビットマップの作成にある程度時間がかかる場合でもユーザーに最短時間でフィードバックを提供することにあります。

DataPackage でコールバックを使用するのは、実際にデータが必要になるまで DataPackage の供給を遅らせるためです。このメカニズムによって、ドロップ ソースは DataPackage でさまざまなフォーマットをアドバタイズできますが、ターゲットが実際に読み取るデータのみを準備して転送しなければなりません。多くの場合、コールバックは呼び出されません。たとえば、対応するデータ フォーマットを理解できるターゲットがなければ呼び出されません。これは優れたパフォーマンスの最適化です。

多くの場合、実際のデータを提供するために非同期呼び出しが必要なります。したがって、アプリからデータを提供するには時間が必要であることと、その後にデータが利用可能になったことを通知するために、コールバックの DataProviderRequest パラメーターで遅延を行っています (図 9 参照)。

図 9 実際に読み取られるまでデータを遅延

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 フォームや Windows Presentation Foundation を使用してプログラミングしたことがある方には既におなじみです。この基本を理解していれば、ドラッグ UI のカスタマイズなどの固有の考え方や、遅延パターンのようにこれまで比較的使用することのなかったパターンといったモダン ドラッグ アンド ドロップの豊富な機能を簡単に習得できるようになります。基本的なドラッグ アンド ドロップのみをサポートすれば十分であれば、これまでの経験を頼りにして簡単に実装できます。必要とあれば新機能を存分に活用して、ユーザーに合わせたエクスペリエンスを提供することも可能です。


Anna Pai は Xbox チームのソフトウェア エンジニアです。以前は Silverlight、Silverlight for Windows Phone、および Windows と Windows Phone 用 XAML の開発に携わっていました。

Alain Zanchetta は Windows XAML チームのソフトウェア エンジニアです。以前は Microsoft France のコンサルティング事業部で事業計画担当を務めていました。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Clément Fauchère に心より感謝いたします。
Clement Fauchere はマイクロソフトの Windows Shell チームのソフトウェア エンジニアです。