C#将【程序集引用-依赖关系】展示到NetronLight图表中

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);
        }
    }
}

运行如图:

相关推荐
大飞pkz1 小时前
【设计模式&C#】外观模式(用于解决客户端对系统的许多类进行频繁沟通)
设计模式·c#·外观模式
格林威2 小时前
Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现动物分类(C#源码,UI界面版)
人工智能·深度学习·数码相机·yolo·计算机视觉·c#·视觉检测
军训猫猫头7 小时前
9.IEnumerable可枚举接口 C#例子 WPF例子
windows·microsoft·c#
枯萎穿心攻击12 小时前
响应式编程入门教程第五节:Unity 生命周期与资源管理中的响应式编程
开发语言·unity·架构·c#·游戏引擎
一只爱做笔记的码农16 小时前
【C#】Vscode中C#工程如何引用自编写的dll
开发语言·vscode·c#
CodeCraft Studio17 小时前
文档处理控件TX Text Control系列教程:使用 C# .NET 将二维码添加到 PDF 文档
pdf·c#·asp.net·二维码·tx text control
张人玉18 小时前
C#`Array`进阶
java·算法·c#
向宇it21 小时前
【实现100个unity特效】unity中使用ShaderGraph实现一个贴图UV循环移动滚动的指示效果
游戏·3d·unity·c#·游戏引擎·贴图·uv
勿芮介1 天前
【微服务】Ocelot微服务网关
微服务·c#·gateway