C#将【程序集引用-依赖关系】展示到NetronLight图表中
修改NetronLight.SimpleRectangle的Paint事件 将文本居中。
cs
public override void Paint(System.Drawing.Graphics g)
{
g.FillRectangle(shapeBrush,rectangle);
if(hovered || isSelected)
g.DrawRectangle(new Pen(Color.Red,2F),rectangle);
else
g.DrawRectangle(blackPen,rectangle);
for(int k=0;k<connectors.Count;k++)
{
connectors[k].Paint(g);
}
//well, a lot should be said here like
//the fact that one should measure the text before drawing it,
//resize the width and height if the text if bigger than the rectangle,
//alignment can be set and changes the drawing as well...
//here we keep it really simple:
if (text != string.Empty)
{
//修改文本居中对齐
SizeF size = g.MeasureString(text, font, 500, StringFormat.GenericTypographic);
float offsetX = rectangle.Width > size.Width ? (rectangle.Width - size.Width) / 2 : 0;
float offsetY = rectangle.Height > size.Height ? (rectangle.Height - size.Height) / 2 : 0;
g.DrawString(text, font, Brushes.Black, rectangle.X + offsetX, rectangle.Y + offsetY);
//g.DrawString(text, font, Brushes.Black, rectangle.X + 10, rectangle.Y + 10);
}
}
新建窗体FormShowAssemblyDependent
窗体设计器代码如下:
FormShowAssemblyDependent.Designer.cs文件
cs
namespace ShowTreeNodeToDiagram
{
partial class FormShowAssemblyDependent
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btnShowDiagram = new System.Windows.Forms.Button();
this.graphControl1 = new NetronLight.GraphControl();
this.tvAssembly = new System.Windows.Forms.TreeView();
this.SuspendLayout();
//
// btnShowDiagram
//
this.btnShowDiagram.Font = new System.Drawing.Font("宋体", 12F);
this.btnShowDiagram.Location = new System.Drawing.Point(39, 7);
this.btnShowDiagram.Name = "btnShowDiagram";
this.btnShowDiagram.Size = new System.Drawing.Size(158, 26);
this.btnShowDiagram.TabIndex = 5;
this.btnShowDiagram.Text = "展示树节点到图表";
this.btnShowDiagram.UseVisualStyleBackColor = true;
this.btnShowDiagram.Click += new System.EventHandler(this.btnShowDiagram_Click);
//
// graphControl1
//
this.graphControl1.Location = new System.Drawing.Point(290, 5);
this.graphControl1.Name = "graphControl1";
this.graphControl1.ShowGrid = true;
this.graphControl1.Size = new System.Drawing.Size(1421, 600);
this.graphControl1.TabIndex = 4;
this.graphControl1.Text = "graphControl1";
//
// tvAssembly
//
this.tvAssembly.Location = new System.Drawing.Point(4, 42);
this.tvAssembly.Name = "tvAssembly";
this.tvAssembly.Size = new System.Drawing.Size(280, 563);
this.tvAssembly.TabIndex = 3;
//
// FormShowAssemblyDependent
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1723, 611);
this.Controls.Add(this.btnShowDiagram);
this.Controls.Add(this.graphControl1);
this.Controls.Add(this.tvAssembly);
this.Name = "FormShowAssemblyDependent";
this.Text = "显示程序集依赖";
this.Load += new System.EventHandler(this.FormShowAssemblyDependent_Load);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button btnShowDiagram;
private NetronLight.GraphControl graphControl1;
private System.Windows.Forms.TreeView tvAssembly;
}
}
FormShowAssemblyDependent实现代码如下
FormShowAssemblyDependent.cs文件
cs
using NetronLight;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ShowTreeNodeToDiagram
{
public partial class FormShowAssemblyDependent : Form
{
public FormShowAssemblyDependent()
{
InitializeComponent();
}
private void FormShowAssemblyDependent_Load(object sender, EventArgs e)
{
//string fileName = @"D:\Debug\XX.exe";
//Assembly assembly = Assembly.LoadFile(fileName);
Assembly assembly = Assembly.GetEntryAssembly();
MessageBox.Show(assembly.GetName().Name+"\n处理器结构:"+ assembly.GetName().ProcessorArchitecture+"\n类型:"+ assembly.GetType()+"\n程序集位置:"+ assembly.CodeBase);
TreeNode rootNode = new TreeNode(assembly.GetName().Name);
tvAssembly.Nodes.Add(rootNode);
GetAssemblies(assembly, rootNode);
tvAssembly.ExpandAll();
}
/// <summary>
/// 递归获取引用的程序集信息
/// </summary>
/// <param name="assembly"></param>
/// <param name="treeNode"></param>
private void GetAssemblies(Assembly assembly, TreeNode treeNode)
{
Queue<Tuple<Assembly, TreeNode>> queue = new Queue<Tuple<Assembly, TreeNode>>();
HashSet<Assembly> loadedAssemblies = new HashSet<Assembly>();
queue.Enqueue(Tuple.Create(assembly, treeNode));
treeNode.Tag = assembly;//节点的数据对象是一个Assembly
while (queue.Any())
{
Tuple<Assembly, TreeNode> tuple = queue.Dequeue();
AssemblyName[] assemblyNames = tuple.Item1.GetReferencedAssemblies();
for (int i = 0; i < assemblyNames.Length; i++)
{
Assembly reference = Assembly.Load(assemblyNames[i]);
TreeNode referenceNode = new TreeNode(reference.GetName().Name);
referenceNode.Tag = reference;//节点的数据对象是一个Assembly
tuple.Item2.Nodes.Add(referenceNode);
if (!loadedAssemblies.Contains(reference)) //loadedAssemblies.Contains(reference.FullName)
{
loadedAssemblies.Add(reference);
//tuple.Item2.Nodes.Add(referenceNode);
queue.Enqueue(Tuple.Create(reference, referenceNode));
}
}
}
//MessageBox.Show(string.Join(",\n", loadedAssemblies.Select(x => x.GetName().Name)));
}
/// <summary>
/// 程序集A是否引用程序集B
/// </summary>
/// <param name="A"></param>
/// <param name="B"></param>
/// <returns></returns>
private bool IsReferencedAssembly(Assembly A, Assembly B)
{
AssemblyName[] assemblyNames = A.GetReferencedAssemblies();
int index = Array.FindIndex(assemblyNames, x => x.Name == B.GetName().Name);
return A != null && B != null && index >= 0;
}
/// <summary>
/// 获取树节点的最大深度Level
/// </summary>
/// <param name="treeNode"></param>
/// <returns></returns>
private int GetMaxLevel(TreeNode treeNode)
{
//考虑到节点可能重复,这里遍历所有节点,找出所有的节点深度
HashSet<int> levelCollection = new HashSet<int>();
Stack<TreeNode> nodes = new Stack<TreeNode>();
nodes.Push(treeNode);
while (nodes.Count > 0)
{
TreeNode currentNode = nodes.Pop();
levelCollection.Add(currentNode.Level);
for (int i = 0; i < currentNode.Nodes.Count; i++)
{
nodes.Push(currentNode.Nodes[i]);
}
}
return levelCollection.Max();
}
/// <summary>
/// 查找指定深度的所有节点集合。获取指定深度的节点集合。这些节点的属于同一行号【Y坐标一致】
/// </summary>
/// <param name="rootNode"></param>
/// <param name="level"></param>
/// <returns></returns>
private List<TreeNode> GetCurrentLevelNodes(TreeNode rootNode, int level)
{
List<TreeNode> nodeList = new List<TreeNode>();
if (rootNode.Level == level)
{
nodeList.Add(rootNode);
}
for (int i = 0; i < rootNode.Nodes.Count; i++)
{
TreeNode current = rootNode.Nodes[i];
//如果当前节点的深度 不等于 已知深度,就继续递归。当前节点的深度 等于 已知深度,就添加该节点
if (current.Level == level)
{
nodeList.Add(current);
}
else
{
List<TreeNode> tempList = GetCurrentLevelNodes(current, level);
if (tempList.Count > 0)
{
nodeList.AddRange(tempList);
}
}
}
return nodeList;
}
/// <summary>
/// 查找指定深度的所有节点集合【去除重复的节点:函数名一致的只保留一个】。获取指定深度的节点集合,这些节点的属于同一行号【Y坐标一致】
/// 节点已经存在ShapeBase的也必须移除掉
/// </summary>
/// <param name="rootNode"></param>
/// <param name="level"></param>
/// <returns></returns>
private List<TreeNode> GetDistinctLevelNodes(TreeNode rootNode, int level)
{
List<TreeNode> nodeList = GetCurrentLevelNodes(rootNode, level);
HashSet<string> methodNames = new HashSet<string>();
for (int i = 0; i < nodeList.Count; i++)
{
//如果重复,就移除掉当前元素
if (!methodNames.Add(nodeList[i].Text))
{
nodeList.RemoveAt(i);
i--;
}
}
//节点已经存在ShapeBase的也不能访问了
for (int i = 0; i < nodeList.Count; i++)
{
//如果重复,就移除掉当前元素
if (GetShapeByTreeNode(nodeList[i]) != null)
{
nodeList.RemoveAt(i);
i--;
}
}
return nodeList;
}
/// <summary>
/// 通过树节点来获取对应的形状节点
/// </summary>
/// <param name="treeNode"></param>
/// <returns></returns>
private ShapeBase GetShapeByTreeNode(TreeNode treeNode)
{
SimpleRectangle shapeNode = graphControl1.Shapes.Cast<SimpleRectangle>().ToList().Find(x => x.Text == treeNode.Text);
return shapeNode;
}
/// <summary>
/// 父子节点进行连线:连线端点Bottom, Left, Right, Top。由起始节点的底部端点 连接到 终止节点的顶部端点
/// </summary>
/// <param name="fromNode"></param>
/// <param name="toNode"></param>
private void AddLink(TreeNode fromNode, TreeNode toNode)
{
ShapeBase fromShape = GetShapeByTreeNode(fromNode);
ShapeBase toShape = GetShapeByTreeNode(toNode);
Assembly A = fromNode.Tag as Assembly;
Assembly B = toNode.Tag as Assembly;
if (fromShape != null && toShape != null && IsReferencedAssembly(A, B))
{
//连线端点Bottom, Left, Right, Top。由起始节点的底部端点 连接到 终止节点的顶部端点
graphControl1.AddConnection(fromShape.Connectors[0], toShape.Connectors[3]);
}
}
/// <summary>
/// 生成一个形状节点
/// </summary>
/// <param name="treeNode"></param>
/// <param name="point"></param>
private void GenerateShapeNode(TreeNode treeNode, Point point, int width)
{
SimpleRectangle diagramNode = new SimpleRectangle(graphControl1);
diagramNode.Text = treeNode.Text;
//主程序提示 特殊处理
diagramNode.Width = width;
diagramNode.Height = 30;
diagramNode.Location = point;
diagramNode.ShapeColor = Color.GreenYellow;
if (!graphControl1.Shapes.Cast<SimpleRectangle>().Any(x => x.Text == treeNode.Text))
{
graphControl1.Shapes.Add(diagramNode);
}
}
/// <summary>
/// 生成节点之间的连线
/// </summary>
private void GenerateLinks(TreeNode treeNode)
{
Stack<TreeNode> nodes = new Stack<TreeNode>();
nodes.Push(treeNode);
while (nodes.Count > 0)
{
TreeNode fromNode = nodes.Pop();
for (int i = 0; i < fromNode.Nodes.Count; i++)
{
TreeNode toNode = fromNode.Nodes[i];
AddLink(fromNode, toNode);
nodes.Push(toNode);//将当前子节点插入集合中,继续连线
}
}
}
private void btnShowDiagram_Click(object sender, EventArgs e)
{
graphControl1.Connections.Clear();//清除所有连线
graphControl1.Shapes.Clear();////清除所有形状节点
//根节点是第一个
TreeNode rootNode = tvAssembly.Nodes[0];
int maxLevel = GetMaxLevel(rootNode);//获取树节点的最大深度
//按照节点的深度Level将树节点进行分组
for (int level = 0; level <= maxLevel; level++)
{
List<TreeNode> nodeList = GetDistinctLevelNodes(rootNode, level);
int sumWidth = nodeList.Sum(x => GetApplyWidth(60, x.Text, graphControl1.Font));//节点所占用的宽度集合
int offsetX = (graphControl1.Width - sumWidth) / (nodeList.Count + 1);
int usedWidth = 0;
for (int i = 0; i < nodeList.Count; i++)
{
int width = GetApplyWidth(60, nodeList[i].Text, graphControl1.Font);
GenerateShapeNode(nodeList[i], new Point(3 + offsetX * (i + 1) + usedWidth, 20 + 120 * level), width + 50);
usedWidth += width;
}
}
//生成节点之间的连线
GenerateLinks(rootNode);
//获取指定深度的节点集合。这些节点的属于同一行号【Y坐标一致】
graphControl1.Invalidate();
}
/// <summary>
/// 获取节点的宽度:考虑到有些节点的文本过长,当文本width大于150时,进行特殊处理
/// </summary>
/// <param name="widthDefault"></param>
/// <param name="text"></param>
/// <param name="font"></param>
/// <returns></returns>
private int GetApplyWidth(int widthDefault, string text, Font font)
{
//获得准确的字符串宽度:
Graphics g = this.CreateGraphics();//一个英文字符占用6个像素
//MeasureString(int width)参数width=350: 允许测量字符串的最大宽度(待测量的字符串长度如果>350,也返回350)
int actualWidth = (int)g.MeasureString(text, font, 350, StringFormat.GenericTypographic).Width;
//Console.WriteLine($"字符个数【{text.Length}】,测量长度【{actualWidth}】,字符串【{text}】,文本格式【{font}】");
return Math.Max(widthDefault, actualWidth);
}
}
}
运行如图:

