/*********************** * 说明:主界面左菜单树 * 创建人:龚宇超 * 创建日期:2018-04-25 * 修改人: * 修改日期: * 修改备注: * 版本:1.0.0.0 ***********************/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; namespace Lskj.Main.Control { public partial class TreeViewCustom : UserControl { #region 公有属性 /// /// 父节点字段 /// public string ParentField; /// /// 显示节点文本字段 /// public string TextField; /// /// 父节点关联子节点位数 /// public int ParentKeyLength; /// /// 左侧菜单点击回调 /// /// The after. public delegate void ButtonClickEventHandler(Button after); /// /// 左侧菜单事件注册 /// public ButtonClickEventHandler OnButtonClickEvent; /// /// 所有节点点击时都触发回调,默认保持原来的末级节点触发逻辑。 /// public bool RaiseClickEventOnAllNodes; /// /// 默认显示所有节点。 /// public bool ExpandAllNodes; /// /// 是否允许点击节点时展开/折叠子节点。 /// public bool AllowNodeCollapse = true; /// /// 鼠标滑过节点时是否触发点击回调。 /// public bool SwitchOnMouseMove; /// /// 使用旧版左侧菜单文字大小。 /// public bool UseMenuBarTextStyle; /// /// 使用分组左侧菜单专用图片样式。 /// public bool UseGroupTreeImageStyle; #endregion #region 私有属性 /// /// 上次显示节点 /// private string _lastTreeNode; /// /// 上次显示按钮ID /// private string _lastMenuId; /// 数据源 /// private DataTable _dataSource; /// /// 缓存菜单,按照节点缓存 /// private Dictionary> _dicTreeNodes = new Dictionary>(); /// /// 缓存所有按钮 /// private Dictionary _dicButtons = new Dictionary(); /// /// 记录节点自身是否应显示,避免 Control.Visible 受父控件可见性影响。 /// private HashSet _visibleNodeValues = new HashSet(); /// /// 默认展开节点。 /// private string _defaultExpandNodeValue; #endregion #region Api [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam); private const int WM_SETREDRAW = 11; /// /// 说明:停止绘制 /// 创建人 龚宇超 /// 创建日期:2018-04-24 /// 修改人: /// 修改日期: /// 修改备注: /// 版本:1.0 /// /// public static void SuspendDrawing(System.Windows.Forms.Control parent) { SendMessage(parent.Handle, WM_SETREDRAW, false, 0); } /// /// 说明:开始绘制 /// 创建人 龚宇超 /// 创建日期:2018-04-24 /// 修改人: /// 修改日期: /// 修改备注: /// 版本:1.0 /// /// public static void ResumeDrawing(System.Windows.Forms.Control parent) { SendMessage(parent.Handle, WM_SETREDRAW, true, 0); parent.Refresh(); } #endregion public TreeViewCustom() { InitializeComponent(); this.plTreeView.Resize += (s, e) => ReflowTreeNodes(); } protected override void WndProc(ref Message m) { if (m.Msg == 0x0014) return; base.WndProc(ref m); } /// /// 说明:检查是否包含父节点 /// 创建人 龚宇超 /// 创建日期:2018-04-24 /// 修改人: /// 修改日期: /// 修改备注: /// 版本:1.0 /// /// private List getParentNodes(string parent) { string parentKey = parent.Length == ParentKeyLength ? parent : parent.Substring(0, parent.Length - ParentKeyLength); List treeNodeList = new List(); if (_dicTreeNodes == null) _dicTreeNodes = new Dictionary>(); if (_dicTreeNodes.ContainsKey(parentKey)) { treeNodeList = _dicTreeNodes[parentKey]; } else { _dicTreeNodes.Add(parentKey, treeNodeList); } return treeNodeList; } /// /// 说明:加载菜单 /// 创建人 龚宇超 /// 创建日期:2018-04-24 /// 修改人: /// 修改日期: /// 修改备注: /// 版本:1.0 /// public void SetDataSource(DataTable dtTable) { SetDataSource(dtTable, string.Empty); } /// /// 加载菜单并展开默认节点。 /// public void SetDataSource(DataTable dtTable, string defaultExpandNodeValue) { int index = 1; this._dataSource = dtTable; this._lastTreeNode = null; this._lastMenuId = null; this._defaultExpandNodeValue = defaultExpandNodeValue; this._dicTreeNodes.Clear(); this._dicButtons.Clear(); this._visibleNodeValues.Clear(); this.plTreeView.Controls.Clear(); if (_dataSource == null || _dataSource.Rows.Count == 0) return; var leftItems = _dataSource.Rows.Cast().OrderBy(n => n[ParentField] + ""); foreach (DataRow item in leftItems) { string nodeValue = item[ParentField] + ""; int length = nodeValue.Length; TreeNodeCustom treeNode = new TreeNodeCustom(); treeNode.Dock = DockStyle.None; treeNode.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; treeNode.Tag = nodeValue; treeNode.TabIndex = index; if (UseMenuBarTextStyle) { treeNode.Height = 44; } SetNodeDefaultStyle(treeNode.btnTreeNode, GetNodeLevel(nodeValue)); treeNode.btnTreeNode.Name = index + ""; treeNode.btnTreeNode.Text = item[TextField] + ""; treeNode.btnTreeNode.Tag = item; treeNode.btnTreeNode.Click += new EventHandler(btnTreeNode_Click); treeNode.btnTreeNode.MouseMove += new MouseEventHandler(btnTreeNode_MouseMove); treeNode.btnTreeNode.MouseLeave += new EventHandler(btnTreeNode_MouseLeave); plTreeView.Controls.Add(treeNode); _dicButtons.Add(index + "", treeNode.btnTreeNode); List parentNodes = getParentNodes(nodeValue); parentNodes.Add(treeNode); // 隐藏需要放在添加后,否则出现顺序错乱 SetNodeVisibleState(treeNode, ExpandAllNodes || length == ParentKeyLength || IsDefaultExpandedChildNode(nodeValue)); index++; } RefreshNodeStyles(); ReflowTreeNodes(); } /// /// 展开指定节点。 /// public void ExpandNode(string nodeValue) { if (string.IsNullOrEmpty(nodeValue)) return; foreach (TreeNodeCustom node in plTreeView.Controls.OfType()) { string menuKey = node.Tag + ""; if (menuKey.Length <= nodeValue.Length || !menuKey.StartsWith(nodeValue)) continue; SetNodeVisibleState(node, true); } RefreshNodeStyles(); ReflowTreeNodes(); } /// /// 选中指定节点,仅更新样式,不触发业务回调。 /// public void SelectNode(string nodeValue) { if (string.IsNullOrEmpty(nodeValue)) return; TreeNodeCustom treeNode = plTreeView.Controls.OfType().FirstOrDefault(n => (n.Tag + "").Equals(nodeValue)); if (treeNode == null) return; SelectNodeButton(treeNode.btnTreeNode, nodeValue); } private int GetNodeLevel(string nodeValue) { if (ParentKeyLength <= 0 || string.IsNullOrEmpty(nodeValue)) return 1; return Math.Max(1, nodeValue.Length / ParentKeyLength); } private int GetVisualNodeLevel(string nodeValue) { return GetNodeLevel(nodeValue); } private bool IsDefaultExpandedChildNode(string nodeValue) { return !string.IsNullOrEmpty(_defaultExpandNodeValue) && nodeValue.Length > _defaultExpandNodeValue.Length && nodeValue.StartsWith(_defaultExpandNodeValue); } private void SetNodeVisibleState(TreeNodeCustom node, bool visible) { if (node == null) return; string nodeValue = node.Tag + ""; if (visible) { _visibleNodeValues.Add(nodeValue); } else { _visibleNodeValues.Remove(nodeValue); } node.Visible = visible; } private bool GetNodeVisibleState(TreeNodeCustom node) { if (node == null) return false; return _visibleNodeValues.Contains(node.Tag + ""); } private void RefreshNodeStyles() { foreach (TreeNodeCustom node in plTreeView.Controls.OfType()) { string nodeValue = node.Tag + ""; SetNodeDefaultStyle(node.btnTreeNode, GetVisualNodeLevel(nodeValue)); } if (!string.IsNullOrEmpty(_lastMenuId) && _dicButtons.ContainsKey(_lastMenuId)) { Button button = _dicButtons[_lastMenuId]; TreeNodeCustom treeNode = button.Parent as TreeNodeCustom; SetNodeSelectImage(button, GetVisualNodeLevel(treeNode == null ? string.Empty : treeNode.Tag + "")); } } private void ReflowTreeNodes() { if (plTreeView == null) return; int top = 0; int width = Math.Max(0, plTreeView.ClientSize.Width); var parNodes = plTreeView.Controls.OfType().OrderBy(n => n.TabIndex); foreach (TreeNodeCustom node in parNodes) { node.Width = width; node.Left = 0; node.Top = top; if (!GetNodeVisibleState(node)) continue; top += node.Height; } plTreeView.AutoScrollMinSize = new Size(0, top); } private bool HasChildNodes(string nodeValue) { if (string.IsNullOrEmpty(nodeValue) || !_dicTreeNodes.ContainsKey(nodeValue)) return false; return _dicTreeNodes[nodeValue].Any(n => (n.Tag + "").Length > nodeValue.Length); } private bool HasVisibleChildNodes(string nodeValue) { if (string.IsNullOrEmpty(nodeValue) || !_dicTreeNodes.ContainsKey(nodeValue)) return false; return _dicTreeNodes[nodeValue].Any(n => (n.Tag + "").Length > nodeValue.Length && GetNodeVisibleState(n)); } private Image GetGroupTreeDefaultImage(int nodeLevel) { if (nodeLevel <= 1) return Lskj.Main.Properties.Resources.tree_group_l1_default; if (nodeLevel == 2) return Lskj.Main.Properties.Resources.tree_group_l2_default; return Lskj.Main.Properties.Resources.tree_group_l3_default; } private Image GetGroupTreeMoveImage(int nodeLevel) { if (nodeLevel <= 1) return Lskj.Main.Properties.Resources.tree_group_l1_move; if (nodeLevel == 2) return Lskj.Main.Properties.Resources.tree_group_l2_move; return Lskj.Main.Properties.Resources.tree_group_l3_move; } private Image GetGroupTreeSelectImage(int nodeLevel) { if (nodeLevel <= 1) return Lskj.Main.Properties.Resources.tree_group_l1_select; if (nodeLevel == 2) return Lskj.Main.Properties.Resources.tree_group_l2_select; return Lskj.Main.Properties.Resources.tree_group_l3_select; } private void SetNodeDefaultStyle(Button button, int nodeLevel) { if (button == null) return; if (UseGroupTreeImageStyle) { button.BackgroundImage = GetGroupTreeDefaultImage(nodeLevel); button.BackgroundImageLayout = ImageLayout.Stretch; button.Image = null; button.TextImageRelation = TextImageRelation.Overlay; button.TabStop = false; button.FlatAppearance.BorderSize = 0; button.FlatAppearance.MouseDownBackColor = Color.Transparent; button.FlatAppearance.MouseOverBackColor = Color.Transparent; button.Padding = new Padding(34 + Math.Max(0, nodeLevel - 1) * 12, 0, 3, 0); button.Font = new Font("微软雅黑", 12); button.TextAlign = ContentAlignment.MiddleLeft; return; } if (UseMenuBarTextStyle) { button.BackgroundImage = Lskj.Main.Properties.Resources.left_default; button.BackgroundImageLayout = ImageLayout.Stretch; button.Image = null; button.TextImageRelation = TextImageRelation.Overlay; button.Font = new Font("微软雅黑", 10); button.Padding = new Padding(20 + Math.Max(0, nodeLevel - 1) * 20, 3, 3, 3); button.TextAlign = ContentAlignment.MiddleLeft; return; } if (nodeLevel <= 1) { button.BackgroundImage = Lskj.Main.Properties.Resources.node_default; button.Image = null; button.Padding = new Padding(10, 0, 0, 0); button.Font = new Font("微软雅黑", 12); } else if (nodeLevel == 2) { button.BackgroundImage = Lskj.Main.Properties.Resources.node2_default; button.Image = null; button.Padding = new Padding(20, 1, 3, 3); button.Font = new Font("微软雅黑", 11); } else { button.BackgroundImage = Lskj.Main.Properties.Resources.node3_default; button.Image = null; button.Padding = new Padding(30 + Math.Max(0, nodeLevel - 3) * 10, 1, 3, 3); button.Font = new Font("微软雅黑", 10); } } private void SetNodeDefaultImage(Button button, int nodeLevel) { if (button == null) return; if (UseGroupTreeImageStyle) { button.BackgroundImage = GetGroupTreeDefaultImage(nodeLevel); return; } if (UseMenuBarTextStyle) { button.BackgroundImage = Lskj.Main.Properties.Resources.left_default; return; } if (nodeLevel <= 1) { button.BackgroundImage = Lskj.Main.Properties.Resources.node_default; } else if (nodeLevel == 2) { button.BackgroundImage = Lskj.Main.Properties.Resources.node2_default; } else { button.BackgroundImage = Lskj.Main.Properties.Resources.node3_default; } } private void SetNodeSelectImage(Button button, int nodeLevel) { if (button == null) return; if (UseGroupTreeImageStyle) { button.BackgroundImage = GetGroupTreeSelectImage(nodeLevel); return; } if (UseMenuBarTextStyle) { button.BackgroundImage = Lskj.Main.Properties.Resources.left_select; return; } if (nodeLevel <= 1) { button.BackgroundImage = Lskj.Main.Properties.Resources.node_select; } else if (nodeLevel == 2) { button.BackgroundImage = Lskj.Main.Properties.Resources.node2_select; } else { button.BackgroundImage = Lskj.Main.Properties.Resources.node3_select; } } private void SetNodeMoveImage(Button button, int nodeLevel) { if (button == null) return; if (UseGroupTreeImageStyle) { button.BackgroundImage = GetGroupTreeMoveImage(nodeLevel); return; } if (UseMenuBarTextStyle) { button.BackgroundImage = Lskj.Main.Properties.Resources.left_select; return; } if (nodeLevel <= 1) { button.BackgroundImage = Lskj.Main.Properties.Resources.node_select; } else if (nodeLevel == 2) { button.BackgroundImage = Lskj.Main.Properties.Resources.node2_move; } else { button.BackgroundImage = Lskj.Main.Properties.Resources.node3_move; } } private void SelectNodeButton(Button button, string nodeValue) { if (button == null) return; if (!string.IsNullOrEmpty(_lastMenuId) && _lastMenuId != button.Name && _dicButtons.ContainsKey(_lastMenuId)) { Button lastButton = _dicButtons[_lastMenuId]; TreeNodeCustom lastNode = lastButton.Parent as TreeNodeCustom; SetNodeDefaultImage(lastButton, GetVisualNodeLevel(lastNode == null ? string.Empty : lastNode.Tag + "")); } SetNodeSelectImage(button, GetVisualNodeLevel(nodeValue)); _lastMenuId = button.Name; } private void TriggerNodeEvent(Button button, string nodeValue) { SelectNodeButton(button, nodeValue); if (OnButtonClickEvent != null) { this.OnButtonClickEvent(button); } } /// /// 说明:树点击 /// 创建人 龚宇超 /// 创建日期:2018-04-24 /// 修改人: /// 修改日期: /// 修改备注: /// 版本:1.0 /// private void btnTreeNode_Click(object sender, EventArgs e) { try { SuspendDrawing(plTreeView); TreeNodeCustom treeNode = (sender as Button).Parent as TreeNodeCustom; if (treeNode != null) { string nodeValue = treeNode.Tag + ""; if (_lastTreeNode == null) { _lastTreeNode = nodeValue; } int nodeLevel = GetVisualNodeLevel(nodeValue); bool hasChildNodes = HasChildNodes(nodeValue); List treeNodes = new List(); // 展开当前点击节点 if (hasChildNodes && AllowNodeCollapse) { treeNodes = _dicTreeNodes[nodeValue]; foreach (TreeNodeCustom node in treeNodes) { string menuKey = node.Tag + ""; if (menuKey.Length <= nodeValue.Length) continue; SetNodeVisibleState(node, !GetNodeVisibleState(node)); } ReflowTreeNodes(); if (!RaiseClickEventOnAllNodes) { if (HasVisibleChildNodes(nodeValue)) { SetNodeSelectImage(treeNode.btnTreeNode, nodeLevel); } else { SetNodeDefaultImage(treeNode.btnTreeNode, nodeLevel); } } } // 点击根节点隐藏所有子节点 if (AllowNodeCollapse && nodeValue.Length == ParentKeyLength && !HasVisibleChildNodes(nodeValue)) { treeNodes = getChildNodes(nodeValue); foreach (TreeNodeCustom node in treeNodes) { SetNodeVisibleState(node, false); //Application.DoEvents();// 处理当前消息队列中的所有windows消息 } ReflowTreeNodes(); } // 点击末节点 if (RaiseClickEventOnAllNodes || nodeValue.Length == ParentKeyLength * 3) { TriggerNodeEvent(treeNode.btnTreeNode, nodeValue); } _lastTreeNode = nodeValue; } } catch (Exception) { } ResumeDrawing(plTreeView); } /// /// 说明:鼠标移动 /// 创建人 龚宇超 /// 创建日期:2018-04-24 /// 修改人: /// 修改日期: /// 修改备注: /// 版本:1.0 /// private void btnTreeNode_MouseMove(object sender, MouseEventArgs e) { try { Button button = sender as Button; if (button != null) { this.Cursor = Cursors.Hand; TreeNodeCustom treeNode = button.Parent as TreeNodeCustom; string treeNodeTag = treeNode.Tag + ""; int nodeLevel = GetVisualNodeLevel(treeNodeTag); if (SwitchOnMouseMove && button.Name != _lastMenuId) { TriggerNodeEvent(button, treeNodeTag); if (!UseGroupTreeImageStyle) { SetNodeMoveImage(button, nodeLevel); } return; } if (button.Name == _lastMenuId) { return; } if (UseGroupTreeImageStyle) { SetNodeMoveImage(button, nodeLevel); return; } if (nodeLevel == 2) { // 第一级节点 if (HasChildNodes(treeNodeTag)) { button.BackgroundImage = HasVisibleChildNodes(treeNodeTag) ? Lskj.Main.Properties.Resources.node2_select : Lskj.Main.Properties.Resources.node2_move; } else { button.BackgroundImage = Lskj.Main.Properties.Resources.node2_move; } } else { // 末节点 SetNodeMoveImage(button, nodeLevel); } } } catch (Exception ex) { } } /// /// 说明:鼠标离开 /// 创建人 龚宇超 /// 创建日期:2018-04-24 /// 修改人: /// 修改日期: /// 修改备注: /// 版本:1.0 /// private void btnTreeNode_MouseLeave(object sender, EventArgs e) { try { Button button = sender as Button; if (button != null) { this.Cursor = Cursors.Hand; TreeNodeCustom treeNode = button.Parent as TreeNodeCustom; string treeNodeTag = treeNode.Tag + ""; int nodeLevel = GetVisualNodeLevel(treeNodeTag); if (button.Name == _lastMenuId) { return; } if (UseGroupTreeImageStyle) { SetNodeDefaultImage(button, nodeLevel); return; } if (nodeLevel == 2) { // 第一级节点 if (HasChildNodes(treeNodeTag)) { button.BackgroundImage = HasVisibleChildNodes(treeNodeTag) ? Lskj.Main.Properties.Resources.node2_select : Lskj.Main.Properties.Resources.node2_default; } else { button.BackgroundImage = Lskj.Main.Properties.Resources.node2_default; } } else { // 末节点 SetNodeDefaultImage(button, nodeLevel); } } } catch (Exception ex) { } } /// /// 说明:获取子节点 /// 创建人 龚宇超 /// 创建日期:2018-04-24 /// 修改人: /// 修改日期: /// 修改备注: /// 版本:1.0 /// /// private List getChildNodes(string nodeValue) { List treeNodes = new List(); // 获取第3个节点之后的节点 DataRow[] rowChilds = _dataSource.Rows.Cast().Where(x => (x[ParentField] + "").Length == nodeValue.Length * 2 && (x[ParentField] + "").Substring(0, nodeValue.Length) == nodeValue).ToArray(); foreach (DataRow item in rowChilds) { if (_dicTreeNodes.ContainsKey(item[ParentField] + "")) { List nodeChilds = _dicTreeNodes[item[ParentField] + ""]; foreach (TreeNodeCustom node in nodeChilds) { treeNodes.Add(node); } } } return treeNodes; } } }