在 Windows 8 Store Apps 中,檔案選取器不只是單純的對話框,更是一種可擴展的合約機制。透過實作「檔案開啟選取器」與「檔案儲存選取器」提供者,開發者能讓自家 App 成為其他應用程式的檔案來源或儲存目標,打造整合度極高的使用者體驗。本篇將深入解析 FileOpenPickerUI 與 FileSavePickerUI 的核心概念,並透過完整程式碼示範如何從零建立自訂選取器。
當使用者在其他 App 中呼叫 FileOpenPicker 並選擇您的提供者時,系統會啟動您的 App 並傳遞 FileOpenPickerActivatedEventArgs。透過此參數取得 FileOpenPickerUI 物件,即可控制選取器介面:
AddFileResult 指示成功、已存在或不被允許儲存選取器的運作模式不同:使用者指定檔名與位置後按下「儲存」,系統會觸發 TargetFileRequested 事件。您必須在事件處理常式中建立目標檔案(通常在 ApplicationData.Current.LocalFolder),並指派給 args.Request.TargetFile。因涉及非同步 I/O,務必呼叫 GetDeferral() 取得延遲物件,完成後呼叫 Complete()。
在 Package.appxmanifest 的「宣告」頁籤新增「檔案開啟選取器」,支援的檔案類型至少要包含一項(如 .png、.jpg 或 *)。
// App.xaml.cs
protected override void OnFileOpenPickerActivated(FileOpenPickerActivatedEventArgs args)
{
var rootFrame = new Frame();
rootFrame.Navigate(typeof(XamlDemo.Picker.MyOpenPicker), args);
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
// MyOpenPicker.xaml.cs 關鍵片段
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var args = (FileOpenPickerActivatedEventArgs)e.Parameter;
_fileOpenPickerUI = args.FileOpenPickerUI;
_fileOpenPickerUI.Title = "自訂檔案開啟選取器";
}
private async void btnPickLocalFile_Click_1(object sender, RoutedEventArgs e)
{
// 從安裝目錄取得範例圖片
StorageFile file = await Package.Current.InstalledLocation.GetFileAsync(@"Assets\Logo.png");
AddFileResult result = _fileOpenPickerUI.AddFile("myFile", file);
lblMsg.Text = "選取的檔案: " + file.Name + "\nAddFileResult: " + result;
}
private async void btnPickRemoteFile_Click_1(object sender, RoutedEventArgs e)
{
// 建立串流式遠端檔案(副檔名需符合 FileTypeFilter)
Uri uri = new Uri("https://images.cnblogs.com/mvpteam.gif", UriKind.Absolute);
StorageFile file = await StorageFile.CreateStreamedFileFromUriAsync(
"mvp.gif", uri, RandomAccessStreamReference.CreateFromUri(uri));
AddFileResult result = _fileOpenPickerUI.AddFile("myFile", file);
lblMsg.Text = "選取的檔案: " + file.Name + "\nAddFileResult: " + result;
}
在 Package.appxmanifest 新增「檔案儲存選取器」宣告,同樣需指定支援的檔案類型。
// App.xaml.cs
protected override void OnFileSavePickerActivated(FileSavePickerActivatedEventArgs args)
{
var rootFrame = new Frame();
rootFrame.Navigate(typeof(XamlDemo.Picker.MySavePicker), args);
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
// MySavePicker.xaml.cs 關鍵片段
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var args = (FileSavePickerActivatedEventArgs)e.Parameter;
_fileSavePickerUI = args.FileSavePickerUI;
_fileSavePickerUI.Title = "自訂檔案儲存選取器";
_fileSavePickerUI.TargetFileRequested += _fileSavePickerUI_TargetFileRequested;
}
private async void _fileSavePickerUI_TargetFileRequested(FileSavePickerUI sender, TargetFileRequestedEventArgs args)
{
var deferral = args.Request.GetDeferral();
try
{
// 在本地資料夾建立唯一檔名的空白檔案
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(
sender.FileName, CreationCollisionOption.GenerateUniqueName);
args.Request.TargetFile = file;
}
catch (Exception ex)
{
await OutputMessage(ex.ToString());
}
finally
{
deferral.Complete();
}
}
AddFile 傳入的檔案副檔名必須符合呼叫端 FileOpenPicker.FileTypeFilter 設定,否則會被拒絕。OnFileOpenPickerActivated / OnFileSavePickerActivated,並在導航時將 args 傳遞給頁面。CreateStreamedFileFromUriAsync 可避免先下載再上傳,直接串流傳遞給呼叫端。TargetFileRequested 必須使用 GetDeferral() 模式,確保非同步作業完成後再通知系統。