Importar contenido multimedia desde un dispositivo
En este artículo se describe cómo importar medios desde un dispositivo, incluida la búsqueda de orígenes multimedia disponibles, la importación de archivos como vídeos, fotos y archivos sidecar, y la eliminación de los archivos importados del dispositivo de origen.
Nota:
El código de este artículo se adaptó del ejemplo de aplicación UWP MediaImport. Puede clonar o descargar este ejemplo desde el repositorio Git de ejemplos de aplicaciones universales de Windows para ver el código en contexto o usarlo como punto de partida para su propia aplicación.
Creación de una interfaz de usuario de importación de medios sencilla
En el ejemplo de este artículo se usa una interfaz de usuario mínima para habilitar los escenarios principales de importación de medios. Para ver cómo crear una interfaz de usuario más sólida para una aplicación de importación de medios, consulte el ejemplo de MediaImport. El código XAML siguiente crea un panel de pila con los siguientes controles:
- Un botón para iniciar la búsqueda de orígenes desde los que se puede importar el medio.
- Un ComboBox para enumerar y seleccionar entre los orígenes de importación multimedia que se encuentren.
- Un control ListView para mostrar y seleccionar entre los elementos multimedia del origen de importación seleccionado.
- Un botón para iniciar la importación de elementos multimedia desde el origen seleccionado.
- Un botón para iniciar la eliminación de los elementos que se han importado del origen seleccionado.
- Un botón para cancelar una operación de importación de medios asincrónica.
<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>
Configuración del archivo de código subyacente
Agregue directivas using para incluir los espacios de nombres usados por este ejemplo que aún no están incluidos en la plantilla de proyecto predeterminada.
using Windows.Media.Import;
using System.Threading;
using Windows.UI.Core;
using System.Text;
Configuración de la cancelación de tareas para las operaciones de importación de medios
Dado que las operaciones de importación de medios pueden tardar mucho tiempo, se realizan de forma asincrónica mediante IAsyncOperationWithProgress. Declare una variable miembro de clase de tipo CancellationTokenSource que se usará para cancelar una operación en curso si el usuario hace clic en el botón para cancelar.
CancellationTokenSource cts;
Implemente un controlador para el botón de cancelación. Los ejemplos que se muestran más adelante en este artículo inicializarán CancellationTokenSource cuando se inicie una operación y lo establecerán en null cuando se complete la operación. En el controlador de botones de cancelación, compruebe si el token es null y, si no es así, llame a Cancel para cancelar la operación.
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
System.Diagnostics.Debug.WriteLine("Operation canceled by the Cancel button.");
}
}
Clases auxiliares de enlace de datos
En un escenario de importación de medios típico, se muestra al usuario una lista de elementos multimedia disponibles para importar, puede haber un gran número de archivos multimedia entre los que elegir y, normalmente, le interesará mostrar una miniatura para cada elemento multimedia. Por este motivo, en este ejemplo se usan tres clases auxiliares para cargar incrementalmente entradas en el control ListView a medida que el usuario se desplaza hacia abajo por la lista.
- Clase IncrementalLoadingBase: implementa IList, ISupportIncrementalLoading e INotifyCollectionChanged para proporcionar el comportamiento de carga incremental base.
- Clase GeneratorIncrementalLoadingClass: proporciona una implementación de la clase base de carga incremental.
- Clase ImportableItemWrapper: un contenedor fino alrededor de la clase PhotoImportItem para agregar una propiedad BitmapImage enlazable para la imagen en miniatura de cada elemento importado.
Estas clases se proporcionan en el ejemplo MediaImport y se pueden agregar al proyecto sin modificaciones. Después de agregar las clases auxiliares al proyecto, declare una variable miembro de clase de tipo GeneratorIncrementalLoadingClass que se usará más adelante en este ejemplo.
GeneratorIncrementalLoadingClass<ImportableItemWrapper> itemsToImport = null;
Búsqueda de orígenes disponibles desde los que se puede importar contenido multimedia
En el controlador de clic del botón para buscar orígenes, llame al método estático PhotoImportManager.FindAllSourcesAsync para iniciar la búsqueda del sistema de dispositivos desde los que se pueden importar medios. Después de esperar a que finalice la operación, recorra cada objeto PhotoImportSource en la lista devuelta y agregue una entrada al ComboBox, estableciendo la propiedad Tag en el propio objeto de origen para que se pueda recuperar fácilmente cuando el usuario realice una selección.
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 una variable miembro de clase para almacenar el origen de importación seleccionado del usuario.
PhotoImportSource importSource;
En el controlador SelectionChanged del ComboBox de origen de importación, establezca la variable miembro de clase en el origen seleccionado y, a continuación, llame al método auxiliar FindItems que se mostrará más adelante en este artículo.
private void sourcesComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.importSource = (PhotoImportSource)((ComboBoxItem)sourcesComboBox.SelectedItem).Tag;
FindItems();
}
Buscar elementos para importar
Agregue variables de miembro de clase de tipo PhotoImportSession y PhotoImportFindItemsResult, que se usarán en los pasos siguientes.
PhotoImportSession importSession;
PhotoImportFindItemsResult itemsResult;
En el método FindItems, inicialice la variable CancellationTokenSource para que se pueda usar para cancelar la operación de búsqueda si es necesario. Dentro de un bloque try, cree una nueva sesión de importación llamando a CreateImportSession en el objeto PhotoImportSource seleccionado por el usuario. Cree un nuevo objeto Progress para proporcionar una devolución de llamada para mostrar el progreso de la operación de búsqueda. A continuación, llame a FindItemsAsync para iniciar la operación de búsqueda. Proporcione un valor PhotoImportContentTypeFilter para especificar si se deben devolver fotos, vídeos o ambos. Proporcione un valor PhotoImportItemSelectionMode para especificar si todos, ninguno o solo los nuevos elementos multimedia se devuelven con su propiedad IsSelected establecida en true. Esta propiedad está enlazada a una casilla para cada elemento multimedia de nuestra plantilla de elemento ListBox.
FindItemsAsync devuelve un IAsyncOperationWithProgress. El método de extensión AsTask se usa para crear una tarea que se puede esperar, se puede cancelar con el token de cancelación y que informa del progreso mediante el objeto Progress proporcionado.
A continuación, se inicializa la clase auxiliar de enlace de datos GeneratorIncrementalLoadingClass. FindItemsAsync, cuando vuelve de esperarse, devuelve un objeto PhotoImportFindItemsResult. Este objeto contiene información de estado sobre la operación de búsqueda, incluido el éxito de la operación y el recuento de los diferentes tipos de elementos multimedia que se encontraron. La propiedad FoundItems contiene una lista de objetos PhotoImportItem que representan los elementos multimedia encontrados. El constructor GeneratorIncrementalLoadingClass toma como argumentos el recuento total de elementos que se cargarán incrementalmente y una función que genera nuevos elementos que se cargarán según sea necesario. En este caso, la expresión lambda proporcionada crea una nueva instancia de ImportableItemWrapper que encapsula PhotoImportItem e incluye una miniatura para cada elemento. Una vez inicializada la clase de carga incremental, establézcala en la propiedad ItemsSource del control ListView en la interfaz de usuario. Ahora, los elementos multimedia encontrados se cargarán de forma incremental y se mostrarán en la lista.
A continuación, se genera la información de estado de la operación de búsqueda. Una aplicación típica mostrará esta información al usuario en la interfaz de usuario, pero este ejemplo simplemente genera la información en la consola de depuración. Por último, establezca el token de cancelación en null porque la operación se ha completado.
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;
}
Importar elementos multimedia
Antes de implementar la operación de importación, declare un objeto PhotoImportImportItemsResult para almacenar los resultados de la operación de importación. Se usará más adelante para eliminar elementos multimedia que se importaron correctamente desde el origen.
private PhotoImportImportItemsResult importedResult;
Antes de iniciar la operación de importación de medios, inicialice la variable CancellationTokenSource y establezca el valor del control ProgressBar en 0.
Si no hay elementos seleccionados en el control ListView, no hay nada que importar. De lo contrario, inicialice un objeto Progress para proporcionar una devolución de llamada de progreso que actualice el valor del control de barra de progreso. Registre un controlador para el evento ItemImported del PhotoImportFindItemsResult devuelto por la operación de búsqueda. Este evento se generará cada vez que se importe un elemento y, en este ejemplo, genera el nombre de cada archivo importado en la consola de depuración.
Llame a ImportItemsAsync para iniciar la operación de importación. Al igual que con la operación de búsqueda, el método de extensión AsTask se usa para convertir la operación devuelta en una tarea que se puede esperar, que notifica sobre el progreso y que se puede cancelar.
Una vez completada la operación de importación, el estado de la operación se puede obtener del objeto PhotoImportImportItemsResult devuelto por ImportItemsAsync. En este ejemplo se genera la información de estado en la consola de depuración y, por último, se establece el token de cancelación en 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;
}
Eliminar elementos importados
Para eliminar los elementos importados correctamente del origen desde el que se importaron, inicialice primero el token de cancelación para que se pueda cancelar la operación de eliminación y establezca el valor de la barra de progreso en 0. Asegúrese de que el PhotoImportImportImportItemsResult devuelto desde ImportItemsAsync no sea null. Si no es así, vuelva a crear un objeto Progress para proporcionar una devolución de llamada de progreso para la operación de eliminación. Llame a DeleteImportedItemsFromSourceAsync para empezar a eliminar los elementos importados. Use AsTask para convertir el resultado en una tarea que se puede esperar con funcionalidades de progreso y cancelación. Después de esperar, el objeto PhotoImportDeleteImportedItemsFromSourceResult devuelto se puede usar para obtener y mostrar información de estado sobre la operación de eliminación.
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;
}