Windows Store App 與 非同步運算

by Anita 9. 四月 2014 02:06

.NET Magazine國際中文電子雜誌
者:羅慧真
稿:張智凱
文章編號:N140414701
出刊日期:2014/4/9
開發工具: Visual Studio 2012
版本:Windows Runtime 1.0

同步 vs 非同步運算

記得曾在侯文詠的書中看到這樣一段故事,他與女朋友約會(也就是現在的老婆)女朋友讓她等了一個多小時,一般人可能就生氣的走人了,而他仍等到女朋友赴約,女朋友訝異於他不但沒有氣憤的走人,還和顏悅色的等待著她,據說當時是感動不已。而事實上,故事的主人翁當時心中卻是想著「小說正看到精彩處呢! 你再慢半個小時出現剛剛好」。

這段故事證明了在無可避免而無聊的等待時間裡,只要可以做點別的事,「等待」似乎就不那麼令人憤怒。

在程式的運作裡,「同步運算」是讓程式一件一件依序執行,如果某一個程序需要花費比較長的時間,那麼就得要「等」那個程序完成才能進行下一個程序。

clip_image001

圖1 - 同步處理

「非同步運算」則是要改善這個問題,讓需要長時間運算的作業在另一條執行緒執行,讓主程式可以接收其他程式或使用者輸入的需求。以抹去令人難以忍受的等待感。

clip_image004

圖2 - 非同步處理,主程式可以繼續處理其他作業

範例1: 同步處理

以下面這段程式來解說兩者之間的差異。使用者在按下按鈕之後會執行四行程式,這四行中其中一段是呼叫GetData方法,它需要花三秒的時間完成。

程式碼如下:

private void btnSync_Click(object sender, RoutedEventArgs e)

{

    ShowInfoBlock.Text = "";

    ShowInfoBlock.Text += "開始處理資料...\r\n" ;

    ShowInfoBlock.Text += GetData();

    ShowInfoBlock.Text += "資料處理完畢.\r\n";

}

string GetData()

{

    DateTime stopTime = DateTime.Now.AddSeconds (3);

    DateTime endTime;

    do

    {

        endTime = DateTime.Now;

        if (stopTime <= endTime)

                break;

        } while (true );

    return "使用同步運作...\r\n";

}

這個範例刻意讓GetData方法空轉迴圈三秒之後才傳回結果。

執行程式之後,按下「同步處理」按鈕之後,整個應用程式會被咬住三秒,使用者無法做任何操作,包含無法在右邊的文字方塊中輸入任何文字,直到GetData方法回傳結果。

clip_image007

圖3 - 同步處理執行結果

範例2: 非同步處理 – 不用await

同樣的情境,換一個寫法(程式碼稍後介紹)在操作的使用者經驗上會有很大的改善。這是按下「非同步(不用await)」按鈕,執行過程一樣要歷經3秒才會得到結果,但在3秒之內使用者是可以操作右邊的文字方塊。

 clip_image010

圖4- 非同步(不用await)

C# 4.5 非同步語法

在實務的案例中,例如檔案處理、綱路下載、資料統計…等有可能因運算時間過長,而遭遇前面範例所面臨的問題(應用程式被咬住,使用者無法操作其他作業)。

為了解決應用程式需長時間運算而被咬住無法操作的問題,可以使用非同步的作業方式。事實上,非同步的作業方式很早就存在,早期我們可能要建立一個ThreadPool(多執行緒的寫法)或者寫個Delegate(非同步的Patten-APM),然後巴啦吧啦的…處理一大堆複雜的執行緒問題(相信你應該沒興趣聽了),到了C# 4.5將它們改的更加簡單容易。

你只需要學會二個關鍵字 -- await、async。

await

await 是說接下來的這個Method需要awatable,程式執行到這裡會先暫停一下,直到await那行程式執行完才會繼續往下執行。(其實就是告訴編譯器這裡有程式要執行先等一下)

用到await 關鍵字時所在那個方法要加上async修飾詞。

async

定義方法時的修飾詞,是宣告這個方法中建議至少有一個await的工作。

