Import media from a device

This article describes how to import media from a device, including searching for available media sources, importing files such as videos, photos, and sidecar files, and deleting the imported files from the source device.

Note

The code in this article was adapted from the MediaImport UWP app sample . You can clone or download this sample from the Universal Windows app samples Git repo to see the code in context or to use it as a starting point for your own app.

Create a simple media import UI

The example in this article uses a minimal UI to enable the core media import scenarios. To see how to create a more robust UI for a media import app, see the MediaImport sample. The following XAML creates a stack panel with the following controls:

  • A Button to initiate searching for sources from which media can be imported.
  • A ComboBox to list and select from the media import sources that are found.
  • A ListView control to display and select from the media items from the selected import source.
  • A Button to initiate importing media items from the selected source.
  • A Button to initiate deleting the items that have been imported from the selected source.
  • A Button to cancel an asynchronous media import operation.
<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>

Set up your code-behind file

Add using directives to include the namespaces used by this example that are not already included in the default project template.

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

Set up task cancellation for media import operations

Because media import operations can take a long time, they are performed asynchronously using IAsyncOperationWithProgress. Declare a class member variable of type CancellationTokenSource that will be used to cancel an in-progress operation if the user clicks the cancel button.

CancellationTokenSource cts;

Implement a handler for the cancel button. The examples shown later in this article will initialize the CancellationTokenSource when an operation begins and set it to null when the operation completes. In the cancel button handler, check to see if the token is null, and if not, call Cancel to cancel the operation.

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

Data binding helper classes

In a typical media import scenario you show the user a list of available media items to import, there can be a large number of media files to choose from and, typically, you want to show a thumbnail for each media item. For this reason, this example uses three helper classes to incrementally load entries into the ListView control as the user scrolls down through the list.

  • IncrementalLoadingBase class - Implements the IList, ISupportIncrementalLoading, and INotifyCollectionChanged to provide the base incremental loading behavior.
  • GeneratorIncrementalLoadingClass class - Provides an implementation of the incremental loading base class.
  • ImportableItemWrapper class - A thin wrapper around the PhotoImportItem class to add a bindable BitmapImage property for the thumbnail image for each imported item.

These classes are provided in the MediaImport sample and can be added to your project without modifications. After adding the helper classes to your project, declare a class member variable of type GeneratorIncrementalLoadingClass that will be used later in this example.

GeneratorIncrementalLoadingClass<ImportableItemWrapper> itemsToImport = null;

Find available sources from which media can be imported

In the click handler for the find sources button, call the static method PhotoImportManager.FindAllSourcesAsync to start the system searching for devices from which media can be imported. After awaiting the completion of the operation, loop through each PhotoImportSource object in the returned list and add an entry to the ComboBox, setting the Tag property to the source object itself so it can be easily retrieved when the user makes a selection.

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

Declare a class member variable to store the user's selected import source.

PhotoImportSource importSource;

In the SelectionChanged handler for the import source ComboBox, set the class member variable to the selected source and then call the FindItems helper method which will be shown later in this article.

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

Find items to import

Add class member variables of type PhotoImportSession and PhotoImportFindItemsResult which will be used in the following steps.

PhotoImportSession importSession;
PhotoImportFindItemsResult itemsResult;

In the FindItems method, initialize the CancellationTokenSource variable so it can be used to cancel the find operation if necessary. Within a try block, create a new import session by calling CreateImportSession on the PhotoImportSource object selected by the user. Create a new Progress object to provide a callback to display the progress of the find operation. Next, call FindItemsAsync to start the find operation. Provide a PhotoImportContentTypeFilter value to specify whether photos, videos, or both should be returned. Provide a PhotoImportItemSelectionMode value to specify whether all, none, or only the new media items are returned with their IsSelected property set to true. This property is bound to a checkbox for each media item in our ListBox item template.

FindItemsAsync returns an IAsyncOperationWithProgress. The extension method AsTask is used to create a task that can be awaited, can be cancelled with the cancellation token, and that reports progress using the supplied Progress object.

Next the data binding helper class, GeneratorIncrementalLoadingClass is initialized. FindItemsAsync, when it returns from being awaited, returns a PhotoImportFindItemsResult object. This object contains status information about the find operation, including the success of the operation and the count of different types of media items that were found. The FoundItems property contains a list of PhotoImportItem objects representing the found media items. The GeneratorIncrementalLoadingClass constructor takes as arguments the total count of items that will be loaded incrementally, and a function that generates new items to be loaded as needed. In this case, the provided lambda expression creates a new instance of the ImportableItemWrapper which wraps PhotoImportItem and includes a thumbnail for each item. Once the incremental loading class has been initialized, set it to the ItemsSource property of the ListView control in the UI. Now, the found media items will be loaded incrementally and displayed in the list.

Next, the status information of the find operation are output. A typical app will display this information to the user in the UI, but this example simply outputs the information to the debug console. Finally, set the cancellation token to null because the operation is complete.

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

Import media items

Before implementing the import operation, declare a PhotoImportImportItemsResult object to store the results of the import operation. This will be used later to delete media items that were successfully imported from the source.

private PhotoImportImportItemsResult importedResult;

Before starting the media import operation initialize the CancellationTokenSource variable and by setting the value of the ProgressBar control to 0.

If there are no selected items in the ListView control, then there is nothing to import. Otherwise, initialize a Progress object to provide a progress callback which updates the value of the progress bar control. Register a handler for the ItemImported event of the PhotoImportFindItemsResult returned by the find operation. This event will be raised whenever an item is imported and, in this example, outputs the name of each imported file to the debug console.

Call ImportItemsAsync to begin the import operation. Just as with the find operation, the AsTask extension method is used to convert the returned operation to a task that can be awaited, reports progress, and can be cancelled.

After the import operation is complete, the operation status can be obtained from the PhotoImportImportItemsResult object returned by ImportItemsAsync. This example outputs the status information to the debug console and then finally, sets the cancellation token to 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;
}

Delete imported items

To delete the successfully imported items from the source from which they were imported, first initialize the cancellation token so that the delete operation can be cancelled and set the progress bar value to 0. Make sure that the PhotoImportImportItemsResult returned from ImportItemsAsync is not null. If not, once again create a Progress object to provide a progress callback for the delete operation. Call DeleteImportedItemsFromSourceAsync to start deleting the imported items. Us AsTask to convert the result to an awaitable task with progress and cancellation capabilities. After awaiting, the returned PhotoImportDeleteImportedItemsFromSourceResult object can be used to get and display status information about the delete operation.


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;


}