Theme NexT works best with JavaScript enabled

ShunNien's Blog

不積跬步,無以致千里;不積小流,無以成江海。

0%

DataGridView 在 Winform 的簡單操作(5)

前言

前篇文章主要是針對資料來源的綁定,學會了那些,那這篇就來個進階的連動

前置作業

首先在介面增加個 comboBox 並將 Name 命名為 cbDes,介面設計如下圖
Design Pic

接著把上次 ComboData 類別變換一下格式,把 Value 變成 int,記得修正因為格變換的錯誤

1
2
3
4
5
6
7
8
public class ComboData {
public ComboData(string text, int value) {
Display = text;
Value = value;
}
public string Display { get; set; }
public int Value { get; set; }
}

連動 ComboBox

接著先來把剛剛新增的 comboBox 增加下拉選單的選項,並讓 Gender 的選單動態變更 Description。先建立一個不同選項的資料對應表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void cbDesBind(dynamic sender, string selVal) {
List<ComboData> listDes = new List<ComboData>();
switch (selVal.Trim()) {
case "Male":
listDes.Add(new ComboData("Handsome", 1));
listDes.Add(new ComboData("Nuttiness", 2));
listDes.Add(new ComboData("Polite", 3));
listDes.Add(new ComboData("Burly", 4));
break;
case "Female":
listDes.Add(new ComboData("Beautiful", 1));
listDes.Add(new ComboData("Sexy", 2));
listDes.Add(new ComboData("Cute", 3));
break;
default:
listDes.Add(new ComboData("Handsome", 1));
listDes.Add(new ComboData("Nuttiness", 2));
listDes.Add(new ComboData("Polite", 3));
listDes.Add(new ComboData("Burly", 4));
listDes.Add(new ComboData("Beautiful", 5));
listDes.Add(new ComboData("Sexy", 6));
listDes.Add(new ComboData("Cute", 7));
break;
}
sender.DataSource = listDes;
}

接著在頁面初始化的時候載入(…表示省略程式碼)

1
2
3
4
5
6
7
8
public Form1() {
InitializeComponent();
...
cbDes.DisplayMember = "Display";
cbDes.ValueMember = "Value";
cbDesBind(cbDes, "Male");
...
}

然後就是要讓選單連動了,在 Gender 的下拉選單觸發 SelectedIndexChanged 事件,然後呼叫剛剛的 cbDesBind

1
2
3
private void cbGender_SelectedIndexChanged(object sender, EventArgs e) {
cbDesBind(cbDes, (sender as ComboBox).Text);
}

連動 DataGridView 中的下拉選單

先把預設的呈現資料增加 Description 這欄位的資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private DataTable sampleData() {
using (DataTable table = new DataTable()) {
...
table.Columns.Add("Des", typeof(int));

// Add rows.
table.Rows.Add("Allen", 1, 0, DateTime.Now, 1);
table.Rows.Add("Kevin", 1, 1, DateTime.Now, 2);
table.Rows.Add("Dean", 1, 0, DateTime.Today, 3);
table.Rows.Add("Jenny", 0, 1, DateTime.Today, 1);
return table;
}
}

private void gvInit() {
...
DataGridViewComboBoxColumn cb = new DataGridViewComboBoxColumn() {
Name = "gvDes",
DataPropertyName = "Des",
HeaderText = "Des",
DisplayMember = "Display",
ValueMember = "Value"
};
cbDesBind(cb, "");
gvSample.Columns.Add(cb);
...
}

這時候發現 DataGridView 的下拉選單會與新增功能的下拉選單連動,這是因為這兩個選單我偷懶使用相同資料來源,而 SelectedIndexChanged 會監看到同樣來源資料的變更,
所以這邊進行一下小調整,建一個新的資料來源

1
2
3
4
5
private void gvInit() {
...
gvGender.DataSource = cbData.Select(q=> q).ToList();
...
}

接著來進行 DataGridView 中的欄位連動,首先針對 DataGridView 增加一個EditingControlShowing事件,這表示當此事件是發生於顯示編輯儲存格的控制項時

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void gvSample_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
// 判斷觸發事件的欄位是 Gender 才進行以下動作
if (((DataGridView)sender).Columns[((DataGridView)sender).CurrentCell.ColumnIndex].Name == "gvGender") {
ComboBox cb = e.Control as ComboBox;
if (cb != null) {
// 增加事件
cb.SelectionChangeCommitted += new EventHandler(cb_SelectedIndexChanged);
}
}
}

