デバイスからのメディアのインポート

この記事では、利用可能なメディア ソースの検索、ビデオ、写真、サイドカー ファイルなどのファイルのインポート、ソース デバイスからインポートしたファイルの削除など、デバイスからメディアをインポートする方法について説明します。

注意

この記事のコードは、MediaImport UWP アプリ サンプルを基にしています。 このサンプルをユニバーサル Windows アプリ サンプル Git リポジトリから複製またはダウンロードすると、コンテキスト内のコードを確認できます。独自のアプリの出発点として使うこともできます。

シンプルなメディア インポート UI の作成

この記事の例では、メディア インポートのコア シナリオを実現する最小限の UI を使用します。 メディア インポート アプリ用のより強力な UI を作成する方法については、MediaImport サンプルをご覧ください。 次の XAML では、次のコントロールを持つスタック パネルを作成します。

  • メディアをインポートできるソースの検索を開始するための Button
  • 見つかったメディア インポート ソースを一覧にして選択するための ComboBox
  • 選択したインポート ソースのメディア項目を表示して選択するための ListView コントロール。
  • 選択したソースからメディア項目のインポートを開始するための Button
  • 選択したソースからインポートされた項目の削除を開始するための Button
  • 非同期メディア インポート操作を取り消すための Button
<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 かどうかを確認し、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.");
    }
}

データ バインディング ヘルパー クラス

一般的なメディア インポート シナリオでは、インポートできるメディア項目の一覧をユーザーに提示します。選択するメディア ファイルの数は非常に多い可能性があり、通常、各メディア項目のサムネイルを表示します。 このため、この例では 3 つのヘルパー クラスを使用して、ユーザーが一覧をスクロールしていくと ListView コントロールにエントリが段階的に読み込まれるようにします。

  • IncrementalLoadingBase クラス - IListISupportIncrementalLoading、および 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;

インポート ソース ComboBoxSelectionChanged ハンドラーで、クラス メンバー変数を選択したソースに設定します。その後、この記事の広範で示す FindItems ヘルパー メソッドを呼び出します。

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

インポートする項目を見つける

この後の手順で使用する PhotoImportSession 型および PhotoImportFindItemsResult 型のクラス メンバー 変数を追加します。

PhotoImportSession importSession;
PhotoImportFindItemsResult itemsResult;

FindItems メソッドで、CCancellationTokenSource 変数を初期化して、必要に応じて検索操作の取り消しに使用できるようにします。 try ブロック内で、ユーザーが選択した PhotoImportSource オブジェクトで CreateImportSession を呼び出して、新しいインポート セッションを作成します。 検索操作の進行状況を表示するためのコールバックを提供するために、新しい Progress オブジェクトを作成します。 次に、FindItemsAsync を呼び出して、検索操作を開始します。 PhotoImportContentTypeFilter の値を提供して、写真、ビデオ、またはその両方を返す必要があるかどうかを指定します。 PhotoImportItemSelectionMode の値を提供して、IsSelected プロパティが true に設定されているときに、すべてのメディア項目を返すのか、メディア項目を何も返さないのか、新しいメディア項目のみを返すのかを指定します。 このプロパティは、ListBox 項目テンプレートの、各メディア項目のチェック ボックスにバインドされます。

FindItemsAsyncIAsyncOperationWithProgress を返します。 拡張メソッド AsTask は、待機可能で、キャンセル トークンを使用して取り消し可能であり、提供された Progress オブジェクトを使用して進行状況を報告するタスクの作成に使用されます。

次に、データ バインディング ヘルパー クラス GeneratorIncrementalLoadingClass が初期化されます。 FindItemsAsync は、待機から返されると、PhotoImportFindItemsResult オブジェクトを返します。 このオブジェクトには、操作の成功、見つかったさまざまな種類のメディア項目の数など、検索操作の状態情報が含まれます。 FoundItems プロパティには、見つかったメディア項目を表す PhotoImportItem オブジェクトの一覧が含まれます。 GeneratorIncrementalLoadingClass コンストラクターは、段階的に読み込まれる項目の合計数と、必要に応じて読み込まれる新規項目を生成する関数を引数として受け取ります。 この場合、指定されたラムダ式によって、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;

メディアのインポート操作を開始する前に、CancellationTokenSource 変数を初期化し、ProgressBar コントロールの値を 0 に設定します。

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;


}