August 2015

Band 30, Nummer 8

Windows 10 – Modernes Drag & Drop für universelle Windows-Anwendungen

Von Alain Zanchetta

Dieser Artikel basiert auf der öffentlichen Vorschauversion von Windows 10 und Visual Studio 2015.

Drag & Drop ist auf dem Windows-Desktop ein intuitives Verfahren zum Übertragen von Daten innerhalb einer Anwendung oder zwischen Anwendungen. In Windows 3.1 wurde das Verfahren im Datei-Manager erstmals unterstützt und dann auf alle Programme mit Unterstützung von OLE 2, wie z. B. Microsoft Office, erweitert. Mit Windows 8 führte Microsoft eine neue Art von Windows-Anwendung namens Windows Store-App ein, die auf Tablets ausgelegt war und stets im Vollbildmodus angezeigt wurde. Da immer nur eine Anwendung angezeigt wurde, war ein anwendungsübergreifendes Drag & Drop nicht sinnvoll, weshalb anderen Arten der Freigabe von Daten, z. B. der Charm "Teilen", entwickelt wurden. Unter Windows 10 werden Anwendungen auf einem Desktop-PC nun wieder im Fenstermodus ausgeführt. Das heißt, dass mehrere Fenster auf dem Bildschirm angezeigt werden. Dies ebnet den Weg zum Comeback von Drag & Drop als universelle Windows-App-API mit neuen Features, die die Benutzererfahrung verbessern.

Drag & Drop-Konzepte

Drag & Drop ermöglicht Benutzern das Übertragen von Daten zwischen Anwendungen oder innerhalb einer Anwendung mit einer Standardgeste (Drücken-Halten-Schwenken mit dem Finger oder Drücken-Schwenken mit einer Maus oder einem Stift).

Die Quelle des Ziehvorgangs (die Anwendung oder der Bereich, in dem die Ziehgeste ausgelöst wird) enthält die zu übertragenden Daten in Form eines aufgefüllten Datenpaketobjekts, das Standarddatenformate wie Text, RTF, HTML, Bitmaps, Speicherelemente oder benutzerdefinierte Datenformate enthalten kann. Die Quelle gibt auch die Art der unterstützten Vorgänge an: Kopieren, Verschieben oder Verknüpfen. Sobald der Zeiger freigegeben wird, erfolgt der Ablegevorgang. Das Ziel des Ablegevorgangs (die Anwendung oder den Bereich unterhalb des Zeigers) verarbeitet das Datenpaket und gibt den Typ des Vorgangs zurück, den es ausgeführt hat.

Während des Drag & Drop-Vorgangs bietet die Benutzeroberfläche für den Ziehvorgang einen visuellen Hinweis, welche Art von Vorgang stattfindet. Dieses visuelle Feedback kann anfänglich von der Quelle angegeben werden, jedoch von den Zielen geändert werden, während der Mauszeiger darüber bewegt wird.

Modernes Drag & Drop ist auf Desktop-PCs, Tablets und Smartphones verfügbar. Es lässt Datenübertragungen zwischen oder innerhalb jeder Art von Anwendung zu, einschließlich klassischer Windows-Apps, obwohl sich dieser Artikel auf die XAML-API für modernes Drag & Drop konzentriert.

Implementieren von Drag & Drop

Die Quelle des Ziehvorgangs und das Ziel des Ablegevorgangs spielen verschiedene Rollen. Eine Anwendung kann Benutzeroberflächenkomponenten haben, die nur Quellen des Ziehvorgangs, nur Ziele des Ablegevorgangs oder beides sind, wie z. B. die in Abbildung 1 gezeigte Beispielanwendung Photo Booth.

Quellen des Ziehvorgangs und Ziele des Ablegevorgangs
Abbildung 1: Quellen des Ziehvorgangs und Ziele des Ablegevorgangs

Ein Drag & Drop-Vorgang wird vollständig von Benutzereingaben gesteuert, sodass seine Implementierung fast ausschließlich ereignisbasiert ist (siehe Abbildung 2).

