Datagridview 實現樹形列表的效果
(2020年6月9日 :換了個gif,之前的圖感覺不太好,沒表現出什么)

菜單文字都做了改動,防止泄露公司的密碼,畢竟簽了協議的
代碼中有兩個字典類型
// 記錄菜單節點的層級 Dictionary<string, int> _dictIsPMenu; // 是否展開 Dictionary<string, bool> _dictPMenuExpand;
2020年6月11日更新
遞歸做了優化,之前的遞歸層級的保存有問題,最開始的if是一個層級的判斷,如果想要遞歸多少級可以自己修改
private void CreateTree(string pMenuId, int layer) { if (layer > 3) return; int thisLayer = layer; List<M_Menu> childMenuList = allMenuList.Where(a => a.MM_PMenID == pMenuId).ToList(); for (int i = 0; i < childMenuList.Count; i++) { menuList.Add(childMenuList[i]); _dictPMenuExpand.Add(childMenuList[i].MM_MenID, true); _dictIsPMenu.Add(childMenuList[i].MM_MenID, thisLayer); layer++; CreateTree(childMenuList[i].MM_MenID, layer); } }
2020年6月3日更新
使用了遞歸,可以做到無限級,但是有個問題,就是列表展示的不太好,
數據獲取:
其中部分代碼變成下面的樣子了
// 循環頂級節點 for (int i = 0; i < topMenuList.Count; i++) { menuList.Add(topMenuList[i]); _dictIsPMenu.Add(topMenuList[i].MM_MenID, 1); _dictPMenuExpand.Add(topMenuList[i].MM_MenID, true); CreateTree(topMenuList[i].MM_MenID, 2); }
遞歸方法:這個遞歸有個問題就是層級有點問題,但是除了顯示的層級不明確以外,其他都還好
/// <summary> /// 創建樹列表遞歸 /// </summary> /// <param name="pMenuId">父菜單</param> /// <param name="layer">層級</param> private void CreateTree(string pMenuId, int layer) { List<M_Menu> childMenuList = allMenuList.Where(a => a.MM_PMenID == pMenuId).ToList(); for (int i = 0; i < childMenuList.Count; i++) { menuList.Add(childMenuList[i]); _dictPMenuExpand.Add(childMenuList[i].MM_MenID, true); _dictIsPMenu.Add(childMenuList[i].MM_MenID, layer); CreateTree(childMenuList[i].MM_MenID, layer++); } }
單元格繪制代碼做了一點小改動
/// <summary> /// 單元格繪制 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void dgvMenuList_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if (e.RowIndex >= 0 && dgvMenuList.Columns[e.ColumnIndex].DataPropertyName == "MM_MenName") { string menuId = this.dgvMenuList.Rows[e.RowIndex].Cells["MM_MenID"].Value.ToString(); string menuName = this.dgvMenuList.Rows[e.RowIndex].Cells["MM_MenName"].Value.ToString(); string pMenuId = this.dgvMenuList.Rows[e.RowIndex].Cells["MM_PMenID"].Value.ToString(); Image img; Rectangle newRect; if (pMenuId == GuidEmpty) { img = _dictPMenuExpand[menuId] ? Properties.Resources.tree_icon_two : Properties.Resources.tree_icon_one; newRect = new Rectangle( e.CellBounds.X + 3, e.CellBounds.Y + e.CellBounds.Height / 2 - img.Height / 2, img.Width, img.Height); } else { img = Properties.Resources.tree_icon_three; // 如果有子集 var orgList = menuList.Where(s => s.MM_PMenID.Equals(menuId)).ToList(); if (orgList.Count > 0) { img = _dictPMenuExpand[menuId] ? Properties.Resources.tree_icon_two : Properties.Resources.tree_icon_one; } newRect = new Rectangle( e.CellBounds.X + 3 + 10, e.CellBounds.Y + e.CellBounds.Height / 2 - img.Height / 2, img.Width, img.Height); //判斷當前父節點是不是二級父節點 if (_dictIsPMenu[pMenuId] >= 2) { newRect.X += 10 * (_dictIsPMenu[pMenuId] - 1); } } using (Brush gridBrush = new SolidBrush(this.dgvMenuList.GridColor), backColorBrush = new SolidBrush(e.CellStyle.BackColor)) { using (Pen gridLinePen = new Pen(gridBrush, 2)) { // 擦除單元格 e.Graphics.FillRectangle(backColorBrush, e.CellBounds); //繪制背景色:如果選中那就用選中的藍色背景色,否則用默認白色背景色 if (dgvMenuList.Rows[e.RowIndex].Cells[e.ColumnIndex].Selected) { e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(172, 204, 253)), e.CellBounds.X, e.CellBounds.Y, e.CellBounds.Width - 1, e.CellBounds.Height - 1); } else { e.Graphics.FillRectangle(new SolidBrush(dgvMenuList.DefaultCellStyle.BackColor), e.CellBounds.X, e.CellBounds.Y, e.CellBounds.Width - 1, e.CellBounds.Height - 1); } //划線 Point p1 = new Point(e.CellBounds.Left + e.CellBounds.Width, e.CellBounds.Top); Point p2 = new Point(e.CellBounds.Left + e.CellBounds.Width, e.CellBounds.Top + e.CellBounds.Height); Point p3 = new Point(e.CellBounds.Left, e.CellBounds.Top + e.CellBounds.Height); Point[] ps = new Point[] { p1, p2, p3 }; e.Graphics.DrawLines(gridLinePen, ps); //畫圖標 e.Graphics.DrawImage(img, newRect); //畫字符串 e.Graphics.DrawString(menuName.ToString(), this.Font, Brushes.Black, newRect.X + 3 * 2 + img.Width, e.CellBounds.Top + Font.GetHeight(), StringFormat.GenericDefault); e.Handled = true; } } } }
單元格點擊也做了改動,可以一次性展開所有子節點
/// <summary> /// 單元格點擊 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void dgvMenuList_CellClick(object sender, DataGridViewCellEventArgs e) { int rowIndex = e.RowIndex; int colIndex = e.ColumnIndex; // 點中序號和標題列就返回 if (colIndex == 0 || rowIndex < 0) { return; } string menuId = dgvMenuList.Rows[e.RowIndex].Cells["MM_MenID"].Value.ToString(); string pMenuId = dgvMenuList.Rows[e.RowIndex].Cells["MM_PMenID"].Value.ToString(); // 判斷當前菜單ID是否是父級菜單 if (pMenuId == GuidEmpty || (menuList.Where(s => s.MM_PMenID.Equals(menuId)).Count() > 0)) { string menuName = dgvMenuList.Rows[e.RowIndex].Cells["MM_MenName"].Value.ToString(); SetBtnShowHideState(pMenuId); EnergyBtnSetShow(true); gpObjChecks.Controls.Clear(); bool state = !_dictPMenuExpand[menuId]; _dictPMenuExpand[menuId] = state; ExpandMenu(menuId, state); } else { SetBtnShowHideState(pMenuId); AddCheckBoxs(menuId); } }
折疊收起遞歸
/// <summary> /// 展開折疊遞歸 /// </summary> /// <param name="pid">父菜單id</param> /// <param name="state">折疊還是收起</param> private void ExpandMenu(string pid, bool state) { List<M_Menu> childList = menuList.Where(s => s.MM_PMenID.Equals(pid)).ToList(); //篩選傳入的子集合 for (int i = 0; i < childList.Count; i++) { for (int j = 0; j < dgvMenuList.RowCount; j++) { string thisMenuId = dgvMenuList.Rows[j].Cells["MM_MenID"].Value.ToString(); if (thisMenuId == childList[i].MM_MenID) { dgvMenuList.Rows[j].Visible = state; } } ExpandMenu(childList[i].MM_MenID, state); } }
2020年5月30日
首先需要綁定一個對應數據的泛型集合,其他的數據暫時沒試過
Dictionary<string, int> _dictIsPMenu = new Dictionary<string, int>(); Dictionary<string, bool> _dictPMenuExpand = new Dictionary<string, bool>(); List<M_Menu> temp = 某個數據接口 List<M_Menu> topMenuList = temp.Where(a => a.MM_PMenID == GuidEmpty).ToList(); List<M_Menu> menuList = new List<M_Menu>(topMenuList.ToArray());//深拷貝 List<M_Menu> secondMenuList; List<M_Menu> thirdMenuList; int index = 0; // 循環頂級節點 for (int i = 0; i < topMenuList.Count; i++) { // 查詢頂級節點下的子節點 secondMenuList = temp.Where(a => a.MM_PMenID == topMenuList[i].MM_MenID).ToList(); // 向數據字典添加數據 _dictIsPMenu.Add(topMenuList[i].MM_MenID, 1);
// true 為默認展開 _dictPMenuExpand.Add(topMenuList[i].MM_MenID, true); // 循環對應子節點 for (int j = 0; j < secondMenuList.Count; j++) { menuList.Insert(++index, secondMenuList[j]); // 二級子節點 thirdMenuList = temp.Where(a => a.MM_PMenID == secondMenuList[j].MM_MenID).ToList(); if (thirdMenuList.Count > 0) { _dictIsPMenu.Add(secondMenuList[j].MM_MenID, 2); _dictPMenuExpand.Add(secondMenuList[j].MM_MenID, true); for (int k = 0; k < thirdMenuList.Count; k++) { menuList.Insert(++index, thirdMenuList[k]); } } } index++; } // 此數據只會查出二級子菜單的數據 dgvMenuList.DataSource = menuList;
然后在datagridview的CellPoint 中寫入畫圖的代碼
// 判斷是否是需要畫圖的列 if (e.RowIndex >= 0 && dgvMenuList.Columns[e.ColumnIndex].DataPropertyName == "MM_MenName") { string menuId = this.dgvMenuList.Rows[e.RowIndex].Cells["MM_MenID"].Value.ToString(); string menuName = this.dgvMenuList.Rows[e.RowIndex].Cells["MM_MenName"].Value.ToString(); string pMenuId = this.dgvMenuList.Rows[e.RowIndex].Cells["MM_PMenID"].Value.ToString(); Image img; Rectangle newRect; if (_dictIsPMenu.ContainsKey(menuId)) { img = _dictPMenuExpand[menuId] ? Properties.Resources.tree_icon_two : Properties.Resources.tree_icon_one; newRect = new Rectangle( e.CellBounds.X + 3, e.CellBounds.Y + e.CellBounds.Height / 2 - img.Height / 2, img.Width, img.Height); //判斷當前節點是不是二級父節點 if (_dictIsPMenu[menuId] == 2) { newRect.X += 10; } } else { img = Properties.Resources.tree_icon_three; newRect = new Rectangle( e.CellBounds.X + 3 + 10, e.CellBounds.Y + e.CellBounds.Height / 2 - img.Height / 2, img.Width, img.Height); //判斷當前父節點是不是二級父節點 if (_dictIsPMenu[pMenuId] == 2) { newRect.X += 10; } } using (Brush gridBrush = new SolidBrush(this.dgvMenuList.GridColor), backColorBrush = new SolidBrush(e.CellStyle.BackColor)) { using (Pen gridLinePen = new Pen(gridBrush, 2)) { // 擦除單元格 e.Graphics.FillRectangle(backColorBrush, e.CellBounds); //繪制背景色:如果選中那就用設置的背景色,否則用默認白色背景色 if (dgvMenuList.Rows[e.RowIndex].Cells[e.ColumnIndex].Selected) { e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(172, 204, 253)), e.CellBounds.X , e.CellBounds.Y , e.CellBounds.Width - 1, e.CellBounds.Height - 1); } else { e.Graphics.FillRectangle(new SolidBrush(dgvMenuList.DefaultCellStyle.BackColor), e.CellBounds.X, e.CellBounds.Y, e.CellBounds.Width - 1, e.CellBounds.Height - 1); } //划線 Point p1 = new Point(e.CellBounds.Left + e.CellBounds.Width, e.CellBounds.Top); Point p2 = new Point(e.CellBounds.Left + e.CellBounds.Width, e.CellBounds.Top + e.CellBounds.Height); Point p3 = new Point(e.CellBounds.Left, e.CellBounds.Top + e.CellBounds.Height); Point[] ps = new Point[] { p1, p2, p3 }; e.Graphics.DrawLines(gridLinePen, ps); //畫圖標 e.Graphics.DrawImage(img, newRect); //畫字符串 e.Graphics.DrawString(menuName.ToString(), this.Font, Brushes.Black, newRect.X + 3 * 2 + img.Width, e.CellBounds.Top + Font.GetHeight(), StringFormat.GenericDefault); e.Handled = true; } } }
然后實現點擊展開折疊,通過datagridview 的CellClick事件,如果這個事件覺得不好也可用換一個事件,只要能獲取到對應行列就行
private void dgvMenuList_CellClick(object sender, DataGridViewCellEventArgs e) { int rowIndex = e.RowIndex; int colIndex = e.ColumnIndex; // 點中序號和標題列就返回 if (colIndex == 0 || rowIndex < 0) { return; } string menuId = dgvMenuList.Rows[e.RowIndex].Cells["MM_MenID"].Value.ToString(); // 判斷當前菜單ID是否是父級菜單 if (_dictIsPMenu.ContainsKey(menuId)) { string menuName = dgvMenuList.Rows[e.RowIndex].Cells["MM_MenName"].Value.ToString(); gpObjChecks.Controls.Clear(); for (int i = e.RowIndex + 1; i < dgvMenuList.RowCount; i++) { string pMenuId = dgvMenuList.Rows[i].Cells["MM_PMenID"].Value.ToString(); string tempMenuId = dgvMenuList.Rows[i].Cells["MM_MenID"].Value.ToString(); bool state = !_dictPMenuExpand[menuId]; // 頂級父級 if (_dictIsPMenu[menuId] == 1) { //判斷是否到下一個頂級父級菜單了 if (pMenuId == GuidEmpty) { _dictPMenuExpand[menuId] = state; break; } dgvMenuList.Rows[i].Visible = state; _dictPMenuExpand[tempMenuId] = state; // 當前父菜單不是下一個頂級父菜單,但是是下一級父菜單 if (pMenuId != menuId) { dgvMenuList.Rows[i].Visible = _dictPMenuExpand[pMenuId]; } } // 次級父級 else { //判斷是否到下一個次級父級菜單了 if (pMenuId != menuId) { _dictPMenuExpand[menuId] = state; break; } else { dgvMenuList.Rows[i].Visible = state; } } // 最后一行的特殊處理 if (i == dgvMenuList.RowCount - 1) { _dictPMenuExpand[menuId] = state; dgvMenuList.Rows[i].Visible = state; } } } else { string pMenuId = dgvMenuList.Rows[e.RowIndex].Cells["MM_PMenID"].Value.ToString(); M_Menu menu = _menuBLL.GetMenu(pMenuId); } }
這篇就這些