意思是告訴編譯器,這個方法裡會執行一個以上的非同步工作,這個方法在執行時不會鎖住呼叫端(caller)的執行緒。因此可以讓caller的執行緒繼續接收使用者的輸入要求。宣告async的方法區塊裡建議至少要有一個await關鍵字。

被宣告為async的方法在回傳型別上有以下的限制:

  • 回傳型別必須是 -- void、Task或是Task<T>
  • 參數不可為 -- ref或是out

範例2的程式碼解說:非同步使用await

首先改GetData方法為非同步方法,為了與範例1做區隔,將這個方法名稱改成 -- ShowData,程式碼如下:

async void ShowData()

{

        await Task.Run(() =>

        {

             DateTime stopTime = DateTime.Now.AddSeconds(3);

             DateTime endTime;

             do {

                  endTime = DateTime.Now;

                  if (stopTime <= endTime)

                       break;

                  } while (true);

      } );

        ShowInfoBlock.Text += "非同步不使用await";

}

非同步運作其實就是將需要運作較長的工作丟到另一條執行緒(ThreadPool)執行,所以要將原本GetData()中的do…while區段以Lambda運算式或是匿名方法放到Task.Run()的參數中,Task.Run會將要處理的工作執行在ThreadPool中。

同時我們必須等待do…while的程序執行完才能顯示結果(顯示結果在ShowInfoBlock文字區塊中),所以必須在Task.Run方法之前加上await關鍵字。

