Importieren von Medien von einem Gerät

In diesem Artikel wird beschrieben, wie Sie Medien von einem Gerät importieren, einschließlich der Suche nach verfügbaren Medienquellen, des Imports von Dateien wie Videos, Fotos und Sidecar-Dateien und des Löschens der importierten Dateien vom Quellgerät.

Hinweis

Der Code in diesem Artikel wurde aus dem Beispiel der MediaImport UWP-App übernommen. Sie können dieses Beispiel aus dem Universal Windows app samples Git Repo klonen oder herunterladen, um den Code im Kontext zu sehen oder ihn als Ausgangspunkt für Ihre eigene Anwendung zu verwenden.

Erstellen einer einfachen Medienimport-UI

Das Beispiel in diesem Artikel verwendet eine minimale Benutzeroberfläche, um die wichtigsten Medienimport-Szenarien zu ermöglichen. Um zu sehen, wie man eine robustere Benutzeroberfläche für eine Medienimport-App erstellt, sehen Sie sich das MediaImport-Beispiel an. Die folgende XAML-Datei erstellt ein Stapelpanel mit den folgenden Steuerelementen:

  • Eine Schaltfläche, um die Suche nach Quellen zu starten, aus denen Medien importiert werden können.
  • Eine ComboBox zur Auflistung und Auswahl der gefundenen Medienimportquellen.
  • Ein ListView Steuerelement zur Anzeige und Auswahl der Medienelemente aus der ausgewählten Importquelle.
  • Eine Schaltfläche, um den Import von Medienelementen aus der ausgewählten Quelle zu starten.
  • Eine Schaltfläche leitet das Löschen der aus der ausgewählten Quelle importierten Elemente ein.
  • Eine Schaltfläche zum Abbrechen eines asynchronen Medienimportvorgangs.
<StackPanel Orientation="Vertical">
    <Button x:Name="findSourcesButton" Click="findSourcesButton_Click" Content="Find sources"/>
    <ComboBox x:Name="sourcesComboBox" SelectionChanged="sourcesComboBox_SelectionChanged"/>
    <ListView x:Name="fileListView" 
                    HorizontalAlignment="Left" Margin="182,260,0,171" 
                    Width="715" 
                    SelectionMode="None" 
                    BorderBrush="#FF858585"   
                    BorderThickness="1" 
                    ScrollViewer.VerticalScrollBarVisibility="Visible">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="0.05*"/>
                        <ColumnDefinition Width="0.20*"/>
                        <ColumnDefinition Width="0.75*"/>
                    </Grid.ColumnDefinitions>
                    <CheckBox Grid.Column="0" IsChecked="{Binding ImportableItem.IsSelected, Mode=TwoWay}" />
                    <!-- Click="CheckBox_Click"/>-->
                    <Image Grid.Column="1" Source="{Binding Thumbnail}" Width="120" Height="120" Stretch="Uniform"/>
                    <TextBlock Grid.Column="2" Text="{Binding ImportableItem.Name}" VerticalAlignment="Center" Margin="10,0"/>
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    
    <Button x:Name="importButton" Click="importButton_Click" Content="Import"/>
    <Button x:Name="deleteButton" Click="deleteButton_Click" Content="Delete"/>
    <Button x:Name="cancelButton" Click="cancelButton_Click" Content="Cancel"/>
    <ProgressBar x:Name="progressBar" SmallChange="0.01" LargeChange="0.1" Maximum="1"/>
    
</StackPanel>

Richten Sie Ihre Code-Behind-Datei ein

Fügen Sie unter Verwendung von Direktiven hinzu, um die von diesem Beispiel verwendeten Namespaces einzuschließen, die nicht bereits in der Standardprojektvorlage enthalten sind.

using Windows.Media.Import;
using System.Threading;
using Windows.UI.Core;
using System.Text;

Einrichten der Aufgabenstornierung für Medienimportvorgänge

Da Medienimportvorgänge viel Zeit in Anspruch nehmen können, werden sie asynchron mit IAsyncOperationWithProgress durchgeführt. Deklarieren Sie eine Klassenvariable vom Typ CancellationTokenSource, die zum Abbrechen eines laufenden Vorgangs verwendet wird, wenn der Benutzer auf die Schaltfläche Abbrechen klickt.