Drag & Drop-Ereignisse
Abbildung 2: Drag & Drop-Ereignisse

Implementieren einer Quelle des Ziehvorgangs In Windows 8.1 kann ein "ListView"-Element die Quelle eines Drag & Drop-Vorgangs innerhalb der App sein, wenn die "CanDragItems"-Eigenschaft auf "true" festgelegt ist:

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

Die Anwendung kann das "DragItemsStarting"-Ereignis für die Quelle verarbeiten. Dies wird unter Windows 10 durch das Hinzufügen eines "DragItemsCompleted"-Ereignisses weiter unterstützt, das in Windows 8.1-Anwendungen nicht erforderlich war, bei denen Ziel und Quelle zum selben Prozess gehören müssen.

Die Hauptquelle für modernes Drag & Drop ist das "UIElement", das den Zugriff auf alle modernen Drag & Drop-Features ermöglicht und auf dem in diesem Artikels der Schwerpunkt liegen soll.

Eine Möglichkeit, "UIElement" ziehbar zu gestalten, ist das Festlegen der "canDrag"-Eigenschaft auf "true". Dies kann im Markup oder Codebehind erfolgen. Das XAML-Framework übernimmt die Gestenerkennung und löst das "DragStarting"-Ereignis aus, um den Anfang eines Ziehvorgangs anzugeben. Die Anwendung muss das "DataPackage" konfigurieren, indem es mit Inhalt aufgefüllt wird und angegeben wird, welche Vorgänge unterstützt werden. Die Quellanwendung kann im "DataPackage" verschiedene Formaten ablegen, wodurch es mit mehr Ziele kompatibel wird (siehe Abbildung 3). Die unterstützten Vorgänge sind im "DataPackageOperation"-Typ definiert, bei denen es sich um "Copy", "Move" und "Link" (auch in beliebiger Kombination) handeln kann.

Abbildung 3: Verarbeiten von "DragStarting" und Auffüllen des "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 });
  ...
}

Sie können den Ziehvorgang im Ereignishandler abbrechen, indem Sie die "Cancel"-Eigenschaft des "DragStartingEventArgs"-Parameters festlegen. In unserer Beispielanwendung wird ein Ziehvorgang nur dann von einem Bildplatzhalter gestartet, wenn dieser ein Bild oder eine Datei empfangen hat.

Der "DragStartingEvent"-Handler ist auch der Ort, an dem die Quellanwendung die Benutzeroberfläche für den Ziehvorgang anpassen kann, was weiter unten in diesem Artikel erläutert wird.

In einigen Fällen will die Anwendung beispielsweise eine spezielle Geste zum Starten eines Drag & Drop-Vorgangs verwenden oder das Ziehen eines Steuerelements zulassen, dessen normale Interaktionen in Konflikt mit der Drag & Drop-Geste stehen, z. B. "TextBox", das bereits auf "Zeiger-unten"-Ereignisse durch Ändern seiner Auswahl reagiert. In diesen Fällen kann die Anwendung eine eigene Gestenerkennung implementieren und dann einen Drag & Drop-Vorgang durch Aufrufen der "StartDragAsync"-Methode auslösen. Beachten Sie, dass diese Methode einen Zeigebezeichner erwartet und daher keinen Drag & Drop-Vorgang mit nicht standardmäßigen Geräten, z. B. einem Kinect-Sensor, starten kann. Sobald "StartDragAsync" aufgerufen wird, folgt der Rest des Drag & Drop-Vorgangs dem gleichen Muster, als wäre "CanDrag=True" einschließlich des "DragStarting"-Ereignisses verwendet worden.

