Импорт мультимедиа с устройства

В этой статье описывается, как импортировать носитель с устройства, включая поиск доступных источников мультимедиа, импорт файлов, таких как видео, фотографии и файлы на стороне, а также удаление импортированных файлов с исходного устройства.

Примечание.

Код в этой статье был адаптирован из примера приложения UWP MediaImport. Вы можете клонировать или скачать этот пример из репозитория универсальных приложений Windows Git, чтобы просмотреть код в контексте или использовать его в качестве отправной точки для собственного приложения.

Создание простого пользовательского интерфейса импорта мультимедиа

В примере в этой статье используется минимальный пользовательский интерфейс для включения основных сценариев импорта мультимедиа. Чтобы узнать, как создать более надежный пользовательский интерфейс для приложения импорта мультимедиа, см . пример MediaImport. Следующий XAML создает панель стека со следующими элементами управления:

  • Кнопка для запуска поиска источников, из которых можно импортировать носитель.
  • Объект ComboBox для списка и выбора из найденных источников импорта мультимедиа.
  • Элемент управления ListView для отображения и выбора элементов мультимедиа из выбранного источника импорта.
  • Кнопка для запуска импорта элементов мультимедиа из выбранного источника.
  • Кнопка для запуска удаления элементов, импортированных из выбранного источника.
  • Кнопка для отмены асинхронной операции импорта мультимедиа.
<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>

Настройка файла программной части

Добавьте директивы using для включения пространств имен, используемых в этом примере, которые еще не включены в шаблон проекта по умолчанию.

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

Настройка отмены задачи для операций импорта мультимедиа

Так как операции импорта мультимедиа могут занять много времени, они выполняются асинхронно с помощью IAsyncOperationWithProgress. Объявите переменную члена класса типа CancelTokenSource , которая будет использоваться для отмены операции выполнения, если пользователь нажимает кнопку отмены.

CancellationTokenSource cts;

Реализуйте обработчик для кнопки отмены. Примеры, показанные далее в этой статье, инициализирует ОтменуTokenSource при запуске операции и задайте для нее значение NULL после завершения операции. В обработчике кнопки отмены проверка, чтобы узнать, имеет ли маркер значение NULL, а если нет, вызовите "Отмена", чтобы отменить операцию.

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

Вспомогательные классы привязки данных

В типичном сценарии импорта мультимедиа вы показываете пользователю список доступных элементов мультимедиа для импорта, может быть большое количество файлов мультимедиа для выбора и, как правило, вы хотите отобразить эскиз для каждого элемента мультимедиа. По этой причине в этом примере используются три вспомогательных класса для добавочной загрузки записей в элемент управления ListView, так как пользователь прокручивает список вниз.

  • Класс IncrementalLoadingBase — реализует IList, ISupportIncrementalLoading и INotifyCollectionChanged, чтобы обеспечить базовое поведение добавочной загрузки.
  • Класс GeneratorIncrementalLoadingClass — предоставляет реализацию базового класса добавочной загрузки.
  • Класс ImportableItemWrapper — тонкий оболочка вокруг класса PhotoImportItem, чтобы добавить привязываемое свойство BitmapImage для изображения эскиза для каждого импортированного элемента.

Эти классы предоставляются в примере MediaImport и могут быть добавлены в проект без изменений. После добавления вспомогательных классов в проект объявите переменную члена класса типа GeneratorIncrementalLoadingClass , которая будет использоваться далее в этом примере.

GeneratorIncrementalLoadingClass<ImportableItemWrapper> itemsToImport = null;

Поиск доступных источников, из которых можно импортировать носитель

В обработчике нажатия кнопки поиска источников вызовите статический метод PhotoImportManager.FindAllSourcesAsync , чтобы запустить системный поиск устройств, из которых можно импортировать носитель. После завершения операции выполните цикл по каждому объекту PhotoImportSource в возвращаемом списке и добавьте запись в comboBox, задав свойству Tag сам исходный объект, чтобы его можно было легко получить при выборе пользователя.

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

Объявите переменную члена класса для хранения выбранного источника импорта пользователя.

PhotoImportSource importSource;

В обработчике SelectionChanged для импорта source ComboBox задайте переменную члена класса выбранному источнику, а затем вызовите вспомогательный метод FindItems , который будет показан далее в этой статье.

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

Поиск элементов для импорта

Добавьте переменные члена класса типа PhotoImportSession и PhotoImportFindItemsResult, которые будут использоваться в следующих шагах.

PhotoImportSession importSession;
PhotoImportFindItemsResult itemsResult;