CancellationTokenSource cts;

Implementieren Sie einen Handler für die Schaltfläche Abbrechen. In den später in diesem Artikel gezeigten Beispielen wird die CancellationTokenSource initialisiert, wenn ein Vorgang beginnt, und auf null gesetzt, wenn der Vorgang abgeschlossen ist. Prüfen Sie im Handler für die Abbrechen-Schaltfläche, ob das Token null ist, und rufen Sie andernfalls Cancel auf, um den Vorgang abzubrechen.

private void cancelButton_Click(object sender, RoutedEventArgs e)
{
    if (cts != null)
    {
        cts.Cancel();
        System.Diagnostics.Debug.WriteLine("Operation canceled by the Cancel button.");
    }
}

Hilfsklassen für die Datenbindung

In einem typischen Medienimport-Szenario zeigen Sie dem Benutzer eine Liste der verfügbaren zu importierenden Medienelemente an. Es kann eine große Anzahl von Mediendateien zur Auswahl stehen, und in der Regel möchten Sie für jedes Medienelement eine Vorschauminiatur anzeigen. Aus diesem Grund werden in diesem Beispiel drei Hilfsklassen verwendet, um Einträge schrittweise in das ListView-Steuerelement zu laden, wenn der Benutzer durch die Liste blättert.

  • IncrementalLoadingBase Klasse - Implementiert die IList, ISupportIncrementalLoading und INotifyCollectionChanged, um das grundlegende inkrementelle Ladeverhalten bereitzustellen.
  • GeneratorIncrementalLoadingClass Klasse - Bietet eine Implementierung der Basisklasse für inkrementelles Laden.
  • ImportableItemWrapper Klasse - Ein dünner Wrapper um die PhotoImportItem Klasse, um eine bindbare BitmapImage Eigenschaft für das Thumbnail-Bild für jedes importierte Element hinzuzufügen.

Diese Klassen werden im MediaImport-Beispiel bereitgestellt und können ohne Änderungen zu Ihrem Projekt hinzugefügt werden. Nachdem Sie die Hilfsklassen zu Ihrem Projekt hinzugefügt haben, deklarieren Sie eine Klassenvariable vom Typ GeneratorIncrementalLoadingClass, die später in diesem Beispiel verwendet wird.

GeneratorIncrementalLoadingClass<ImportableItemWrapper> itemsToImport = null;

Suche nach verfügbaren Quellen, aus denen Medien importiert werden können

Rufen Sie im Click-Handler für die Schaltfläche „Quellen suchen“ die statische Methode PhotoImportManager.FindAllSourcesAsync auf, damit das System nach Geräten sucht, von denen Medien importiert werden können. Nachdem Sie den Abschluss des Vorgangs abgewartet haben, durchlaufen Sie in einer Schleife jedes PhotoImportSource-Objekt in der zurückgegebenen Liste und fügen der ComboBox einen Eintrag hinzu, wobei Sie die Eigenschaft Tag auf das Quellobjekt selbst setzen, damit es leicht abgerufen werden kann, wenn der Benutzer eine Auswahl trifft.

private async void findSourcesButton_Click(object sender, RoutedEventArgs e)
{
    var sources = await PhotoImportManager.FindAllSourcesAsync();
    foreach (PhotoImportSource source in sources)
    {
        ComboBoxItem item = new ComboBoxItem();
        item.Content = source.DisplayName;
        item.Tag = source;
        sourcesComboBox.Items.Add(item);
    }
}

Deklarieren Sie eine Klassenvariable zur Speicherung der vom Benutzer gewählten Importquelle.

PhotoImportSource importSource;

Im Handler SelectionChanged für die Importquelle ComboBox setzen Sie die Klassenvariable auf die ausgewählte Quelle und rufen dann die Hilfsmethode FindItems auf, die später in diesem Artikel gezeigt wird.

private void sourcesComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    this.importSource = (PhotoImportSource)((ComboBoxItem)sourcesComboBox.SelectedItem).Tag;
    FindItems();
}

Zu importierende Objekte finden

Fügen Sie Klassenmitgliedsvariablen des Typs PhotoImportSession und PhotoImportFindItemsResult hinzu, die in den folgenden Schritten verwendet werden.

PhotoImportSession importSession;
PhotoImportFindItemsResult itemsResult;

