从设备导入媒体

本文介绍如何从设备导入媒体,包括搜索可用媒体源、导入文件(如视频、照片和 sidecar 文件)以及从源设备中删除导入的文件。

注意

本文中的代码改编自 MediaImport UWP 应用示例。 你可以从通用 Windows 应用示例 Git 存储库中克隆或下载此示例,以便在上下文中查看代码或将其用作你自己的应用的起始点。

创建简单的媒体导入 UI

本文中的示例使用最少的 UI 来启用核心媒体导入方案。 若要了解如何为媒体导入应用创建更可靠的 UI,请参阅导入示例。 以下 XAML 创建带有以下控件的堆栈面板:

  • 一个按钮,用于发起对可从中导入媒体的源进行搜索的操作。
  • 一个组合框,用于列出所找到的媒体导入源,并从中进行选择。
  • 一个 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 异步执行它们。 声明一个 CancellationTokenSource 类型的类成员变量,该变量将用于在用户单击取消按钮时取消正在进行的操作。

CancellationTokenSource cts;

实现取消按钮的处理程序。 本文后面部分介绍的示例将在操作开始时初始化 CancellationTokenSource,并在操作完成时将其设置为 null。 在取消按钮处理程序中,检查令牌是否为 null,如果不是,则调用 Cancel 来取消该操作。

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

数据绑定帮助程序类

在典型的媒体导入方案中,你向用户显示可供导入的媒体项列表,可能有大量的媒体文件可供选择,通常你需要为每个媒体项显示缩略图。 出于此原因,当用户在列表中向下滚动时,此示例使用三个帮助程序类以增量方式将条目加载到 ListView 控件中。

这些类在 MediaImport 示例中提供,并且无需修改即可添加到你的项目。 在将帮助程序类添加到你的项目后,声明一个 GeneratorIncrementalLoadingClass 类型的类成员变量,此变量将在本示例后面部分中使用。

GeneratorIncrementalLoadingClass<ImportableItemWrapper> itemsToImport = null;

查找可从中导入媒体的可用源

在查找源按钮的单击处理程序中,调用静态方法 PhotoImportManager.FindAllSourcesAsync 来针对可从中导入媒体的设备启动系统搜索。 等待该操作完成后,循环访问返回的列表中的每个 PhotoImportSource 对象,并向组合框 添加一个条目,从而将 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;

在导入源 ComboBoxSelectionChanged 处理程序中,将类成员变量设置为选定的源,然后调用 FindItems 帮助程序方法,此方法将在本文后面部分中进行介绍。

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

查找要导入的项

添加 PhotoImportSessionPhotoImportFindItemsResult 类型的类成员变量,这些变量将在以下步骤中使用。

PhotoImportSession importSession;
PhotoImportFindItemsResult itemsResult;

FindItems 方法中,初始化 CancellationTokenSource 变量,以便可以使用它在必要时取消查找操作。 在 try 块内,通过在用户选择的 PhotoImportSource 对象上调用 CreateImportSession 来创建新的导入会话。 创建新的 Progress 对象以提供用于显示查找操作进度的回调。 接下来,调用 FindItemsAsync 以启动查找操作。 提供一个 PhotoImportContentTypeFilter 值来指定应返回照片、视频还是两者都返回。 提供一个 PhotoImportItemSelectionMode 值来指定是返回全部媒体项、不返回任何媒体项还是仅返回新的媒体项,同时将它们的 IsSelected 属性设置为 true。 此属性绑定到我们的 ListBox 项模板中的每个媒体项的复选框。

FindItemsAsync 返回 IAsyncOperationWithProgress。 扩展方法 AsTask 用于创建可等待、可使用取消令牌取消以及使用所提供的 Progress 对象报告进度的任务。

接下来,初始化数据绑定帮助程序类 GeneratorIncrementalLoadingClass。 当从等待状态返回时,FindItemsAsync 会返回 PhotoImportFindItemsResult 对象。 此对象包含关于查找操作的状态信息,包括操作是否成功以及所找到的不同类型的媒体项的计数。 FoundItems 属性包含表示所找到的媒体项的 PhotoImportItem 对象的列表。 GeneratorIncrementalLoadingClass 构造函数将要以增量方式加载的项的总计数以及生成要按需加载的新项的函数作为参数。 在此情况下,所提供的 lambda 表达式创建 ImportableItemWrapper 的新实例,该实例包装 PhotoImportItem 并包含每个项的缩略图。 初始化增量加载类后,将其设置为 UI 中的 ListView 控件的 ItemsSource 属性。 现在,所找到的媒体项将以增量方式加载,并在列表中显示。

接下来,输出查找操作的状态信息。 典型应用会在 UI 中向用户显示此信息,但本示例仅将该信息输出到调试控制台。 最后,由于操作已完成,将取消令牌设置为 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;

在启动媒体导入操作前,通过将 ProgressBar 控件的值设置为 0 来初始化 CancellationTokenSource 变量。

如果 ListView 控件中没有选定的项,则没有可导入的内容。 否则,初始化 Progress 对象以提供一个进度回调,用于更新进度栏控件的值。 为查找操作返回的 PhotoImportFindItemsResultItemImported 事件注册一个处理程序。 每当导入某个项时都会引发此事件,并且在本示例中,此事件会将每个已导入文件的名称输出到调试控制台。

调用 ImportItemsAsync 开始导入操作。 和查找操作一样,AsTask 扩展方法用于将返回的操作转换为可等待、报告进度和可取消的任务。

在导入操作完成后,可以从 ImportItemsAsync 返回的 PhotoImportImportItemsResult 对象获取操作状态。 此示例会将状态信息输出到调试控制台,接着最后将取消令牌设为 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。 确保从 ImportItemsAsync 返回的 PhotoImportImportItemsResult 不为 null。 如果不是,则再次创建一个 Progress 对象来为删除操作提供进度回调。 调用 DeleteImportedItemsFromSourceAsync 以开始删除导入的项。 使用 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;


}