顯示具有 WPF 標籤的文章。 顯示所有文章
顯示具有 WPF 標籤的文章。 顯示所有文章

2017年7月30日 星期日

【WPF】執行背景工作時,讓畫面不會看起來像卡住


只是想在WPF中使用熟悉的Process Bar,那就別花時間看這篇了
到 youtube去搜尋比較快

這篇不會提到Process Bar,雖然同樣的原理也能用在Process Bar,甚至是自訂的任何進度條。這篇還順便迴避某些書中範例無法回傳string的問題。

原因是WPF畫面的呈現和背景工作是分開的
當程式在忙碌時,畫面會乖乖的一直等背景工作跟他說OK才刷新
(圖片看起來會動,就是因為畫面不斷的刷新喔!)

想像影片前半段,希望能夠「圖片會動、文字會逐項目顯示」,那就需要用到
Async 和 Await
來寫出非同步程式,這樣畫面就不會等到海枯石爛了!
沒聽過「非同步, await, async」的也不用擔心,可以先理解差異,有興趣再找資料吧

●先來一段失敗的程式碼

        private void test_Click(object sender, RoutedEventArgs e)
        {
            //沒使用async & await
            showLog.Text = "轉檔開始(2秒/個)\r\n";
            for (int i = 0; i < 3; i++)
            {
                showLog.Text += "開始第" + (int)(i + 1) + "個轉檔...";
                Thread.Sleep(2000); //模擬轉檔時會停頓的時間
                showLog.Text += "End\r\n";
            }
            showLog.Text += "轉檔全部結束\r\n";
        }
●然後這是成功的程式碼

        private async void test_Click(object sender, RoutedEventArgs e)
        {
            //使用async & await
            showLog.Text = "轉檔開始(2秒/個)\r\n";
            for (int i = 0; i < 3; i++)
            {
                showLog.Text += "開始第" + (int)(i + 1) + "個轉檔...";
                showLog.Text += await ConvertFile("End\r\n");
            }
            showLog.Text += "轉檔全部結束\r\n";
        }

        public Task<string> ConvertFile(string str)
        {
            return Task.Factory.StartNew(() => this._ConvertFile(str));
        }

        public string _ConvertFile(string para1) {
            Thread.Sleep(2000); //模擬轉檔時會停頓的時間
            return para1;
        }

知道怎麼套用await和async,讓畫面不會卡住了嗎?
還是不懂的話,再繼續往下看吧


1. 在可能會執行到需要花時間,但前後想加上其他程式碼的方法(method)的宣告加上「async」,這樣之後要加的await才有作用。只加async卻沒加await,也不會有作用的喔!
before:
private void test_Click(object sender, RoutedEventArgs e){...}
after:
private async void test_Click(object sender, RoutedEventArgs e){...}
2. 宣告一個回傳「數值或字串」的方法 (※不見得要寫成能傳參數,只寫"public string _ConverFile()"也行) 
before:
private async void test_Click(object sender, RoutedEventArgs e){
        ...
        Thread.Sleep(2000); //模擬轉檔時會停頓的時間
        ...
}
after:
private async void test_Click(object sender, RoutedEventArgs e){
        ...
        //Thread.Sleep(2000); //將要背景執行的function搬出這個method
        ...
}
public string _ConvertFile(string para1) {
    Thread.Sleep(2000); //模擬轉檔時會停頓的時間
    return para1;
}
3. 再宣告一個Task,要利用它awaitable的特性,同時利用方便的「Factory.StartNew(...)」來快速的建立與啟動新Task (※也可以不丟參數,直接寫成 "public Task ConverFile()")  
public Task ConvertFile(string str)
{
    return Task.Factory.StartNew(() => "action");
}
4. 將「步驟2」跟「步驟3」結合
public Task ConvertFile(string str)
{
    return Task.Factory.StartNew(() => this._ConvertFile(str+"只是個丟參數的地方")); 
}

5. 將組好的task放在method中使用,記得要加上「await」
private async void test_Click(object sender, RoutedEventArgs e){
        ...
        showLog.Text += await ConvertFile("End\r\n"); //取代原本會拖時間的function
        ...
}

然後就...完成! 可以執行程式,體驗差異了!

【延伸】
有興趣的可以試試看:
1. 只加Async,卻沒用Await,是不是也能成功?
2. 只是把工作丟給Task,卻沒有用Async和Await,是不是也能成功?