Initialisieren Sie in der Methode FindItems die Variable CancellationTokenSource, damit sie bei Bedarf zum Abbrechen des Suchvorgangs verwendet werden kann. Erstellen Sie innerhalb eines try-Blocks eine neue Importsitzung, indem Sie CreateImportSession für das vom Benutzer ausgewählte PhotoImportSource-Objekt aufrufen. Erstellen Sie ein neues Progress-Objekt, um einen Callback zur Anzeige des Fortschritts des Suchvorgangs bereitzustellen. Rufen Sie anschließend FindItemsAsync auf, um den Suchvorgang zu starten. Geben Sie einen PhotoImportContentTypeFilter Wert an, um anzugeben, ob Fotos, Videos oder beides zurückgegeben werden sollen. Geben Sie einen PhotoImportItemSelectionMode Wert an, um anzugeben, ob alle, keine oder nur die neuen Medienelemente zurückgegeben werden, deren IsSelected Eigenschaft auf true gesetzt ist. Diese Eigenschaft ist an ein Kontrollkästchen für jedes Medienelement in unserer ListBox-Elementvorlage gebunden.

FindItemsAsync gibt eine IAsyncOperationWithProgress zurück. Die Erweiterungsmethode AsTask wird verwendet, um eine Aufgabe zu erstellen, auf die gewartet werden kann, die mit dem Stornierungs-Token abgebrochen werden kann und die den Fortschritt mit dem übergebenen Progress-Objekt meldet.

Als nächstes wird die Datenbindungs-Helferklasse GeneratorIncrementalLoadingClass initialisiert. FindItemsAsync gibt, wenn es aus der Wartezeit zurückkehrt, ein PhotoImportFindItemsResult-Objekt zurück. Dieses Objekt enthält Statusinformationen über den Suchvorgang, einschließlich des Erfolgs des Vorgangs und der Anzahl der verschiedenen Arten von Medienelementen, die gefunden wurden. Die Eigenschaft FoundItems enthält eine Liste von PhotoImportItem-Objekten, die die gefundenen Medienelemente darstellen. Der Konstruktor GeneratorIncrementalLoadingClass nimmt als Argumente die Gesamtzahl der Elemente, die inkrementell geladen werden sollen, und eine Funktion, die bei Bedarf neue zu ladende Elemente erzeugt. In diesem Fall erstellt der bereitgestellte Lambda-Ausdruck eine neue Instanz des ImportableItemWrapper, der PhotoImportItem umhüllt und eine Vorschauminiatur für jedes Element enthält. Nachdem die Klasse für inkrementelles Laden initialisiert wurde, setzen Sie sie auf die Eigenschaft ItemsSource des ListView Steuerelements in der Benutzeroberfläche. Nun werden die gefundenen Medienelemente schrittweise geladen und in der Liste angezeigt.

Anschließend werden die Statusinformationen des Suchvorgangs ausgegeben. Eine typische Anwendung zeigt diese Informationen dem Benutzer in der Benutzeroberfläche an, aber dieses Beispiel gibt die Informationen einfach in der Debug-Konsole aus. Abschließend setzen Sie das Stornierungs-Token auf Null, da der Vorgang abgeschlossen ist.