Sobald der Benutzer den Zeiger freigibt, wird der Drag & Drop-Vorgang abgeschlossen und die Quelle über das "DropCompleted"-Ereignis benachrichtigt. Dieses Ereignis enthält die vom Ziel zurückgegebene "DataPackageOperation", für die der Benutzer den Zeiger freigegeben hat, oder "DataPackageOperation.None", wenn der Zeiger für ein Ziel freigegeben wurde, das die Daten nicht akzeptiert, oder auf "Abbrechen" geklickt wurde:

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" gibt "IAsyncOperation<DataPackageOperation>" zurück. Die Quellanwendung kann das Ende des Vorgangs verarbeiten, indem entweder auf "IAsyncOperation" gewartet oder das "DropCompleted"-Ereignis verarbeitet wird. Das programmgesteuerte Abbrechen nach dem "DragStarting"-Ereignis wird über die "IAsyncOperation"-Schnittstelle möglich, was aber die Benutzer ggf. irritiert.

Beachten Sie, dass obgleich sowohl "ListView"-Drag &Drop als auch "UIElement"-Drag & Drop für dieselben Systemdienste implementiert und vollständig kompatibel sind, lösen sie auf Quellseite nicht die gleichen Ereignisse aus. Wenn also für "ListView" die "CanDragItems"-Eigenschaft auf "true" festgelegt ist, werden nur "DragItemsStarting" und "DragItemsCompleted" ausgelöst. "DragStarting" und "DropCompleted" sind Ereignisse im Zusammenhang mit der "CanDrag"-Eigenschaft von "UIElement".

Implementieren eines Ziels für den Ablegevorgang Jedes "UIElement" kann Ziel des Ablegevorgangs sein, sofern die "AllowDrop"-Eigenschaft auf "true" festgelegt ist. Bei einem Drag & Drop-Vorgang können die folgenden Ereignisse für ein Ziel ausgelöst werden: "DragEnter", "DragOver", "DragLeave" und "Drop". Diese Ereignisse sind in Windows 8.1 bereits vorhanden, aber in Windows 10 wurde die "DragEventArgs"-Klasse so erweitert, dass Anwendungen Zugriff auf alle Features von modernem Drag & Drop haben. Bei der Verarbeitung eines Drag & Drop-Ereignisses muss die Zielanwendung zuerst den Inhalt von "DataPackage" über die "DataView"-Eigenschaft des Ereignisarguments überprüfen. In den meisten Fällen ist die Überprüfung auf das Vorhandensein eines Typs ausreichend und kann synchron erfolgen. In einigen Fällen, wie z. B. bei Dateien, muss die Anwendung ggf den Typ der verfügbaren Dateien überprüfen, bevor das "DataPackage" akzeptiert oder ignoriert wird. Dies ist ein asynchroner Vorgang, der von der Anwendung eine Verzögerung und einen späteren Abschluss erfordert (dieses Muster wird weiter unten in diesem Artikel beschrieben).

Nachdem das Ziel ermittelt hat, ob die Daten verarbeitet werden können, muss es die "AcceptedOperation"-Eigenschaft einer "DragEventArgs"-Instanz so festlegen, dass das System dem Benutzer das richtige Feedback geben kann.

Beachten Sie, dass wenn die Anwendung von einem Ereignishandler "DataTransferOperation.None" oder einen von der Quelle nicht akzeptierten Vorgang zurückgibt, der Ablegevorgang nicht erfolgt, auch wenn der Benutzer den Zeiger über dem Ziel freigibt. Stattdessen wird das "DragLeave"-Ereignis ausgelöst.

Die Anwendung kann entweder "DragEnter" oder "DragOver" verarbeiten. Die von "DragEnter" zurückgegebene "AcceptedOperation" wird beibehalten, wenn "DragOver" nicht verarbeitet wird. Da "DragEnter" nur einmal aufgerufen wird, sollte es aus Leistungsgründen "DragOver" vorgezogen werden. Bei geschachtelten Zielen ist es jedoch erforderlich, den richtigen Wert von "DragOver" für den Fall zurückzugeben, dass ein übergeordnetes Ziel es ggf. außer Kraft setzt (das Festlegen von "Handled" auf "true" verhindert, dass das Ereignis zum übergeordneten Ziel nach oben gelangt). In der Beispielanwendung sucht jeder Fotoplatzhalter nach Bildern im "DataPackage" und leitet das Ereignis nur dann zum übergeordneten Raster weiter, wenn kein Bild verfügbar ist. Dadurch kann das Raster "Text" zulassen, auch wenn es physisch auf einem Platzhalter platziert wird (siehe Abbildung 4).