如果好奇「await和async」的原理,可以看MSDN的介紹
使用Async 和 Await 設計非同步程式 (C# 和 Visual Basic)
然後找到段落「非同步方法中執行了哪些工作」,會有詳細的執行流程
圖看一次不懂沒關係,不需要刻意去記住,用多就會理解了

2017年3月10日 星期五

【C#】跨視窗傳遞集合

我預計的期望是:
  在「視窗A」點按鈕(Button) → 彈出「視窗B」→ 將「視窗B」更改後的數值回傳

由於「視窗B」的設定不是只有一項,要回傳的話一定是陣列(Array)或集合(Collections)
好處是彼此的變數並不直接干涉,不用因為改了變數名,另一個也要跟著改。

※我用的是WPF,會跟WinForm有一點差距,但是概念差不多。偏基礎的部分,下面介紹就省略了,畢竟需要跨視窗還很在意修飾詞(public, private...)的人,看廢話也覺得煩吧。


視窗A - MainWindow
        private Dictionary<string, string> SetDic = new Dictionary<string, string>();
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            SettingWindow sw = new SettingWindow(SetDic);
            if (sw.ShowDialog() == true) //從設定中取值
                SetDic=sw.ApplySetting();
         }

視窗A的程式碼主要是
第一列:建立Dictionary的集合「SetDic」,作為接收與後續利用
第四列:在事件中用實做視窗B「SettingWindow」,視窗B的建構式看下面
第五、六列:如果在視窗B有點「確定」之類的,那就會執行「SettingWindow」中寫好的方法,將回傳的集合賦值給「SetDic」

視窗B - SettingWindow
    public partial class SettingWindow
    {
        private Dictionary<string, string> _SetDic;
        public SettingWindow(Dictionary<string, string> _SetDic)
        {
            //(不重要,中略)
            _SetDic=SetDic;
        }
        private void Apply_Click(object sender, RoutedEventArgs e)
        {
            //(不重要,中略)
            this.DialogResult = true;
            this.Close();
        }
        internal Dictionary<string, string> ApplySetting() {
            //(不重要,中略)
            return _SetDic;
        }
    }

視窗B就是重頭戲了,分成
第一列:可以看到這個視窗的類別名稱是「SettingWIndow」
第三列:建立一個用來暫存從視窗A丟過來的集合「_SetDic」
第四~八列:由於要接視窗A丟過來的「SetDic」,所以在寫建構式時,要定義參數型別。然後在將接到「SetDic」賦予給「_SetDic」
第九~十四列:這個是點「確定」時,會觸發的事件。重點在第十三列與第十四列的「this.DialogResult = true; this.Close();」,功能分別是「要讓視窗A第五列的IF判斷能成功觸發、關閉視窗B」
第十五~十八列:寫一個會回傳數值的方法,把在視窗B變更過的「_SetDic」回頭賦予給「SetDic」


2016年6月29日 星期三

[WPF]使用App.config來集中管理連線字串(ConnectionString)

因為內部的程式(VB6, winForm)要整個翻新,所以從ASP.net被拉回寫視窗程式
想說既然都要翻新,那乾脆整個框架一起翻新就用了M$支援度比較高的WPF
當然也不意外地要跟資料庫溝通,也就整個從Access換去SQL server

---------好,上面都不是重點, 只是緣由-----------------------------------

既然要跟資料庫結合,一定少不了「連線字串」
因為我都是ADO.net去寫,對於連線字串的依賴相當大
至於每次都要重寫但不易管理的方法就不提了,書本上都有
我這邊是寫個人偏好的集中管理 :D

1. 加入參考「System.Configuration.dll」
2. 在App.config中的<configuration>...</configuration>中加上

<connectionStrings>    
    <add name="DBconnStr" connectionString="要使用連線字串" />
</connectionStrings>

3. 在要設定連線字串的地方用以下進行代入
C#
string connStr = ConfigurationManager.ConnectionStrings["DBconnStr"].ConnectionString;
VB

Dim connStr As Stringction(Configuration.ConnfigurationManager.ConnectionStrings("DBconnStr").ConnectionString

----------------------------
如果直接 new SqlConnection, 可參考下列寫法

SqlConnection connstr = new SqlConnection(ConfigurationManager.ConnectionStrings["AL_connstr"].ConnectionString)