Files
lserp_cs_6.0/插件库/Lskj.Main/Control/TreeViewCustom.cs
T
2026-07-14 18:03:09 +08:00

780 lines
28 KiB
C#

/***********************
* 说明:主界面左菜单树
* 创建人:龚宇超
* 创建日期: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 公有属性
/// <summary>
/// 父节点字段
/// </summary>
public string ParentField;
/// <summary>
/// 显示节点文本字段
/// </summary>
public string TextField;
/// <summary>
/// 父节点关联子节点位数
/// </summary>
public int ParentKeyLength;
/// <summary>
/// 左侧菜单点击回调
/// </summary>
/// <param name="after">The after.</param>
public delegate void ButtonClickEventHandler(Button after);
/// <summary>
/// 左侧菜单事件注册
/// </summary>
public ButtonClickEventHandler OnButtonClickEvent;
/// <summary>
/// 所有节点点击时都触发回调,默认保持原来的末级节点触发逻辑。
/// </summary>
public bool RaiseClickEventOnAllNodes;
/// <summary>
/// 默认显示所有节点。
/// </summary>
public bool ExpandAllNodes;
/// <summary>
/// 是否允许点击节点时展开/折叠子节点。
/// </summary>
public bool AllowNodeCollapse = true;
/// <summary>
/// 鼠标滑过节点时是否触发点击回调。
/// </summary>
public bool SwitchOnMouseMove;
/// <summary>
/// 使用旧版左侧菜单文字大小。
/// </summary>
public bool UseMenuBarTextStyle;
/// <summary>
/// 使用分组左侧菜单专用图片样式。
/// </summary>
public bool UseGroupTreeImageStyle;
#endregion
#region 私有属性
/// <summary>
/// 上次显示节点
/// </summary>
private string _lastTreeNode;
/// <summary>
/// 上次显示按钮ID
/// </summary>
private string _lastMenuId;
/// 数据源
/// </summary>
private DataTable _dataSource;
/// <summary>
/// 缓存菜单,按照节点缓存
/// </summary>
private Dictionary<string, List<TreeNodeCustom>> _dicTreeNodes = new Dictionary<string, List<TreeNodeCustom>>();
/// <summary>
/// 缓存所有按钮
/// </summary>
private Dictionary<string, Button> _dicButtons = new Dictionary<string, Button>();
/// <summary>
/// 记录节点自身是否应显示,避免 Control.Visible 受父控件可见性影响。
/// </summary>
private HashSet<string> _visibleNodeValues = new HashSet<string>();
/// <summary>
/// 默认展开节点。
/// </summary>
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;
/// <summary>
/// <para>说明:停止绘制</para>
/// <para>创建人 龚宇超</para>
/// <para>创建日期:2018-04-24 </para>
/// <para>修改人:</para>
/// <para>修改日期:</para>
/// <para>修改备注:</para>
/// <para>版本:1.0</para>
/// </summary>
/// <param name="parent"></param>
public static void SuspendDrawing(System.Windows.Forms.Control parent)
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
/// <summary>
/// <para>说明:开始绘制</para>
/// <para>创建人 龚宇超</para>
/// <para>创建日期:2018-04-24 </para>
/// <para>修改人:</para>
/// <para>修改日期:</para>
/// <para>修改备注:</para>
/// <para>版本:1.0</para>
/// </summary>
/// <param name="parent"></param>
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);
}
/// <summary>
/// <para>说明:检查是否包含父节点</para>
/// <para>创建人 龚宇超</para>
/// <para>创建日期:2018-04-24 </para>
/// <para>修改人:</para>
/// <para>修改日期:</para>
/// <para>修改备注:</para>
/// <para>版本:1.0</para>
/// </summary>
/// <returns></returns>
private List<TreeNodeCustom> getParentNodes(string parent)
{
string parentKey = parent.Length == ParentKeyLength ? parent : parent.Substring(0, parent.Length - ParentKeyLength);
List<TreeNodeCustom> treeNodeList = new List<TreeNodeCustom>();
if (_dicTreeNodes == null)
_dicTreeNodes = new Dictionary<string, List<TreeNodeCustom>>();
if (_dicTreeNodes.ContainsKey(parentKey))
{
treeNodeList = _dicTreeNodes[parentKey];
}
else
{
_dicTreeNodes.Add(parentKey, treeNodeList);
}
return treeNodeList;
}
/// <summary>
/// <para>说明:加载菜单</para>
/// <para>创建人 龚宇超</para>
/// <para>创建日期:2018-04-24 </para>
/// <para>修改人:</para>
/// <para>修改日期:</para>
/// <para>修改备注:</para>
/// <para>版本:1.0</para>
/// </summary>
public void SetDataSource(DataTable dtTable)
{
SetDataSource(dtTable, string.Empty);
}
/// <summary>
/// 加载菜单并展开默认节点。
/// </summary>
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<DataRow>().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<TreeNodeCustom> parentNodes = getParentNodes(nodeValue);
parentNodes.Add(treeNode);
// 隐藏需要放在添加后,否则出现顺序错乱
SetNodeVisibleState(treeNode, ExpandAllNodes || length == ParentKeyLength || IsDefaultExpandedChildNode(nodeValue));
index++;
}
RefreshNodeStyles();
ReflowTreeNodes();
}
/// <summary>
/// 展开指定节点。
/// </summary>
public void ExpandNode(string nodeValue)
{
if (string.IsNullOrEmpty(nodeValue)) return;
foreach (TreeNodeCustom node in plTreeView.Controls.OfType<TreeNodeCustom>())
{
string menuKey = node.Tag + "";
if (menuKey.Length <= nodeValue.Length || !menuKey.StartsWith(nodeValue)) continue;
SetNodeVisibleState(node, true);
}
RefreshNodeStyles();
ReflowTreeNodes();
}
/// <summary>
/// 选中指定节点,仅更新样式,不触发业务回调。
/// </summary>
public void SelectNode(string nodeValue)
{
if (string.IsNullOrEmpty(nodeValue)) return;
TreeNodeCustom treeNode = plTreeView.Controls.OfType<TreeNodeCustom>().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<TreeNodeCustom>())
{
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<TreeNodeCustom>().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);
}
}
/// <summary>
/// <para>说明:树点击</para>
/// <para>创建人 龚宇超</para>
/// <para>创建日期:2018-04-24 </para>
/// <para>修改人:</para>
/// <para>修改日期:</para>
/// <para>修改备注:</para>
/// <para>版本:1.0</para>
/// </summary>
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<TreeNodeCustom> treeNodes = new List<TreeNodeCustom>();
// 展开当前点击节点
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);
}
/// <summary>
/// <para>说明:鼠标移动</para>
/// <para>创建人 龚宇超</para>
/// <para>创建日期:2018-04-24 </para>
/// <para>修改人:</para>
/// <para>修改日期:</para>
/// <para>修改备注:</para>
/// <para>版本:1.0</para>
/// </summary>
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)
{
}
}
/// <summary>
/// <para>说明:鼠标离开</para>
/// <para>创建人 龚宇超</para>
/// <para>创建日期:2018-04-24 </para>
/// <para>修改人:</para>
/// <para>修改日期:</para>
/// <para>修改备注:</para>
/// <para>版本:1.0</para>
/// </summary>
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)
{
}
}
/// <summary>
/// <para>说明:获取子节点</para>
/// <para>创建人 龚宇超</para>
/// <para>创建日期:2018-04-24 </para>
/// <para>修改人:</para>
/// <para>修改日期:</para>
/// <para>修改备注:</para>
/// <para>版本:1.0</para>
/// </summary>
/// <returns></returns>
private List<TreeNodeCustom> getChildNodes(string nodeValue)
{
List<TreeNodeCustom> treeNodes = new List<TreeNodeCustom>();
// 获取第3个节点之后的节点
DataRow[] rowChilds = _dataSource.Rows.Cast<DataRow>().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<TreeNodeCustom> nodeChilds = _dicTreeNodes[item[ParentField] + ""];
foreach (TreeNodeCustom node in nodeChilds)
{
treeNodes.Add(node);
}
}
}
return treeNodes;
}
}
}