從裝置匯入媒體
本文介紹如何從裝置匯入媒體,包括搜尋可用媒體來源、匯入影片、sidecar 檔案等檔案,以及從來源裝置刪除匯入的檔案。
注意
本文中的程式碼改編自 MediaImport UWP 應用範例。 您可以從通用 Windows 應用程式範例 Git 儲存庫複製或下載此範例,以查看內容中的程式碼或將其用做您自己的應用程式的起點。
建立簡單的媒體匯入 UI
本文中的範例會使用最少的 UI 來啟用核心媒體匯入案例。 若要查看如何為媒體匯入應用程式建立更健全的 UI,請參閱 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 非同步執行的。 宣告一個 CancellationTokenSource 類型的類別成員變數,該變數將用於在使用者按一下取消按鈕時取消正在進行的作業。
CancellationTokenSource cts;
實作取消按鈕的處理常序。 本文後面顯示的範例將在作業開始時初始化 CancellationTokenSource,並在作業完成時將其設為 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 控制項。
- IncrementalLoadingBase 類別 - 實作 IList、ISupportIncrementalLoading 和 INotifyCollectionChanged 以提供基本增量載入行為。
- GeneratorIncrementalLoadingClass 類別 - 提供增量載入基本類別的實作。
- ImportableItemWrapper 類別 - PhotoImportItem 類別的精簡型包裝函式,用於為每個匯入項目的縮圖新增可繫結的 BitmapImage 屬性。
這些類別在 MediaImport sample 範例中提供,無需修改即可新增至您的專案。 將協助程式類別新增至專案後,宣告一個 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;
在匯入來源 ComboBox 的 SelectionChanged 處理常式中,將類別成員變數設定為選定的來源,然後呼叫 FindItems 協助程式方法,該方法將在本文後面介紹。
private void sourcesComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.importSource = (PhotoImportSource)((ComboBoxItem)sourcesComboBox.SelectedItem).Tag;
FindItems();
}
尋找要匯入的項目
新增 PhotoImportSession 和 PhotoImportFindItemsResult 類型的類別成員變數,這些變數將在以下步驟中使用。
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;
在開始媒體匯入作業之前,初始化 CancellationTokenSource 變數並將 ProgressBar 控制項的值設為 0。
如果 ListView 控制項中沒有已選取的項目,則無需匯入任何內容。 否則,初始化 Progress 物件以提供更新進度列控制項的值的進度回呼。 為尋找作業傳回的 PhotoImportFindItemsResult 的 ItemImported 事件註冊一個處理常式。 每當匯入項目時,就會引發此事件,在此範例中,會將每個匯入檔案的名稱輸出至偵錯主控台。
呼叫 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;
}