Abbildung 4: Verarbeiten von "DragEnter" und Überprüfen von "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
}

Erweiterte Konzepte

Anpassen des visuellen Feedbacks Das einzige Feedback, das Drag & Drop bei OLE 2 zur Verfügung stellte, war das Ändern des Mauszeigers entsprechend der Antwort des Ziels auf das "DragOver"-Ereignis. Modernes Drag & Drop lässt komplexere Szenarien zu, da das visuelle Feedback, das dem Benutzer bereitgestellt wird, umfassender ist. Die Benutzeroberfläche für den Ziehvorgang besteht aus drei Teilen: visueller Inhalt, Glyphe und Beschriftung.

Der visuelle Inhalt stellt die gezogenen Daten dar. Dabei kann es sich um Folgendes handeln: das gezogene "UIElement" (wenn die Quelle eine XAML-Anwendung ist), ein vom System basierend auf dem Inhalt des "DataPackage" ausgewähltes Standardsymbol oder ein benutzerdefiniertes von der Anwendung festgelegtes Bild.

Die Glyphe gibt den Typ des Vorgangs wieder, der vom Ziel akzeptiert wird. Sie kann je nach Wert des "DataPackageOperation"-Typs vier verschiedene Formen annehmen. Sie kann von Anwendungen nicht angepasst, aber ausgeblendet werden.

Die Beschriftung ist eine vom Ziel angegebene Beschreibung. Je nach Zielanwendung kann es sich bei einem Kopiervorgang um das Hinzufügen eines Musiktitels zu einer Wiedergabeliste, das Hochladen einer Datei auf OneDrive oder einen einfachen Dateikopiervorgang handeln. Die Beschriftung ermöglicht ein präziseres Feedback als die Glyphe und spielt eine einer QuickInfo sehr ähnliche Rolle.

Die Tabelle in Abbildung 5 zeigt, wie diese verschiedenen Teile von Quelle und Ziel angepasst werden können. Wenn sich der Mauszeiger nicht über einem Ziel für einen Ablegevorgang befindet, entspricht die Benutzeroberfläche für den Ziehvorgang genau dem, was in der Quelle konfiguriert wurde. Wenn sich der Mauszeiger über einem Ziel für einen Ablegevorgang befindet, können einige Teile der Darstellung vom Ziel überschrieben werden. Alle Überschreibungen werden gelöscht, wenn der Mauszeiger das Ziel verlässt.

Abbildung 5: Für Quelle und Ziel verfügbare Anpassungen

  Quelle Ziel
Visueller Inhalt

Standard = gezogenes Element

Kann basierend auf dem "DataPackage" vom System generierte Inhalte verwenden

Kann beliebiges Bitmap verwenden

Standard = Festlegungen durch die Quelle

Kann keine vom System generierten Inhalte verwenden

Kann beliebiges Bitmap verwenden

Kann ein- oder ausblenden

Glyphe Kein Zugriff

Aspekt basierend auf "AcceptedOperation"

Kann ein- oder ausblenden

Beschriftung Kein Zugriff

Kann eine beliebige Zeichenfolge verwenden

Kann ein- oder ausblenden