private void cb_SelectedIndexChanged(object sender, EventArgs e) {
// 取得現在欄位索引
int columnIndex = gvSample.CurrentCell.ColumnIndex;
// 將控制項轉給 ComboBox
ComboBox cbx = sender as ComboBox;
// 判斷現在選取的 Column 是 Gender
if (gvSample.Columns[columnIndex].Name == "gvGender") {
string selTxt = cbx.Text,
selVal = cbx.SelectedValue.ToString();
// 當選取項目值和顯示文字任一為空,就不進行動作
if (string.IsNullOrEmpty(selTxt) || string.IsNullOrEmpty(selVal)) return;
// 將此列資料的 Des Cell 取出
// 其 DataType 為 DataGridViewCell
// 將其強制轉換為 DataGridViewComboBoxCell
var targetCbx = gvSample.CurrentRow.Cells["gvDes"] as DataGridViewComboBoxCell;
// 綁定資料
cbDesBind(targetCbx, selTxt);
}
}

別忘記把新增的功能增加項目

1
2
3
4
5
private void button1_Click(object sender, EventArgs e) {
...
dr["Des"] = cbDes.SelectedValue;
...
}

現在剩下當範例資料載入的時候沒有去將 DataGridViewComboBox 連動,要完成這功能,只要寫個迴圈去跑 DataGridViewrow 的資料,然後變動更新就好。把以下程式碼放到gvInit()這方法的最後

1
2
3
4
5
6
7
8
9
10
private void gvInit() {
...
// DataGridViewComboBoxCell gvDes dynamic databinding
foreach (var dgvr in gvSample.Rows) {
var targetCell = ((DataGridViewRow)dgvr).Cells["gvDes"] as DataGridViewComboBoxCell;
var genderCell = ((DataGridViewRow)dgvr).Cells["gvGender"] as DataGridViewComboBoxCell;
if (targetCell == null) continue;
cbDesBind(targetCell, genderCell.FormattedValue.ToString());
}
}

現在會發現,畫面好像還是沒有變化,這是因為呼叫gvInit()是在 Form1 初始化的時候,在 *LifeCycle 上,有順序上的問題,所以將gvInit()移動到Form1_Load事件

1
2
3
private void Form1_Load(object sender, EventArgs e) {
gvInit();
}

至於為什麼不移動到 DataBindingComplete 則可以參考這篇,主要是因為會多跑一次,所以才用這種架構。

StackOverflow:Alternative to DataGridView DataBindingComplete Eventlink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void Initialize_DataGridView()
{
// Add dummy data to generate the columns
this.dataGridView_Items.DataContext = new Item[]{ new Item {Id = 5, Value = 6}};

// Make your formatting
foreach (DataGridViewRow row in grdComponents.Rows)
{
row.HeaderCell.Value = row.Cells[0].Value.ToString();
}

grdComponents.Columns[0].Visible = false;

// Reset the dummy data
this.dataGridView_Items.DataContext = null; // Or new Item[]{};
}

...
public MyForm()
{
Initialize();

this.Initialize_DataGridView();
}

最後順便調整一下DataGridView編輯模式,不然每次都要在 DataGridView 點擊兩次才會出現下拉選單;只要在gvInit()中增加

1
gvSample.EditMode = DataGridViewEditMode.EditOnEnter;

下拉選單的防呆設計,增加以下程式碼在cbDesBind方法內,放在設定 DataSource 之前就可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void cbDesBind(dynamic sender, string selVal) {
List<ComboData> listDes = new List<ComboData>();
switch (selVal.Trim()) {
case "Male":
listDes.Add(new ComboData("Select...", 0));
...
break;
case "Female":
listDes.Add(new ComboData("Select...", 0));
...
break;
default:
listDes.Add(new ComboData("Select...", 0));
...
break;
}
if (sender.GetType().Name == "DataGridViewComboBoxCell") {
var cb = (DataGridViewComboBoxCell)sender;
if (!listDes.Any(q => q.Value == (int)cb.Value)) {
cb.Value = 0;
}
}
sender.DataSource = listDes;
}

差不多完成了,但是發現這樣連動綁定,在新增一筆資料,在 Des Column 會是預設的下拉選項;如下圖所示
Error Sample
好吧,看起來只能使用 DataBindingComplete 作法,把gvInit()放到Form1()初始化,把原本的綁定程式碼移動到 DataBindingComplete 事件

1
2
3
4
5
6
7
8
9
10
11
12
private void gvSample_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) {
// DataGridViewComboBoxCell gvDes dynamic databinding
var dgv = sender as DataGridView;
if (dgv.Rows.Count > 0 && dgv.Columns.Count > 5 ) {
foreach (var dgvr in dgv.Rows) {
var targetCell = ((DataGridViewRow)dgvr).Cells["gvDes"] as DataGridViewComboBoxCell;
var genderCell = ((DataGridViewRow)dgvr).Cells["gvGender"] as DataGridViewComboBoxCell;
if (targetCell == null) continue;
cbDesBind(targetCell, genderCell.FormattedValue.ToString());
}
}
}

畫面呈現

這個系列就先到此結束,之後有其他想法再繼續延伸吧。

範例程式

Github 上的 Sample Code

參考資料

歡迎關注我的其它發布渠道