private async void FindItems()
{
    this.cts = new CancellationTokenSource();

    try
    {
        this.importSession = this.importSource.CreateImportSession();

        // Progress handler for FindItemsAsync
        var progress = new Progress<uint>((result) =>
        {
            System.Diagnostics.Debug.WriteLine(String.Format("Found {0} Files", result.ToString()));
        });

        this.itemsResult =
            await this.importSession.FindItemsAsync(PhotoImportContentTypeFilter.ImagesAndVideos, PhotoImportItemSelectionMode.SelectAll)
            .AsTask(this.cts.Token, progress);

        // GeneratorIncrementalLoadingClass is used to incrementally load items in the Listview view including thumbnails
        this.itemsToImport = new GeneratorIncrementalLoadingClass<ImportableItemWrapper>(this.itemsResult.TotalCount,
        (int index) =>
        {
            return new ImportableItemWrapper(this.itemsResult.FoundItems[index]);
        });

        // Set the items source for the ListView control
        this.fileListView.ItemsSource = this.itemsToImport;

        // Log the find results
        if (this.itemsResult != null)
        {
            var findResultProperties = new System.Text.StringBuilder();
            findResultProperties.AppendLine(String.Format("Photos\t\t\t :  {0} \t\t Selected Photos\t\t:  {1}", itemsResult.PhotosCount, itemsResult.SelectedPhotosCount));
            findResultProperties.AppendLine(String.Format("Videos\t\t\t :  {0} \t\t Selected Videos\t\t:  {1}", itemsResult.VideosCount, itemsResult.SelectedVideosCount));
            findResultProperties.AppendLine(String.Format("SideCars\t\t :  {0} \t\t Selected Sidecars\t:  {1}", itemsResult.SidecarsCount, itemsResult.SelectedSidecarsCount));
            findResultProperties.AppendLine(String.Format("Siblings\t\t\t :  {0} \t\t Selected Sibilings\t:  {1} ", itemsResult.SiblingsCount, itemsResult.SelectedSiblingsCount));
            findResultProperties.AppendLine(String.Format("Total Items Items\t :  {0} \t\t Selected TotalCount \t:  {1}", itemsResult.TotalCount, itemsResult.SelectedTotalCount));
            System.Diagnostics.Debug.WriteLine(findResultProperties.ToString());
        }

        if (this.itemsResult.HasSucceeded)
        {
            // Update UI to indicate success
            System.Diagnostics.Debug.WriteLine("FindItemsAsync succeeded.");
        }
        else
        {
            // Update UI to indicate that the operation did not complete
            System.Diagnostics.Debug.WriteLine("FindItemsAsync did not succeed or was not completed.");
        }
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("Photo import find items operation failed. " + ex.Message);
    }


    this.cts = null;
}

Medienelemente importieren

Bevor Sie den Importvorgang implementieren, deklarieren Sie ein PhotoImportImportItemsResult-Objekt, um die Ergebnisse des Importvorgangs zu speichern. Dies wird später verwendet, um Medienelemente zu löschen, die erfolgreich aus der Quelle importiert wurden.

private PhotoImportImportItemsResult importedResult;

Initialisieren Sie vor dem Start des Medienimportvorgangs die Variable CancellationTokenSource und setzen Sie den Wert des Steuerelements ProgressBar auf 0.

Wenn im Steuerelement ListView keine ausgewählten Elemente vorhanden sind, gibt es auch nichts zu importieren. Andernfalls initialisieren Sie ein Progress-Objekt, um einen Fortschritts-Callback bereitzustellen, der den Wert des Fortschrittsbalken-Steuerelements aktualisiert. Registrieren Sie einen Handler für das ItemImported Ereignis des PhotoImportFindItemsResult, das vom Suchvorgang zurückgegeben wird. Dieses Ereignis wird immer dann ausgelöst, wenn ein Element importiert wird, und gibt in diesem Beispiel den Namen jeder importierten Datei auf der Debug-Konsole aus.

Rufen Sie ImportItemsAsync auf, um den Importvorgang zu starten. Genau wie beim Suchvorgang wird die Erweiterungsmethode AsTask verwendet, um den zurückgegebenen Vorgang in eine Aufgabe umzuwandeln, die abgewartet werden kann, den Fortschritt meldet und abgebrochen werden kann.

Nach Abschluss des Importvorgangs kann der Status des Vorgangs dem Objekt PhotoImportImportItemsResult entnommen werden, das von ImportItemsAsync zurückgegeben wird. In diesem Beispiel werden die Statusinformationen auf der Debug-Konsole ausgegeben und schließlich das Stornierungs-Token auf Null gesetzt.