await Task.Run(() => { //do..while(); });

ShowInfoBlock.Text += "非同步不使用await.\r\n";

然後將ShowData方法宣告成非同步方法,加上async,因為直接顯示結果,所以回傳型別定義為void。

async void ShowData()

{

       //其中至少包含一個await

}

接著是修改「非同步(不用await)」按鈕的程式碼:

private void btnAsyncNonAwait_Click(object sender, RoutedEventArgs e)

{//不使用await

        ShowInfoBlock.Text = "";

        ShowInfoBlock.Text += "開始處理資料...\r\n";

        ShowData();

        ShowInfoBlock.Text += "結束.\r\n";

}

它顯示的順序是「開始處理資料…」à「結束.」à「非同步不使用await(ShowData方法的顯示內容)」。

clip_image011

圖5- 非同步(不用await)

這個範例我們成功的將要長時間運算的工作丟到背景執行緒去執行,達到使用者可以繼續操作使用者介面的目的。但是如果我們將呈現結果的「結束.」當作是ShowData()方法執行完畢的話,這樣的顯示順序就不正確的。

各位看官看到這兒或許已經學會await的用法,或許你會將btnAsyncNonAwait_Click事件這樣改:

private async void btnAsyncNonAwait_Click(object sender, RoutedEventArgs e)

{//不使用await

    ShowInfoBlock.Text = "";

    ShowInfoBlock.Text += "開始處理資料...\r\n";

    await ShowData();

    ShowInfoBlock.Text += "結束.\r\n";

}

在ShowData方法前加上await,然後整個方法定義成async,因為你想讓「結束.」”等到”ShowData方法執行完後再顯示。

private async void btnAsyncNonAwait_Click(object sender, RoutedEventArgs e)

{//不使用await

    ShowInfoBlock.Text = "";

    ShowInfoBlock.Text += "開始處理資料...\r\n";

    await ShowData();

    ShowInfoBlock.Text += "結束.\r\n";

}

只可惜,事情不是這麼簡單! 你會得到這樣的錯誤提示:

clip_image013

圖6- 加上await後的錯誤提示

也就是要被加上await關鍵字的方法必須傳回Task才行。如果我們將ShowData回傳型別從void改成Task:

async Task ShowData()

{

    //程式碼省略

}

程式就可以順利執行,結果如:順序是「開始處理資料…」à「非同步不使用await(ShowData方法的顯示內容)」à「結束.」。

clip_image014

圖7 - 非同步(不用await),加上await

其實這個範例可以就此結束。不過為了呼應原始的GetData()方法是回傳String型別

string GetData() { // 程式碼省略}

,於是筆者又寫了一個「非同步(用await)」的範例。

async Task< string> GetDataAsync() { // 程式碼省略}

範例3: 非同步(用await)

首先修改GetData方法為非同步方法,非同步的方法慣性上名稱會多加個「Async」,所以這個方法名稱改成 -- GetDataAsync,程式碼如下:

async Task< string> GetDataAsync()

{

    await Task.Run(() => {DateTime stopTime = DateTime.Now.AddSeconds(3);

                     DateTime endTime;

                     do

                     {

                               endTime = DateTime.Now;

                               if (stopTime <= endTime)

                                           break;

                               } while (true);

                   });

                 return "Hello Windows 8...\r\n";

}

如同範例2我們使用在Task.Run方法並在之前加上await關鍵字。

await Task.Run(() => { //do..while(); });

並且將GetDataAsync方法定義成async,同時它必須回傳字串,回傳型別必須改成Task<string>。

async Task< string> GetDataAsync()

{

}

接著是修改「非同步(用await)」按鈕的程式碼:呼叫GetDataAsyc方法時加上await關鍵字,並將btnAsync_Click宣告為async方法。

private async void btnAsync_Click(object sender, RoutedEventArgs e)

{

        ShowInfoBlock.Text = "";

        ShowInfoBlock.Text += "開始處理資料...\r\n";

        ShowInfoBlock.Text += await GetDataAsync();

        ShowInfoBlock.Text += "資料處理完畢.\r\n";

}

執行結果如下:

clip_image016

圖8 - 非同步(用await)的執行結果

進入Windows Store Apps

為什麼學習Windows Store Apps程式開發一定先學非同步語法呢?

因為Windows Store Apps是一種使用者端的使用者介面軟體,隨時都有可能遇上向網路或是檔案系統要求資源的需求,這都免不了需要長時等候,於是為了讓開發人員寫出一個好的使用者經驗,許多指令天生自然的就是非同步語法了。

訊息對話方塊

在Windows 類型的應用程式中常用到對話盒,熟Visual Basic 6的就會說它是 -- MsgBox,熟.NET Windows Forms的話說它是 -- MessageBox,在Windows 8 RunTime的API裡類似功能的類別是MessageDialog(命名空間 -- Windows.UI.Popups)。

不過它的長像有一些些的不同,以前是Windows Form,所以會有個視窗框框,Windows Store Apps希望應用程式的畫面不要出現多餘的框框,所以不會有視窗外框,而是以長方塊出現在畫面的水平中央位置,它的樣子像這樣:

clip_image018

圖9- 訊息對話方塊會顯示於螢幕的水平中央位置

它有幾個區域,包含:訊息標題、訊息內容,命令列。

clip_image020

圖10 - MessageDialog 的訊息區域

怎麼使用它呢?首先必須建立MessageDialog物件,然後使用建構函式的參數指定訊息標題及內容,接著呼叫ShowAsync方法既可。

var msg = new MessageDialog(

         "今天是101年07月11日, City Cafe' 半價", "優惠通告");

var result = await msg.ShowAsync();

ResultBlock.Text = "使用者按下關閉按鈕, 代表看到訊息了";

程式執行到ShowAsync時會暫停等待使用者的回應,直到使用者按下任何一個命令按鈕才會執行下一行。

你可以在這段簡短的範例程式中,看到非同步語法,在Windows Store Apps裡就是這樣,三不五時就需要用到非同步語法,這就是筆著為何要優先介紹async的語法了。

定義命令按鈕

在沒有定義任何命令按鈕時,它會出現「關閉」按鈕,上面的例句就是使用預設命令按鈕。

那麼要如何定義自訂命令按鈕呢?這也不難,看你需要幾個命令按鈕就加入幾個UICommand。在MessageDialog類別中有一個Commands屬性,這是一個集合型的屬性,只要使用Add方法加入UICommand既可。

UICommand是一個定義訊息回應按鈕的類別,可以利用建構函示的參數傳遞命令的名稱及要執行的動作。

  • 第一個參數Label,命令按鈕的標籤文字。
  • 第二個參數Action,命令要執行的動作。
  • 第三個參數Id,命令的識別碼。

例如:

var YesCommand = new UICommand("是", (command) => { App.Current.Exit(); }));

例句中的第一個參數是UICommand的Label,"是"會出現在按鈕的文字上。第二個參數則是要執行的動作。例句是用Lambda運算式,這個按鈕被按下時將應用程式將會被關閉。例句是使用兩個參數的多載,所以沒有傳遞第三個參數。

訊息回應

MessageDialog如何處理使用者的回應命令呢?

有兩種方式:

  • 直接指定UICommand的Action參數,如前一個例句。
  • 使用ShowAsync方法的回傳值 -- IAsyncOperation<IUCommand>

IUICommand的Label屬性可取得UICommand的按鈕標籤文字,Id屬性可取得命令按鈕的識別碼。

var result = await msg.ShowAsync();

ResultBlock.Text = "Label:" + result.Label +

"\r\nId:" + result.Id ;

我們用這個範例來解說會更清楚它的完整用法。

範例4: 使用MessageDialog

這個範例在按下「指定命令」按鈕後會出現訊息對話方塊。

clip_image022

圖11 - 訊息對話方塊,使用命令

按下「是」,應用程式便會結束。按下「否」,會出現命令的Label及Id的資訊。

clip_image023

圖12 - 按下「否」時出現的訊息

「指定命令」按鈕的Click事件程式碼如下:

private async void btnCommands_Click(object sender, RoutedEventArgs e)

{

        var msg = new MessageDialog("是否結束程式?", "結束");

        msg.Commands.Add(new UICommand("是", (command) => { App.Current.Exit(); }, "Yes"));

        msg.Commands.Add(new UICommand("否", null, "No"));

        msg.DefaultCommandIndex = 1;

        var result = await msg.ShowAsync();

        ResultBlock.Text = "Label:" + result.Label + "\r\nId:" + result.Id ;

}

程式中的DefaultCommandIndex將會定義按下「Enter」時執行的命令按鈕,這裡指為1是指Commands集合中索引的第1個,也就是「否」那個按鈕。

檔案選取器

檔案選取器在Windows Store Apps來說佔有很重要的角色,Windows Store Apps屬於Sandbox應用程式(中譯為沙箱,是指容許在限制的範圍內存取資源,藉此控管應用程式在安全無虞的情況下仍可存取網路或本地資源),Apps基本上都是來Microsoft的Apps市集,應用程式不可在使用者不知道的情況下存取檔案資料夾。若要存取本地資源必須經由使用者同意,而檔案選取器就是一個讓使用者選取某個檔案或是資料夾的使用者介面,藉由檔案選取器的操作,應用程式才可獲取使用者選取的檔案或資料夾。

clip_image025

圖13 - 透過檔案選取器挑選要可用的檔案

Windows 8 RunTime提供三種檔案選取器,分別是:

  • FileOpenPicker -- 開啟檔案選取器
  • FileSavePicker --存檔選取器
  • FolderPicker -- 資料夾選取器

所屬的命名空間是Windows.Storage.Pickers。

它們常用的屬性如下:

  • ViewMode,檔案或資料夾的檢視方式。有兩個模式,List(清單)及Thumbnail(縮圖)。
  • SuggestedStartLocation,選取器初次啟動時顯示的位置。

型別是PickerLocationId,它的列舉清單包含:DoucmentLibrary、ComputerFolder、Desktop、Downloads、HomeGroup、MusicLibrary、PicturesLibrary以及VideosLibrary。

  • FileTypeFilter,選取器的檔案類型篩選,這個是字串集合的屬性,一定要指定。例如,”.jpg”、”.doc”或是”*”…。
  • CommitButtonText,選取後確認按鈕的文字標籤。

注意!

 SuggestedStartLocation屬性只是初次啟動時顯示的位置,這些選取器很聰明會自動記住上次使用的資料夾位置。

FileOpenPicker

FileOpenPicer,開啟檔案選取器的使用者介面。它提供兩個方法:

  • PickMultipleFileAsync,允許使用者選取多個檔案,回傳StorageFile的陣列。
  • PickSingleFileAsync,只能選取一個檔案,回傳StorageFile物件。

先使用new關鍵字建立物件實體,然後指定SuggestedStartLocation、FileTypeFilter等屬性,其中FileTypeFilter是必要指定項目。以下的範例將會以縮圖的模式呈現,並只篩選.jpg及.png的檔案類型(如圖13)。呼叫PickSingleFileAsync方法之後選取器便會出現。

var openPicker = new FileOpenPicker();

openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;

openPicker.ViewMode = PickerViewMode.Thumbnail;

openPicker.FileTypeFilter.Add(".jpg");

openPicker.FileTypeFilter.Add(".png");

var result = await openPicker.PickSingleFileAsync();

圖14是左上角的檔案下拉選單,可以跳到其他資料夾的位置。

clip_image027

圖14 - 開啟檔案選取器,可以從「檔案」的下拉按鈕選到其他資料夾位置

範例5: 檔案選取器

這個範例將會用到檔案選取器挑選照片,並在選好確認之後顯示於Image控制項上。

clip_image031

圖15 - 選取檔案之後的結果

1. 建立Windows Metro style專案,選擇Blank App(XAML)範本。

2. 開啟MainPage.xaml,從「工具箱」放入兩個Button,分別指定它們的Content為「選取檔案」、「另存新檔」。

3. 放入一個TextBlock,為其命名為imageNameBlock。

4. 放入一個Image,為其命名為image。

5. 進到MainPage.xaml.cs的程式碼視窗,加入命名空間:

using System.Threading.Tasks;

using Windows.Storage;

using Windows.Storage.Pickers;

using Windows.Storage.Streams;

6. 在MainPage類別中宣告一個變數:

StorageFile pickerFile;

7. 在「選取檔案」按鈕的Click事件,撰寫開啟檔案選取器的程式碼,並定義為async方法:

private async void btnOpenFile_Click(object sender, RoutedEventArgs e)

{

        var openPicker = new FileOpenPicker();

        openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;

        openPicker.ViewMode = PickerViewMode.Thumbnail;

         openPicker.FileTypeFilter.Add(".jpg");

         openPicker.FileTypeFilter.Add(".png");

         var result = await openPicker.PickSingleFileAsync();

         if (result != null)

         {

                 pickerFile = result;

                 imageNameBlock.Text = result.Name;

                 using (var stream = await result.OpenAsync(FileAccessMode.Read))

                {

                            stream.Seek(0);

                            BitmapImage bitmap = new BitmapImage();

                            bitmap.SetSource(stream);

                            image.Source = bitmap;

                }

      }

  }

執行完PickSingleFileAsync方法之後便會傳回StorageFile(result變數),透過StorageFile的OpenAsync方法讀取選取的檔案內容。然後將取得的stream設為BitmapImage物件的來源,再指定為image物件的Source便可完成。

8. 執行結果如圖15。

FileSavePicker

FileSavePicker,存檔檔案選取器的UI介面。它的檔案類型篩選的屬性名稱有些不同是FileTypeChoices,其他的屬性說明如下:

  • DefaultFileExtension,預設副檔名。
  • SuggestedFileName,建議的檔名,string型別。
  • SuggestedSaveFile,建議的檔案,StorageFile型別,與SuggestedFileName同義,只設定其中一個即可。若兩個屬性都沒有設定,則檔案名稱的位置不會出現建議檔名。

clip_image033

圖16 - FileSavePicker的全貌

呼叫存檔選取器的方法是FileSavePicker。

以下這段範例是設定存檔選取器的初始位置為ComputerFolder,建議的存檔檔案是先前開啟StorageFile的檔名,然後選取的檔案類型為.jpg及png。

var savePicker = new FileSavePicker();

savePicker.SuggestedStartLocation = PickerLocationId.ComputerFolder;

savePicker.SuggestedSaveFile = pickerFile;

savePicker.FileTypeChoices.Add("Image Files",

new List<string> { ".jpg", ".png" });

var result = await savePicker.PickSaveFileAsync();

範例6: 存檔選取器

這個範例延續自範例5。

1. 開啟MainPage.xaml.cs的程式碼視窗,在MainPage類別中寫一個非同步的方法 -- SimpleMessage,作為顯示訊息對話方塊。定義兩個參數,第一個是訊息內容,第二個是訊息標題。

async void SimpleMessage(string content, string title)

{

      var dialog = new MessageDialog(content, title);

      await dialog.ShowAsync();

}

2. 寫一個方法做為另存新檔的功能並定義為async方法。接收兩個參數,第一個是開啟的檔案,第二個是要存檔的檔案。讀取openFile的Stream,然後寫到saveFile物件。

private async Task SaveAsAsync(StorageFile openFile, StorageFile saveFile)

{

    // 開啟要存檔的檔案Stream

    using (var outStream = await saveFile.OpenStreamForWriteAsync())

    {

    // 取得原始檔案的Size屬性

    var properties = await openFile.GetBasicPropertiesAsync();

    ulong size = properties.Size;

    byte[] buffer = new byte[size];

    //讀取開啟檔案的Stream

    var inStream = await openFile.OpenStreamForReadAsync();

    //Stream位置移到開始的地方.

    inStream.Seek(0, SeekOrigin.Begin);

    //開始讀取檔案內容.

    await inStream.ReadAsync(buffer, 0, (int)size);

    //寫到要儲存的檔案

    await outStream.WriteAsync(buffer, 0, (int)size);

    SimpleMessage(saveFile.Name + " - 存檔完成.", "訊息");

    }

}

3. 在「另存新檔」按鈕的Click事件處理以下程序,並定義為async方法:

  • 確認目前有開啟的檔案
  • 建立FileSavePicker物件
  • 指定相關屬性之後呼叫PickSaveFileAsync以開啟存檔選取器
  • 完成選檔動作之後,檢查如果與原檔資料夾不相同才處理存檔作業

程式碼如下:

private async void btnSaveAs_Click(object sender, RoutedEventArgs e)

{

    if (pickerFile == null)

    {

        SimpleMessage("未開啟檔案, 無法另存新檔!!", "警告");

    }

    var savePicker = new FileSavePicker();

    savePicker.SuggestedStartLocation = PickerLocationId.ComputerFolder;

    savePicker.SuggestedFileName = pickerFile.Name;

    savePicker.FileTypeChoices.Add("Image Files",    new List<string> { ".jpg", ".png" });

    var result = await savePicker.PickSaveFileAsync();

    if (result != null)

    {

        if (result.Path== pickerFile.Path )

      {

            SimpleMessage("內容相同不需存檔!!", "警告");

    }

        else

        {

                await SaveAsAsync(pickerFile, result);

        }

    }

}

4. 按F5執行程式。按「開啟檔案」,任選一張照片。接著按「另存新檔」。除了可以選取本地資料夾之外,也可以選取SkyDrive的位置,接著畫面會要求登入MSN的帳號。

clip_image035

圖17 - 可以指到SkyDrive的位置

接著會要求登入MSN帳號,成功登入後便可看見SkyDrive的資料夾,若不想使用建議檔名可輸入一個適當的名稱按「存檔」既可。

clip_image037

圖18 - 成功登入之後就可以看到SkyDrive的資料夾,指定好要存檔的名稱按「儲存」

完成儲存動作便會出現訊息方塊。

clip_image039

FolderPicker

FolderPicker,資料夾選取器的UI介面,用法與FileOpenPicker大致相同。

FolderPicker picker = new FolderPicker();

picker.ViewMode = PickerViewMode.Thumbnail;

picker.FileTypeFilter.Add ( ".jpg");

picker.FileTypeFilter.Add ( ".png" );

picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;

var result = await picker.PickSingleFolderAsync();

建立FolderPicker物件實體,指定ViewMode、FilteTypeFilter及SuggestedStartLocation等屬性,然後呼叫PickSingleFolderAsync方法既可。

clip_image042

圖19 - 資料夾選取器

範例7: 資料夾選取器

這個範例會利用FolderPicker選取一個資料夾,然後將選取的資料夾所在的.jpg、.png格式的檔案列表於ListBox,當使用者選取照片檔案時將照片顯示於Image控制項。

clip_image044

圖20 - 執行結果

1. 建立Windows Metro style專案,選擇Blank App(XAML)範本。

2. 開啟MainPage.xaml,從「工具箱」放入Button、ListBox及Image。

3. Button的Content為「目錄選取」。

4. ListBox取名為fileListBox。

5. Image取名為image。

6. 進入程式碼視窗,加入以下命名空間。

using System.IO;

using Windows.Storage;

using Windows.Storage.Pickers;

using Windows.Storage.Search;

7. 在「目錄選取」按鈕的Click事件並定義為async方法,此方法的程序如下:

  • 建立FolderPicker物件實體並定義ViewMode、FileTypeFilter…等相關屬性。
  • 呼叫PickSingleFolderAsync方法開啟選取器畫面。
  • 使用QueryOptions物件指定搜尋選項為.jpg及.png,然後在使用者選定的資料夾(result變數)進行搜尋.jpg、.png圖檔的作業(CreateFileQueryWithOptions方法)。
  • 使用GetFilesAsync方法取得搜尋後的檔案集合(files變數 -- StorageFile集合型別)。
  • 將files的檔案陣列與fileListBox控制項進行資料繫結,並指定顯示在清單上的屬性為Name。

private async void btnFolderPicker_Click(object sender, RoutedEventArgs e)

{

    FolderPicker picker = new FolderPicker();

    picker.ViewMode = PickerViewMode.Thumbnail;

    picker.FileTypeFilter.Add ( ".jpg");

    picker.FileTypeFilter.Add ( ".png" );

    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;

    var result = await picker.PickSingleFolderAsync();

    // 取得使用者選取的資料夾

    if (result != null)

    {

        // 提供類似檔案總管的搜尋功能, 只要 .jpg 及 .png.

        var queryOptions = new QueryOptions(CommonFolderQuery.DefaultQuery);

        queryOptions.FileTypeFilter.Add (".jpg");

        queryOptions.FileTypeFilter.Add (".png");

        var queryResult = result.CreateFileQueryWithOptions ( queryOptions );

        // 取得搜尋後的檔案清單

        var files = await queryResult.GetFilesAsync(); // 顯示於ListBox

        fileListBox.ItemsSource = files;

        fileListBox.DisplayMemberPath = "Name";

    }

}

8. 在fileListBox控制項的SelectionChanged事件處理以下程序,並定義此事件為async方法。

  • 確認fileListBox有選取項目。
  • 從SelectedItem屬性取得StorageFile物件。
  • 使用OpenReadAsync方法開啟選取的檔案串流。
  • 串流位置定位在初始點。
  • 使用BitmapImage物件讀取串流內容為圖檔。
  • 設定為image物件的圖案來源。

private async void fileListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)

{

    if (fileListBox.SelectedIndex<0)

        return ;

    var pickFile = fileListBox.SelectedItem as StorageFile;

    var stream = await pickFile.OpenReadAsync();

    stream.Seek(0);

    BitmapImage img = new BitmapImage();

    img.SetSource(stream);

    image.Source = img;

}

9. 按F5執行應用程式,按下「目錄選取」按鈕,在開啟的FolderPicker選取有.jpg、.png影像檔的資料夾。在FolderPicker按下「選擇此資料夾」、「確定」(參考圖19),回到應用程式的畫面,將會在fileListBox看見選取資料夾所包含的影像檔清單。在fileListBox選取其中一個項目,就可以看到選取影像檔的照片或影像內容了(如圖20)。

總結

為什麼要先介紹非同步語法而不先介紹畫面配置及控制項的設計呢?

從這章的範例中你會發現,只要稍微具備Microsoft Visual Studio以前任何一個版本的使用經驗應該就可以輕鬆設計Windows Store Apps的畫面。但是當你要用以前C#(或Visual Basic)所會的語法開始寫程式存取資源時,會發現”咔啦!”卡住了,檔案也不知如何存取更別說是資料庫等資源,這就是為什麼筆者要從async相關語法下手了。

Tags:

.NET Magazine國際中文電子雜誌 | Visual Studio | 羅慧真Anita Lo | XAML | Windows Store App

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List