[Windows Mobile .NET CF] 為了順暢的 UI , 要採用背景執行 (Multi-Thread) - Day5

[Windows Mobile .NET CF] 為了順暢的 UI , 要採用背景執行 (Multi-Thread) - Day5

當使用者按下一個 MenuItem , 我們的程式開始上傳照片, 登入 plurk, 發出 plurk message…
但真正執行的時候, 使用者最大的感覺就是 “當住了” … 一直到 posted 的 MessageBox 出現才知道
原來是手機正在辛苦執行許多的工作啊…

對現代上班族來說, 辛苦執行不為人知的工作是不行的!!! (我在生氣甚麼啊!)
應該要隨時讓老闆知道 “你正在很辛苦的工作中! “
所以, 我們的程式也是需要隨時回報給使用者知道, “我正在很辛苦的幫你做事中…”

那麼, 一邊執行, 一邊回報 UI , 就成為稍微進階的程式一定要有的 UI 囉!

廢話講完了, 簡單就是靠 Thread 這個 class 解決就是了!
但是 !!! Multi-thread 一但牽扯到 UI , 就一定要使用 Invoke 這個函式來跟 UI 同步.
簡單來說, 功能性的 code 可以放在 thread 中執行, 但要更新 UI control 時,
請在 Invoke 函式當中執行. 這樣就 ok 了!

首先在新的 CameraNow 的 Form 上面加個 Progress Bar  用來顯示進度, 在加一個 label 顯示訊息文字:
(在最下方的那個藍色長條就是 progress bar…, 疊在上面的就是 label )

image

把 progress bar 跟 label 的 visible 屬性設為 false, 這樣一開始就不會顯示出來.

然後把丟照片跟丟訊息的程式改成如下:



private void SetMessage(string msg)
{
    this.Invoke(new invokefunc(() =>
    {
        progressBar1.Visible = false;
        lbMsg.Text = msg;
        lbMsg.Visible = true;
    }));
}

private void menuItem6_Click(object sender, EventArgs e)
{
    string postmsg = textBox1.Text;

    Thread executeThread = new Thread(() =>
    {
        try
        {
            if (string.IsNullOrEmpty(flickr.AuthToken) == true)
            {
                SetMessage("尚未取得 Flickr 授權");
                return;
            }

            if (String.IsNullOrEmpty(settings.PlurkAccount) == true)
            {
                SetMessage("請設定 Plurk 帳號");
                return;
            }

            this.Invoke(new invokefunc(() =>
            {
                progressBar1.Value = 0;
                progressBar1.Visible = true;
            }));


            int filesize = Convert.ToInt32(new FileInfo(imagefilename).Length);
            flickr.OnUploadProgress += new Flickr.UploadProgressHandler((obj, evt) =>
            {
                double percent = ((double)evt.Bytes)/((double)filesize);
                int value = (int)(percent*80);
                this.Invoke(new invokefunc(() =>
                {
                    progressBar1.Value = value;
                }));
            });

            string photoid = flickr.UploadPicture(imagefilename, "CameraNow", "Photo by CameraNow at " + DateTime.Now.ToString());
            var photoinfo = flickr.PhotosGetInfo(photoid);

            PlurkApi.PlurkApi plurk = new PlurkApi.PlurkApi();
            if (plurk.Login(settings.PlurkAccount, settings.PlurkPassword))
            {
                this.Invoke(new invokefunc(() =>{progressBar1.Value = 90;}));

                if (plurk.addMessage("tr_ch", string.Empty, photoinfo.WebUrl + " " + postmsg, true, string.Empty))
                {
                    SetMessage("已成功發佈訊息.");
                    return;
                }
            }
            SetMessage("發佈訊息失敗.");
        }
        catch (Exception ex)
        {
            this.Invoke(new invokefunc(() =>
            {
                MessageBox.Show(ex.ToString());
            }));
        }
    });

    executeThread.IsBackground = true;
    executeThread.Start();
}

你可以注意到, 透過 C# 3.0 的 anonymous method 功能, 要運用 Invoke, Thread 並不困難.
在 VS2005 的時代, 要 Invoke, Thread 都要另外再寫 function, 傳參數也很麻煩…
但是現在透過 anonymous method, 就可以共用參數囉 (當然如果有同時存取時, 還是要注意 lock 的問題)
上面的 code 最重要的示範就是, “凡是所有跟 UI Control 有關的, 通通在 Invoke 內使用!”
還有, .NET thread 內如果發生 exception, 很容易導致程式結束.
所以 thread 內最好要捕抓 exception.

最後, 我實測的結果發現, 那個 Flickr API 的 call back 幾乎只呼叫一次 XD
所以整個 progress bar 運作沒有我想像中的漂亮啊.