實現 DataGridView 中的「日期選取器 (DateTimePicker)」
在 Window Form 中,DataGridView 控制項是一個還算蠻好用的工具,可以快速的顯示井然有序的資料。欄位可以使用按鍵、核取方塊、下拉選單、圖檔、超連結與文字方塊,足以應付很多的狀態了。
但總還是有些不足的地方,比如說某一個欄位是日期的格式,一般來說都是使用文字方塊來處理,若只是單純顯示那還沒問題,要是可以讓使用者編輯的時後,就有點麻煩了,要是使用者輸入了不正確的日期,有可能造成無法預期的錯誤產生。所以最好的方式就是透過 DateTimePicker 來讓使用者選取日期,如此一來使用者就不會輸入不正確的日期了。
但問題來了,在 DataGridView 的 Column 型態中並沒有 DateTimePicker 這個類型,這時可以想到的解決方式就是透過 CellValueChanged 的事件來判斷,但也要當 Cell 失去焦點後才會觸發。而且必需要寫一堆的判斷式來處理,比如輸入的格式有 yyyy/mm/dd, yyyy-mm-dd, yyyymmdd 或是月和日有1碼的情況,如:1/9/2014,這樣算是2014年1月9日,還是2014年9月1日呢。
因此基於種種無法判定的因素,只有朝 DateTimePicker 來處理才是又快又適當的方式。
以下就以範例來解說:
首先在 Form 上新增一個 DateGridView,在此就不做資料庫的連結,僅以 DataGridView 的新增資料功能來處裡。DataGridView 中安置了 7 個 Column,其中 Column1 與 Column6 是唯讀不允許變更的,Column2 是下拉選單填值,Column4 為一般的文字輸入欄位,Column5 為勾選方塊,Column3 與 Column7 則為日期欄位,但這邊須設定為文字輸入欄位。
而 Column3 的日期顯示格式為 yyyy.MM.dd,Column7 的日期顯示格式為 yyyy-MM-dd,這會設定在另一個地方,並不是去設定 Column 中 DefaultCellStyle 的 Format 喔!
再來將 DataGridView 的 EditMode 變更為 EditProgrammatically。改由程式控制是否要進編輯模式,當然這個部份也可以使用預設值,但是如果不允許修改的欄位,就必需要設定為 ReadOnly 囉。
執行狀態如下圖:
這時後點選 Column1 或 Column6 都不會進入編輯狀態。點選了其他的欄位時,就會開啟編輯模式,這是透過 Cell Click 事件來處理的。
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
if ((e.RowIndex < 0) || (e.ColumnIndex < 0)) return;
switch (dataGridView1.Columns[e.ColumnIndex].Name)
{
case "Column2":
case "Column3":
case "Column4":
case "Column5":
case "Column7":
dataGridView1.BeginEdit(true);
break;
default:
dataGridView1.EndEdit();
break;
}
}
當點選到行首或列首時,不做任何處理直接跳出。然後就依照觸發的 ColumnIndex 來判斷是在哪一個 Column 下,如果是 2,3,4,5,7 就啟用編輯,其他的結束編輯。到這邊都還是平淡無奇,還看不出來 DataTimePicker 在哪產生的。
接下來就是重頭戲了,透過 EditingControlShowing 事件,在 DataGridView 進入編輯模式要產生編輯的欄位後,將 DataTimePicker 以五鬼搬運加入到控制項中,並且將原本的文字方塊控制項給隱藏起來。
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (dataGridView1.CurrentCell.ColumnIndex < 0) return;
Control parentCTL = e.Control.Parent;
if ((dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Column3") || (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Column7"))
{
DateTimePicker dtPicker = new DateTimePicker();
dtPicker.Name = "dateTimePicker1";
dtPicker.Size = dataGridView1.CurrentCell.Size;
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Column3")
{
dtPicker.CustomFormat = "yyyy.MM.dd";
}
else if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Column7")
{
dtPicker.CustomFormat = "yyyy-MM-dd";
}
dtPicker.Format = DateTimePickerFormat.Custom;
dtPicker.Location = new Point(e.Control.Location.X - e.Control.Margin.Left < 0 ? 0 :e.Control.Location.X - e.Control.Margin.Left, e.Control.Location.Y - e.Control.Margin.Top < 0 ? 0:e.Control.Location.Y - e.Control.Margin.Top);
if (e.Control.Text != "")
{
dtPicker.Value = DateTime.ParseExact(e.Control.Text, dtPicker.CustomFormat, null);
}
e.Control.Visible = false;
foreach (Control tmpCTL in parentCTL.Controls)
{
if (tmpCTL.Name == dtPicker.Name) parentCTL.Controls.Remove(tmpCTL);
}
parentCTL.Controls.Add(dtPicker);
dtPicker.ValueChanged += new EventHandler(dateTimePicker1_ValueChanged);
}
else
{
foreach (Control tmpCTL in parentCTL.Controls)
{
if (tmpCTL.Name == "dateTimePicker1") parentCTL.Controls.Remove(tmpCTL);
}
}
}
首先取出父系控制項到 parentCTL,接下來判斷目前的 Column 是否在所指定的 3 與 7 中。然後新增一個 DateTimePicker 物件為 dtPicker 並且命名為 dateTimePicker1,然後設定大小與目前的欄位大小一樣。
至於格式設定則依各人喜好,在這個例子中 Column3 是 yyyy.MM.dd、Column7 是 yyyy-MM-dd。然後指訂 DateTimePicker 的 Format 為自定模式(Custom)。
再來就調整 DateTimePicker 的顯示位置,透過原控制項來取得設定的位置。
然後將原資料代回到 DateTimePicker 中,總要記得設定前的資料吧。
接下來就是在父系控制項中尋找是否有同名的子控制項,(當然如果要再多判斷型別也是可以的!) 找到後將已存在的子控制項刪除。然後再將新的 DateTimePicker 控制項加入到父系控制項中。並且指定 ValueChanged 的事件。
如果目前點選的 Column 並沒有要做這樣客製行為,那麼就在父系控制項中將存在的 DateTimePicker 控制項全部刪除。
ValueChanged 事件
private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
{
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Column3")
{
dataGridView1.CurrentCell.Value = ((DateTimePicker)sender).Value.ToString("yyyy.MM.dd");
}
else if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Column7")
{
dataGridView1.CurrentCell.Value = ((DateTimePicker)sender).Value.ToString("yyyy-MM-dd");
}
}
這個地方就只是判斷是哪一個欄位,要以什麼樣的格式傳回到原始觸發的欄位中。
執行畫面
如此一來,省去了一堆的判斷,都委由 DateTimePicker 來處理,同時還可以限定選取的最小日期與最大日期,這樣不是很好嗎!
程式是運氣與直覺堆砌而成的奇蹟。
若不具備這兩者,不可能以這樣的工時實現這樣的規格。
修改規格是對奇蹟吐槽的褻瀆行為。
而追加修改則是相信奇蹟還會重現的魯莽行動。