private async void importButton_Click(object sender, RoutedEventArgs e)
{
    cts = new CancellationTokenSource();
    progressBar.Value = 0;

    try
    {
        if (itemsResult.SelectedTotalCount <= 0)
        {
            System.Diagnostics.Debug.WriteLine("Nothing Selected for Import.");
        }
        else
        {
            var progress = new Progress<PhotoImportProgress>((result) =>
            {
                progressBar.Value = result.ImportProgress;
            });

            this.itemsResult.ItemImported += async (s, a) =>
            {
                await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    System.Diagnostics.Debug.WriteLine(String.Format("Imported: {0}", a.ImportedItem.Name));
                });
            };

            // import items from the our list of selected items
            this.importedResult = await this.itemsResult.ImportItemsAsync().AsTask(cts.Token, progress);

            if (importedResult != null)
            {
                StringBuilder importedSummary = new StringBuilder();
                importedSummary.AppendLine(String.Format("Photos Imported   \t:  {0} ", importedResult.PhotosCount));
                importedSummary.AppendLine(String.Format("Videos Imported    \t:  {0} ", importedResult.VideosCount));
                importedSummary.AppendLine(String.Format("SideCars Imported   \t:  {0} ", importedResult.SidecarsCount));
                importedSummary.AppendLine(String.Format("Siblings Imported   \t:  {0} ", importedResult.SiblingsCount));
                importedSummary.AppendLine(String.Format("Total Items Imported \t:  {0} ", importedResult.TotalCount));
                importedSummary.AppendLine(String.Format("Total Bytes Imported \t:  {0} ", importedResult.TotalSizeInBytes));

                System.Diagnostics.Debug.WriteLine(importedSummary.ToString());
            }

            if (!this.importedResult.HasSucceeded)
            {
                System.Diagnostics.Debug.WriteLine("ImportItemsAsync did not succeed or was not completed");
            }
        }
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("Files could not be imported. " + "Exception: " + ex.ToString());
    }

    cts = null;
}

Importierte Objekte löschen

Um die erfolgreich importierten Elemente aus der Quelle, aus der sie importiert wurden, zu löschen, initialisieren Sie zunächst das Stornierungs-Token, damit der Löschvorgang abgebrochen werden kann, und setzen Sie den Wert des Fortschrittsbalkens auf 0. Vergewissern Sie sich, dass das von ImportItemsAsync zurückgegebene PhotoImportImportItemsResult nicht null ist. Falls nicht, erstellen Sie erneut ein Progress-Objekt, um einen Fortschritts-Callback für den Löschvorgang bereitzustellen. Rufen Sie DeleteImportedItemsFromSourceAsync auf, um mit dem Löschen der importierten Elemente zu beginnen. Verwenden Sie AsTask, um das Ergebnis in eine erwartbare Aufgabe mit Fortschritts- und Abbruchmöglichkeiten umzuwandeln. Nach dem Abwarten kann das zurückgegebene Objekt PhotoImportDeleteImportedItemsFromSourceResult verwendet werden, um Statusinformationen über den Löschvorgang zu erhalten und anzuzeigen.


private async void deleteButton_Click(object sender, RoutedEventArgs e)
{
    cts = new CancellationTokenSource();
    progressBar.Value = 0;

    try
    {
        if (importedResult == null)
        {
            System.Diagnostics.Debug.WriteLine("Nothing was imported for deletion.");
        }
        else
        {
            var progress = new Progress<double>((result) =>
            {
                this.progressBar.Value = result;
            });

            PhotoImportDeleteImportedItemsFromSourceResult deleteResult = await this.importedResult.DeleteImportedItemsFromSourceAsync().AsTask(cts.Token, progress);

            if (deleteResult != null)
            {
                StringBuilder deletedResults = new StringBuilder();
                deletedResults.AppendLine(String.Format("Total Photos Deleted:\t{0} ", deleteResult.PhotosCount));
                deletedResults.AppendLine(String.Format("Total Videos Deleted:\t{0} ", deleteResult.VideosCount));
                deletedResults.AppendLine(String.Format("Total Sidecars Deleted:\t{0} ", deleteResult.SidecarsCount));
                deletedResults.AppendLine(String.Format("Total Sibilings Deleted:\t{0} ", deleteResult.SiblingsCount));
                deletedResults.AppendLine(String.Format("Total Files Deleted:\t{0} ", deleteResult.TotalCount));
                deletedResults.AppendLine(String.Format("Total Bytes Deleted:\t{0} ", deleteResult.TotalSizeInBytes));
                System.Diagnostics.Debug.WriteLine(deletedResults.ToString());
            }

            if (!deleteResult.HasSucceeded)
            {
                System.Diagnostics.Debug.WriteLine("Delete operation did not succeed or was not completed");
            }
        }

    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("Files could not be Deleted." + "Exception: " + ex.ToString());
    }

    // set the CancellationTokenSource to null when the work is complete.
    cts = null;


}