Wenn ein Drag & Drop-Vorgang beginnt und die Quellanwendung nicht versucht, die Benutzeroberfläche für den Ziehvorgang im "DragStarting"-Ereignishandler anzupassen, wird von XAML eine Momentaufnahme des gezogenen "UIElement" erstellt und als Inhalt der Benutzeroberfläche für den Ziehvorgang verwendet. Das anfängliche "UIElement" wird weiter an seiner ursprünglichen Position angezeigt. Dies ist ein anderes Verhalten als bei "ListView", bei dem die gezogenen "ListViewItems" an der ursprünglichen Position ausgeblendet sind. Da die Momentaufnahme des gezogenen "UIElement" erstellt wird, nachdem das "DragStarting"-Ereignis ausgelöst wurde, ist es möglich, während dieses Ereignisses eine Änderung eines visuellen Zustands auszulösen, um die Momentaufnahme zu verändern. (Beachten Sie, dass auch der "UIElement"-Status geändert wird und bei seiner Zurücksetzung ein helles Flimmern auftreten kann.)

Bei der Verarbeitung eines "DragStarting"-Ereignisses kann die Quelle des Ziehvorgangs das visuelle Feedback über die "DragUI"-Eigenschaft der "DragStartingEventArgs"-Klasse anpassen. Das Auffordern des Systems zum Verwenden des Inhalts von "DataPackage" zum Generieren des visuellen Inhalts erfolgt über "SetContentFromDataPackage" (siehe Abbildung 6).

Abbildung 6: Verwenden von "SetContentFromDataPackage" zum Generieren visueller Inhalte

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

Sie können eine benutzerdefinierte Bitmap als Inhalt der Benutzeroberfläche des Ziehvorgangs mit zwei Klassen festlegen: der bekannten XAML-Klasse "BitmapImage" oder der neuen Windows 10-Klasse "SoftwareBitmap". Wenn diese Bitmap eine Ressource der Anwendung ist, ist es einfacher, "BitmapImage" zu verwenden und es mit der URI der Ressource zu initialisieren:

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

Wenn die Bitmap direkt generiert werden muss, sobald der Ziehvorgang gestartet oder der Zeiger auf ein Ablegeziel bewegt wird, kann eine "SoftwareBitmap" anhand des Puffers erstellt werden, der von der XAML-Klasse "RenderTargetBitmap" erzeugt wird. Dadurch wird eine Bitmap mit der visuellen Darstellung eines "UIElement" erstellt (siehe Abbildung 7). Dieses "UIElement" muss in der visuellen XAML-Struktur, jedoch nicht im sichtbaren Teil der Seite enthalten sein. Da "RenderTargetBitmap" dieses Rendern asynchron ausführt, ist hier eine Verzögerung erforderlich, damit XAML erkennen kann, ob das Bitmap bereit ist, nachdem der Ereignishandler beendet wurde, und gewartet wird, bis die Verzögerung abgeschlossen ist, um den Inhalt der Benutzeroberfläche für den Ziehvorgang zu aktualisieren. (Der Verzögerungsmechanismus wird im nächsten Abschnitt dieses Artikels ausführlicher erläutert.)

Abbildung 7: Anpassen des Inhalts der Benutzeroberfläche für den Ziehvorgang mit "RenderTargetBitmap" und "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 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();
  }
}

Wenn allerdings das "SoftwareBitmap" bereits generiert wurde (und es für nachfolgende Drag & Drop-Vorgänge zwischengespeichert werden kann), ist keine Verzögerung erforderlich.

Für "SetContentFromBitmapImage" und "SetContentFromSoftwareBitmap" können Sie einen Ankerpunkt angeben, der die Position der Benutzeroberfläche für den Ziehvorgang in Bezug auf die Position des Zeigers angibt. Wenn Sie die Überladung ohne Ankerpunktparameter verwenden, folgt die linke obere Ecke der benutzerdefinierten Bitmap dem Zeiger. Die "GetPosition"-Methode von "DragStartingEventArgs" gibt die Position des Mauszeigers in Bezug auf ein beliebiges "UIElement" an, die verwendet werden kann, um die Startposition der Benutzeroberfläche des Ziehvorgangs genau dort festzulegen, wo sich das gezogene "UIElement" befindet.

