2018年9月12日 星期三

.Net 非同步處理

初次接觸 Windows Form 程式設計時,因為基本觀念不清楚,常常會鬧笑話,以下就是一個常見的例子:
為了避免某段程序執行時無回應,想用另一個 Thread 來處理並回寫資料。(通常是寫入某個元件,例如 Label 或 TextBox,同理讀取資料也是)

看似簡單,但因為 Windows Form 的 UI Thread 是分開的,往往不是只看到最後結果,就是在不同 Thread 內呼叫彼此 UI 元件跳出異常 (跨 Thread 存取 UI 元件是不允許的)。找了一些資料看,程度太差,也是一直半解 Task、Backgroudworker、BeginInvoke、Delegate...

直到發現這篇,寫得非常好:
https://dotblogs.com.tw/johnny/2014/04/02/144594

其中有一段寫到
... 因為上面那三行程式都在同一個 UI thread 上面, 而 Windows Form 會在 UI thread 上的指令執行完畢並且返回之後, 才會觸發它的 Paint 事件。同樣的程式, 如果 ... 在 Console 程式中這個問題就不會發生, 因為 Console 程式沒有像 Windows Form 那種 Paint 事件。

上面的連結寫得很詳細,不再贅述,簡單紀錄一下自己的用法如下:

private async void btnTest_Click(object sender, EventArgs e)
{
    //
    // do something...
    //

    await doSomethingAsync();
}

private async Task doSomethingAsync()
{
    //
    // do something...
    //

    await Task.Delay(1000); // 暫停一下,看效果用
}

2018年9月11日 星期二

利用 Trigger 紀錄資料表異動 (Log)


原始資料表:[xxx]
異動資料表:[xxx_log]

[xxx_log] 比 [xxx] 至少要多兩個欄位:

  • T1,異動時間,Datetime,預設 getdate() 即可。
  • T2,異動指令,Varchar(10),存放 INSERT UPDATE DELETE 做為識別。

2019/03/25 補充
兩個系統資料表 [INSERTED] & [DELETED]
執行指令 insert 後,[INSERTED] 表內有新增的資料,[DELETED] 表內無資料。
執行指令 delete 後,[INSERTED] 表內無資料,[DELETED] 表內有刪除的資料。
執行指令 update 後,[INSERTED] 表內有更新的資料,[DELETED] 表內有更新的資料。
所以執行指令後,可藉由 select 兩個表判斷為哪種行為,並取得資料。
 
建立一個名為 tr_xxx 的 Trigger

CREATE TRIGGER tr_xxx ON [dbo].[xxx] --// 在資料表 xxx 建立一個名為 tr_xxx 的 Trigger
AFTER INSERT, UPDATE --// 如果需要判斷 DELETE 得加進來
AS

SET NOCOUNT ON; --// 這行是為了正確紀錄批次 UPDATE 或 DELETE,如果不寫,只會 LOG 到一筆異動
DECLARE @INS int, @DEL int --// 取得 INSERTED 與 DELETED 的資料數

--// INSERT:@INS > 0 AND @DEL = 0
--// DELETE:@INS = 0 AND @DEL > 0
--// UPDATE:@INS > 0 AND @DEL > 0

SELECT @INS = COUNT(*) FROM INSERTED
SELECT @DEL = COUNT(*) FROM DELETED

IF @INS > 0 AND @DEL > 0 
BEGIN
     INSERT INTO [xxx_log]  ( T2, C1, C2, C3 ... )  
         SELECT 'UPDATE', C1, C2, C3 ... FROM INSERTED
END

ELSE 
BEGIN
     INSERT INTO [xxx_log]  ( T2, C1, C2, C3 ... )  
         SELECT 'INSERT', C1, C2, C3 ... FROM INSERTED
END

參考資料:
https://stackoverflow.com/questions/9931839/create-a-trigger-that-inserts-values-into-a-new-table-when-a-column-is-updated
https://dotblogs.com.tw/jamesfu/2014/06/20/triggersample

2018年9月10日 星期一

ComboBox 禁用滑鼠滾輪

ComboBox 點選後,焦點會停留在元件上,此時如動到滑鼠滾輪,可能會造成資料變動。

最初的解決方像是想變更焦點,因為 Winform 沒有類似 Blur() 的函式,所以在 SelectedIndexChanged 事件加入:
// 下拉選單選擇後就讓焦點消失,避免USER滾到滑鼠滾輪
this.ActiveControl = null;


但是,如果 USER 這樣操作:點了選單,但並沒有改變選項,又再次點擊選單本身,滾輪依然是有效的。想在 MouseLeave 內處理,但是同上,沒有離開 (Leave) 也是無效。

上述的方式換成讓其他元件 Focus() 也可以,問題在於沒有合適的事件能處理。最後只好新建一個 ComboBox 類別,繼承原生物件,但屏蔽滑鼠滾論事件。
using System.Windows.Forms;

namespace LIB.MyControl
{
    // 定義一個新的 ComboBox,繼承原本元件,但排除滾論事件 (m.Msg == 0x020A)
    public class ComboBoxNoWheel : System.Windows.Forms.ComboBox, IMessageFilter
    {
        public ComboBoxNoWheel()
        {
            Application.AddMessageFilter(this); // 整個 Application 都會有影響
        }

        public bool PreFilterMessage(ref Message m)
        {
            if (m.Msg == 0x020A && this.Focused) // 加入 this.Focused,判斷 ComboBox 為焦點時才作用,避免影響整個 Application
                return true;
            return false;
        }
    }
}


參考資料:
https://blog.csdn.net/jamex/article/details/4257679

2018年9月7日 星期五

取得軟體版本編號 ( 含 ClickOnce )

一般 Winform 自行修改「組件資訊」內的版本編號
// 取得組件資訊內的版本
AssemblyName.GetAssemblyName(System.Windows.Forms.Application.ExecutablePath).Version.ToString();

使用 ClickOnce 發行的版本編號
// 判斷是否為發行版本 (否則 Debug 執行時會出錯)
if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
{
    MessageBox.Show(System.Deployment.Application.ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString());
}