顯示具有 Windows Form 標籤的文章。 顯示所有文章
顯示具有 Windows Form 標籤的文章。 顯示所有文章

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月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());
}

2018年8月28日 星期二

Winform 軟體自動更新

Winform 的更新方式很多,最方便的莫過於微軟的 ClickOnce。這次的需求無法用這個機制滿足User,只好自己開發,順便紀錄一下。

網路上能找了幾篇來參考:



最後整理出一些重點符合需求也能兼顧效能與彈性,我的版本:


  1. 更新檔直接放共享網路。(不用架設額外 Web 或 FTP,大家也都能存取)
  2. 更新檔封裝為一個壓縮檔,縮短下載(複製)時間。
  3. 放置一個純文字文件紀錄目前版本供本地端讀取比較。
  4. 更新時不需出現確認視窗,有更新就直接執行。(ClickOnce會出現)
  5. 需於主程式啟動判斷更新,並可定時檢查與手動執行。

簡單畫個圖 (包含專案佈署流程):

2018年7月11日 星期三

MDI 架構,子表單存取父層表單元件



一般子表單存取父表單的元件資料,通常會這樣寫。

Form1 (父),內含一個 TextBox1
Form2 (子),想存取 TextBox1

Form1 這樣寫:

Form2 frm = new Form2(this); //設定Owner 
frm.ShowDialog();


Form2 這樣寫:
TextBox txt = ((Form2)Owner).TextBox1; //取得Owner內元件


但如果今天放到MDI架構下會不太一樣,假設今天....


FormMain 設定為 MdiContainer,從裡面啟動 Form1
Form1 (父),內含一個 TextBox1
Form2 (子),想存取 TextBox1

 這時如果同上述寫法,Form2 的 Owner (居然)會抓到 FormMain !?
 Form2 必須改為這樣寫:
TextBox txt = ((Form2)Owner.ActiveMdiChild).TextBox1;

2018年4月8日 星期日

自動送出 Enter

在處理 DataGridView 編輯時,原本使用 GetChanges() 方法,取得 DataTable 中有異動的資料。 再根據其 RowState 決定要 INSERT 或 UPDATE。 但如果使用者編輯後沒有按TAB或ENTER,呼叫 GetChangs() 可能會得到 null 類似這篇 作者是參考這篇解決 測試後依然無解,最後乾脆於儲存時強行 ENTER
SendKeys.SendWait("{ENTER}");
參考這篇,送出字串與按鈕的方法不同。

DataGridView 內使用 DateTimePicker

宣告全域變數
private DateTimePicker cellDateTimePicker;
初始化 Form1() 時加入
this.cellDateTimePicker = new DateTimePicker();
this.cellDateTimePicker.ValueChanged += new EventHandler(cellDateTimePicker_ValueChanged);
//this.cellDateTimePicker.CloseUp += new EventHandler(cellDateTimePicker_CloseUp);
this.cellDateTimePicker.Format = DateTimePickerFormat.Short;
this.cellDateTimePicker.ShowCheckBox = true;
this.cellDateTimePicker.Checked = true;
this.cellDateTimePicker.Visible = false;
this.dgvCertificate.Controls.Add(cellDateTimePicker);
在 ValueChanged 事件加入對應方法 (cellDateTimePicker_ValueChanged),一些屬性設定。 最後將 DateTimePicker 加入 DataGridView 的 Controls 內 撰寫 cellDateTimePicker_ValueChanged
void cellDateTimePicker_ValueChanged(object sender, EventArgs e)
{
    if (cellDateTimePicker.Checked)
    {
        //cellDateTimePicker.Format = DateTimePickerFormat.Short;
        //cellDateTimePicker.CustomFormat = null;
        dgvCurrent.CurrentCell.Value = cellDateTimePicker.Value.ToString("yyyy/MM/dd"); //convert the date as per your format
    }
    else
    {
        //cellDateTimePicker.Checked = false; // 採用checkbox,清除值要取消勾選
        //cellDateTimePicker.Format = DateTimePickerFormat.Custom;
        //cellDateTimePicker.CustomFormat = " ";
        //cellDateTimePicker.forma
        dgvCurrent.CurrentCell.Value = " ";
    }
    cellDateTimePicker.Visible = false;
}
在 DataGridView 的 CellBeginEdit 事件內加入判斷,對應的儲存格才用DateTimePicker取代
private void dgvCertificate_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
    dgvCurrent = dgvCertificate;
    // 如果欄位是證照日期就用之前宣告的 cellDateTimePicker 取代
    // 也可以用  e.ColumnIndex == 1 判斷,用 Name 避免欄位移動又得修改
    if ("certificate_CER_DATE".Equals(dgvCertificate.Columns[e.ColumnIndex].Name))
    {
        Rectangle tempRect = dgvCertificate.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true);
        cellDateTimePicker.Location = tempRect.Location;
        cellDateTimePicker.Width = tempRect.Width;
        string s = dgvCurrent.CurrentCell.Value.ToString();
        cellDateTimePicker.Value = (!"".Equals(s) && !"0001/1/1 上午 12:00:00".Equals(s)) ? DateTime.Parse(s) : DateTime.Today;
        cellDateTimePicker.Checked = (!"".Equals(cellDateTimePicker.Value) && !"0001/1/1 上午 12:00:00".Equals(cellDateTimePicker.Value)) ? true : false;
        cellDateTimePicker.Visible = true; //這行要放在所有設定之後
    }
}
編輯結束後隱藏 DateTimePicker
private void dgvCertificate_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
    if ("certificate_CER_DATE".Equals(dgvCertificate.Columns[e.ColumnIndex].Name))
    {
        cellDateTimePicker.Visible = false;
    }
    //MessageBox.Show(dgvCertificate.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString()+
    //    dgvCertificate.Rows[e.RowIndex].Cells[e.ColumnIndex].FormattedValue.ToString());
}