Auf der Zielseite können die verschiedenen Teile des gezogenen visuellen Objekts in die Ereignishandlern für "DragEnter" oder "DragOver" angepasst werden. Die Anpassung erfolgt über die "DragUIOverride"-Eigenschaft der "DragEventArgs"-Klasse, die vier "SetContentFrom"-Methoden identisch mit "DragUI" auf der Quellseite sowie vier Eigenschaften verfügbar macht, mit denen Sie verschiedene Teile von "DragUI" ausblenden und die Beschriftung ändern können. Schließlich macht "DragUIOverride" auch eine "Clear-Methode" verfügbar, die alle Überschreibungen von "DragUI" durch das Ziel zurücksetzt.

Asynchrone Vorgänge Die für universelle Windows-Anwendungen erzwingt ein asynchrones Muster für alle Vorgänge, die länger als ein paar Millisekunden dauern können. Dies ist bei Drag & Drop besonders wichtig, da diese Vorgänge vollständig vom Benutzer gesteuert werden. Aufgrund der Vielfalt werden für Drag & Drop drei verschiedene, asynchrone Muster verwendet: asynchrone Aufrufe, Verzögerungen und Rückrufe.

Asynchrone Aufrufe werden verwendet, wenn die Anwendung eine System-API aufruft und der Aufruf einige Zeit in Anspruch nehmen kann. Dieses Muster ist Windows-Entwicklern bekannt und wird mithilfe der Schlüsselwörter "async" und "await" in C# (oder "create_task" und "then" in C++) vereinfacht. Alle Methoden, die Daten aus "DataPackage" abrufen, folgen diesem Muster, wie z. B. "GetBitmapAsync", die in unserer Beispielanwendung zum Abrufen eines Verweises auf einen Bilddatenstrom verwendet wird (siehe Abbildung 8).

Abbildung 8: Verwenden asynchroner Aufrufe zum Lesen von "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();
    }
...
}

Verzögerungen werden verwendet, wenn das XAML-Framework einen Rückruf an Code einer Anwendung richtet, der selbst einen asynchronen Aufruf auslöst, bevor ein vom Framework erwarteter Wert zurückgegeben wird. Dieses Muster wurde in früheren Versionen von XAML nicht häufig verwendet. Deshalb wollen wir uns nun etwas Zeit nehmen, um es zu analysieren. Wenn eine Schaltfläche ein "Click"-Ereignis generiert, ist dies ein unidirektionaler Aufruf in dem Sinne, das kein Wert von der Anwendung zurückgegeben werden muss. Wenn ein asynchroner Aufruf durch die Anwendung erfolgt, ist das Ergebnis nach Abschluss des "Click"-Ereignishandlers verfügbar. Doch dies ist in Ordnung, da dieser Handler keinen Wert zurückgibt.

Wenn XAML hingegen ein "DragEnter"- oder "DragOver"-Ereignis auslöst, wird erwartet, dass die Anwendung die "AcceptedOperation"-Eigenschaft der Ereignisargumente festlegt, um anzugeben, ob der Inhalt von "DataPackage" verarbeitet werden kann. Wenn für die Anwendung nur der verfügbaren Datentyp in "DataPackage" interessant ist, kann dies weiter synchron erfolgen, wie z. B.:

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

Wenn die Anwendung jedoch beispielsweise nur einige Dateitypen akzeptieren kann, muss sie nicht nur die Datentypen in "DataPackage" überprüfen, sondern auch auf die Daten zugreifen, was nur asynchron erfolgen kann. Dies bedeutet, dass die Ausführung des Codes angehalten wird, bis die Daten gelesen wurden und der Ereignishandler für "DragEnter" (oder "DragOver") abgeschlossen ist, bevor die Anwendung erkennt, ob sie die Daten akzeptieren kann. Dieses Szenario ist genau der Zweck der Verzögerung: Durch Abrufen einer Verzögerung aus dem "DragEventArg"-Objekt, informiert XAML die Anwendung, dass ein Teil seiner Verarbeitung verzögert wird. Nach Abschluss der Verzögerung benachrichtigt die Anwendung XAML, dass diese Verarbeitung erfolgt ist und die Ausgabeeigenschaften der "DragEventArgs"-Instanz festgelegt wurden. Sehen Sie sich nochmals Abbildung 4 an, um zu prüfen, wie unsere Beispielanwendung auf "StorageItems" prüft, nachdem die Verzögerung abgerufen wurde.