В методе FindItems инициализируйте переменную CancelTokenSource , чтобы ее можно было использовать для отмены операции поиска при необходимости. В блоке try создайте новый сеанс импорта, вызвав CreateImportSession в объекте PhotoImportSource, выбранном пользователем. Создайте новый объект Progress , чтобы обеспечить обратный вызов для отображения хода выполнения операции поиска. Затем вызовите FindItemsAsync , чтобы запустить операцию поиска. Укажите значение PhotoImportContentTypeFilter, чтобы указать, должны ли быть возвращены фотографии, видео или оба. Укажите значение PhotoImportItemSelectionMode, чтобы указать, возвращаются ли все, нет или только новые элементы мультимедиа с заданным свойством IsSelected значение true. Это свойство привязано к проверка box для каждого элемента мультимедиа в шаблоне элемента ListBox.

FindItemsAsync возвращает IAsyncOperationWithProgress. Метод расширения AsTask используется для создания задачи, которую можно ожидать, можно отменить с помощью маркера отмены и сообщить о ходе выполнения с помощью предоставленного объекта Progress.

Далее вспомогательный класс привязки данных инициализируется GeneratorIncrementalLoadingClass . FindItemsAsync, когда возвращается от ожидания, возвращает объект PhotoImportFindItemsResult . Этот объект содержит сведения о состоянии операции поиска, включая успешность операции и количество найденных элементов мультимедиа различных типов. Свойство FoundItems содержит список объектов PhotoImportItem , представляющих найденные элементы мультимедиа. Конструктор GeneratorIncrementalLoadingClass принимает в качестве аргументов общее количество элементов, которые будут загружаться добавочно, и функция, которая создает новые элементы для загрузки по мере необходимости. В этом случае предоставленное лямбда-выражение создает новый экземпляр ImportableItemWrapper , который упаковывает PhotoImportItem и включает эскиз для каждого элемента. После инициализации класса добавочной загрузки задайте для него свойство ItemsSource элемента управления ListView в пользовательском интерфейсе. Теперь найденные элементы мультимедиа будут загружаться постепенно и отображаться в списке.

Затем выходные данные о состоянии операции поиска. Обычное приложение отображает эти сведения пользователю в пользовательском интерфейсе, но в этом примере просто выводится информация в консоль отладки. Наконец, задайте для маркера отмены значение NULL, так как операция завершена.

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

Импорт элементов мультимедиа

Перед реализацией операции импорта объявите объект PhotoImportImportItemsResult для хранения результатов операции импорта. Это будет использоваться позже для удаления элементов мультимедиа, которые были успешно импортированы из источника.

private PhotoImportImportItemsResult importedResult;

Перед запуском операции импорта мультимедиа инициализирует переменную CancellationTokenSource и задав для элемента управления ProgressBar значение 0.

Если в элементе управления ListView нет выбранных элементов, импорт не требуется. В противном случае инициализируйте объект Progress, чтобы обеспечить обратный вызов хода выполнения, который обновляет значение элемента управления индикатора выполнения. Зарегистрируйте обработчик для события ItemImported объекта PhotoImportFindItemsResult, возвращенного операцией поиска. Это событие будет возникать всякий раз, когда элемент импортируется, и в этом примере выводит имя каждого импортированного файла в консоль отладки.

Вызовите ImportItemsAsync , чтобы начать операцию импорта. Как и при операции поиска, метод расширения AsTask используется для преобразования возвращаемой операции в задачу, которую можно ожидать, отчеты о ходе выполнения и может быть отменена.

После завершения операции импорта состояние операции можно получить из объекта PhotoImportImportItemsResult, возвращаемого ImportItemsAsync. Этот пример выводит сведения о состоянии в консоль отладки, а затем, наконец, задает маркер отмены значение NULL.

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

Удаление импортированных элементов

Чтобы удалить успешно импортированные элементы из источника, из которого они были импортированы, сначала инициализируйте маркер отмены, чтобы операция удаления была отменена и задайте значение индикатора хода выполнения значение 0. Убедитесь, что photoImportImportItemsResult , возвращенный из ImportItemsAsync , не имеет значения NULL. Если нет, еще раз создайте объект Progress , чтобы обеспечить обратный вызов хода выполнения для операции удаления. Вызовите DeleteImportedItemsFromSourceAsync , чтобы начать удаление импортированных элементов. Us AsTask для преобразования результата в ожидаемую задачу с возможностями хода выполнения и отмены. После ожидания возвращаемый объект PhotoImportDeleteImportedItemsFromSourceResult можно использовать для получения и отображения сведений о состоянии операции удаления.


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;


}