Die Verzögerung kann auch verwendet werden, wenn die Anpassung der Inhalte der Benutzeroberfläche für den Ziehvorgang asynchrone Vorgänge wie die "RenderAsync"-Methode von "RenderTargetBitmap" erfordert.

Auf der Quellseite eines Drag & Drop-Vorgangs macht "DragStartingEventArgs" außerdem eine Verzögerung verfügbar, deren Zweck ist, dem Vorgang das Starten zu erlauben, sobald der Ereignishandler beendet ist (selbst wenn die Verzögerung noch nicht abgeschlossen wurde). Ziel ist es, dem Benutzer das schnellste Feedback bereitzustellen, auch wenn das Erstellen einer Bitmap zum Anpassen der Benutzeroberfläche für den Ziehvorgang einige Zeit dauert.

Rückrufe werden im "DataPackage" zum Verzögern der Daten bereitstellt, bis sie wirklich benötigt werden. Mit diesem Mechanismus kann die Quellanwendung mehrere Formate im "DataPackage" ankündigen, doch nur die von einem Ziel tatsächlich gelesenen Daten müssen vorbereitet und übertragen werden. In vielen Fällen wird der Rückruf nie aufgerufen, beispielsweise, wenn kein Ziel das entsprechende Datenformat verstehen kann, was eine wünschenswerte Leistungsoptimierung darstellt.

Beachten Sie, dass in vielen Fällen zum Bereitstellen der tatsächlichen Daten ein asynchroner Aufruf erforderlich ist. Deshalb macht der "DataProviderRequest"-Parameter dieses Rückrufs eine Verzögerung verfügbar, damit Daten die Benachrichtigung senden können, dass sie mehr Zeit zum Bereitstellen der Daten brauchen und dass die Daten schließlich zur Verfügung stehen (siehe Abbildung 9).

Abbildung 9: Verzögern der Daten, bis sie tatsächlich gelesen werden

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

Zusammenfassung

Beim Schreiben einer Anwendung, die Standarddatenformate wie Dateien, Bilder oder Text bearbeitet, sollten Sie die Implementierung von Drag & Drop in Betracht ziehen, da dies ein quasi natürlicher Vorgang ist, der den Benutzern bekannt ist. Die Grundlagen von Drag & Drop sind Entwicklern bereits vertraut, die mit Windows Forms und Windows Presentation Foundation programmiert haben. Dadurch verringert sich der Lernaufwand dieses umfassenden Features mit seinen spezifischen Konzepten, wie z. B. Anpassung der Benutzeroberfläche für den Ziehvorgang, und relativ selten verwendeten Muster wie z. B. dem Muster für die Verzögerung. Wenn Sie einfache Drag & Drop-Szenarien unterstützen möchten, können Sie auf Ihre erworbene Erfahrung bauen und die Umsetzung direkt in Angriff nehmen. Sie können aber auch die neuen Features nutzen, um den Benutzern eine maßgeschneiderte Lösung zu bieten.


Anna Pai ist Softwareentwicklerin im Xbox-Team. Zuvor hat sie an Silverlight, Silverlight für Windows Phone und danach an XAML für Windows und Windows Phone gearbeitet.

Alain Zanchetta ist Softwareentwickler im Windows XAML-Team. Zuvor war er Softwarearchitekt in der Beratungsabteilung von Microsoft Frankreich.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Clément Fauchère
Clément Fauchère ist Softwareentwickler im Windows Shell-Team bei Microsoft.