.NET手撸绘制TypeScript类图——下篇

.NET手撸绘制TypeScript类图——下篇

在上篇的文章中,我们介绍了如何使用 .NET解析 TypeScript,这篇将介绍如何使用代码将类图渲染出来。

类型定义渲染

不出意外,我们继续使用 FlysEngine。虽然文字排版没做过,但不试试怎么知道好不好做呢?

正常实时渲染时,画一两行文字可能很容易,但绘制大量文字时,就需要引入一些排版操作了。为了实现排板,首先需要将 ClassDef类扩充一下——干脆再加个 RenderingClassDef类,包含一个 ClassDef

class RenderingClassDef	
{	public ClassDef Def { get; set; }	public Vector2 Position { get; set; }	public Vector2 Size { get; set; }	public Vector2 Center => Position + Size / 2;	
}

它包含了一些位置和大小信息,并提供了一个中间值的变量。之所以这样定义,因为这里存在了一些挺麻烦的过程,比如想想以下操作:

  • 如果我想绘制放在中间的 类名,我就必须知道所有行的宽度

  • 如果我想绘制边框,我也必须知道所有行的高度

还好 Direct2DDirectWrite提供了方块的文字宽度、高度计算属性,通过 .Metrics即可获取。有了这个,排板过程中,我认为最难处理的是 y坐标了,它是一个状态机,需要实时去更新、计算 y坐标的位置,绘制过程如下:

foreach (var classDef in AllClass.Values)	
{	ctx.FillRectangle(new RectangleF(classDef.Position.X, classDef.Position.Y, classDef.Size.X, classDef.Size.Y), XResource.GetColor(Color.AliceBlue));	var position = classDef.Position;	List<TextLayout> lines =	classDef.Def.Properties.OrderByDescending(x => x.IsPublic).Select(x => x.ToString())	.Concat(new string[] { "" })	.Concat(classDef.Def.Methods.OrderByDescending(m => m.IsPublic).Select(x => x.ToString()))	.Select(x => XResource.TextLayouts[x, FontSize])	.ToList();	TextLayout titleLayout = XResource.TextLayouts[classDef.Def.Name, FontSize + 3];	float width = Math.Max(titleLayout.Metrics.Width, lines.Max(x => x.Metrics.Width)) + MarginLR * 2;	ctx.DrawTextLayout(new Vector2(position.X + (width - titleLayout.DetermineMinWidth()) / 2 + MarginLR, position.Y), titleLayout, XResource.GetColor(Color.Black));	ctx.DrawLine(new Vector2(position.X, position.Y + titleLayout.Metrics.Height), new Vector2(position.X + width, position.Y + titleLayout.Metrics.Height), XResource.GetColor(TextColor), 2.0f);	float y = lines.Aggregate(position.Y + titleLayout.Metrics.Height, (y, pt) =>	{	if (pt.Metrics.Width == 0)	{	ctx.DrawLine(new Vector2(position.X, y), new Vector2(position.X + width, y), XResource.GetColor(TextColor), 2.0f);	return y;	}	else	{	ctx.DrawTextLayout(new Vector2(position.X + MarginLR, y), pt, XResource.GetColor(TextColor));	return y + pt.Metrics.Height;	}	});	float height = y - position.Y;	ctx.DrawRectangle(new RectangleF(position.X, position.Y, width, height), XResource.GetColor(TextColor), 2.0f);	classDef.Size = new Vector2(width, height);	
}

请注意变量 y的使用,我使用了一个 LINQ中的 Aggregate,实时的绘制并统计 y变量的最新值,让代码简化了不少。

这里我又取巧了,正常文章排板应该是 x和 y都需要更新,但这里每个定义都固定为一行,因此我不需要关心 x的位置。但如果您想搞一些更的操作,如所有类型着个色,这时只需要同时更新 x和 y即可。

此时渲染出来效果如下: 

640?wx_fmt=png

可见 类图可能太小,我们可能需要局部放大一点,然后类图之间产生了重叠,我们需要拖拽的方式来移动到正确位置。

放大和缩小

由于我们使用了 Direct2D,无损的高清放大变得非常容易,首先我们需要定义一个变量,并响应鼠标滚轮事件:

Vector2 mousePos;	
Matrix3x2 worldTransform = Matrix3x2.Identity;	
protected override void OnMouseWheel(MouseEventArgs e)	
{	float scale = MathF.Pow(1.1f, e.Delta / 120.0f);	worldTransform *= Matrix3x2.Scaling(scale, scale, mousePos);	
}

其中魔术值 1.1代表,鼠标每滚动一次,放大 1.1倍。

另外 mousePos变量由鼠标移动事件的 X和 Y坐标经 worldTransform的逆变换计算而来:

protected override void OnMouseMove(MouseEventArgs e)	
{	mousePos = XResource.InvertTransformPoint(worldTransform, new Vector2(e.X, e.Y));	
}

注意:

矩阵逆变换涉及一些高等数学中的线性代数知识,没必要立即掌握。只需知道矩阵变换可以变换点位置,矩阵逆变换可以恢复原点的位置。

在本文中鼠标移动的坐标是窗体提供的,换算成真实坐标,即需要进行“矩阵逆变换”——这在碰撞检测中很常见。

以防我有需要,我们还再加一个快捷键,按空格即可立即恢复缩放:

protected override void OnKeyUp(KeyEventArgs e)	
{	if (e.KeyCode == Keys.Space) worldTransform = Matrix3x2.Identity;	
}

然后在 OnDraw事件中,将 worldTransform应用起来即可:

protected override void OnDraw(DeviceContext ctx)	
{	ctx.Clear(Color.White);	ctx.Transform = worldTransform; // 重点	// 其它代码...	
}

运行效果如下(注意放大缩小时,会以鼠标位置为中心点进行): 

640?wx_fmt=gif

碰撞检测和拖拽

拖拽而已,为什么会和碰撞检测有关呢?

这是因为拖拽时,必须知道鼠标是否处于元素的上方,这就需要碰撞检测了。

首先给 RenderingClassDef方法加一个 TestPoint()方法,判断是鼠标是否与绘制位置重叠,这里我使用了 SharpDX提供的 RectangleF.Contains(Vector2)方法,具体算法已经不用关心,调用函数即可:

class RenderingClassDef	
{	// 其它代码...	public bool TestPoint(Vector2 point) => new RectangleF(Position.X, Position.Y, Size.X, Size.Y).Contains(point);	
}

然后在 OnDraw方法中,做一个判断,如果类方框与鼠标出现重叠,则画一个宽度 2.0的红色的边框,代码如下:

if (classDef.TestPoint(mousePos))	
{	ctx.DrawRectangle(new RectangleF(classDef.Position.X, classDef.Position.Y, classDef.Size.X, classDef.Size.Y), XResource.GetColor(Color.Red), 2.0f);	
}

测试效果如下(注意鼠标位置和红框): 

640?wx_fmt=gif

碰撞检测做好,就能写代码拖拽了。要实现拖拽,首先需要在 RenderingClassDef类中定义两个变量,用于保存其起始位置和鼠标起始位置,用于计算鼠标移动距离:

class RenderingClassDef	
{	// 其它定义...	public Vector2? CapturedPosition { get; set; }	public Vector2 OriginPosition { get; set; }	
}

然后在鼠标按下、鼠标移动、鼠标松开时进行判断,如果鼠标按下时处于某个类的方框里面,则记录这两个起始值:

protected override void OnMouseDown(MouseEventArgs e)	
{	foreach (var item in this.AllClass.Values)	{	item.CapturedPosition = null;	}	foreach (var item in this.AllClass.Values)	{	if (item.TestPoint(mousePos))	{	item.CapturedPosition = mousePos;	item.OriginPosition = item.Position;	return;	}	}	
}

如果鼠标移动时,且有类的方框处于有值的状态,则计算偏移量,并让该方框随着鼠标移动:

protected override void OnMouseMove(MouseEventArgs e)	
{	mousePos = XResource.InvertTransformPoint(worldTransform, new Vector2(e.X, e.Y));	foreach (var item in this.AllClass.Values)	{	if (item.CapturedPosition != null)	{	item.Position = item.OriginPosition + mousePos - item.CapturedPosition.Value;	return;	}	}	
}

如果鼠标松开,则清除该记录值:

protected override void OnMouseUp(MouseEventArgs e)	
{	foreach (var item in this.AllClass.Values)	{	item.CapturedPosition = null;	}	
}

此时,运行效果如下: 

640?wx_fmt=gif

类型间的关系

类型和类型之间是有依赖关系的,这也应该通过图形的方式体现出来。使用 DeviceContext.DrawLine()方法即可画出线条,注意先画的会被后画的覆盖,因此这个 foreach需要放在 OnDraw方法的 foreach语句之前:

foreach (var classDef in AllClass.Values)	
{	List<string> allTypes = classDef.Def.Properties.Select(x => x.Type).ToList();	foreach (var kv in AllClass.Where(x => allTypes.Contains(x.Key)))	{	ctx.DrawLine(classDef.Center, kv.Value.Center, XResource.GetColor(Color.Gray), 2.0f);	}	
}

此时,运行效果如下: 

640?wx_fmt=png

注意:在真正的 UML图中,除了依赖关系,继承关系也是需要体现的。而且线条是有箭头、且线条类型也是有讲究的, Direct2D支持自定义线条,这些都能做,权当留给各位自己去挑战尝试了。


方框顺序

现在我们不能决定哪个在前,哪个在后,想象中方框可能应该就像窗体一样,客户点击哪个哪个就应该提到最前,这可以通过一个 ZIndex变量来表示,首先在 RenderingClassDef类中加一个属性:

public int ZIndex { get; set; } = 0;

然后在鼠标点击事件中,判断如果击中该类的方框,则将 ZIndex赋值为最大值加1:

protected override void OnClick(EventArgs e)	
{	foreach (var item in this.AllClass.Values)	{	if (item.TestPoint(mousePos))	{	item.ZIndex = this.AllClass.Values.Max(v => v.ZIndex) + 1;	}	}	
}

然后在 OnDraw方法的第二个 foreach循环,改成按 ZIndex从小到大排序渲染即可:

// 其它代码...	
foreach (var classDef in AllClass.Values.OrderBy(x => x.ZIndex))	
// 其它代码...

运行效果如下(注意我的鼠标点击和前后顺序): 

640?wx_fmt=gif

总结

其实这是一个真实的需求,我们公司写代码时要求设计文档,通常我们都使用 ProcessOn等工具来绘制,但前端开发者通过需要面对好几屏幕的类、方法和属性,然后弄将其名称、参数和类型一一拷贝到该工具中,这是一个需要极大耐心的工作。

“哪里有需求,哪里就有办法”,这个小工具也许能给我们的客户少许帮助,我正准备“说干就干”时——有人提醒我,我们的开发流程要先出文档,再写代码。所以……理论上不应该存在这种工具😂

但后来有一天,某同事突然点醒了我,“为什么不能有呢?这就叫 CodeFirst设计!”——是啊, EntityFramework也提供了 CodeFirst设计,很合理嘛,所以最后,就有了本篇文章😁。

微信公众号无法评论,有什么想法各位可以转至我的博客园留言/评论/点赞:https://www.cnblogs.com/sdflysha/p/20191114-ts-uml-with-dotnet-2.html

本文所用到的完整代码,可以在我的 Github仓库中下载:https://github.com/sdcb/blog-data/tree/master/2019/20191113-ts-uml-with-dotnet

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313022.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

China .NET Conf 2019-.NET技术架构下的混沌工程实践

这个月的8号、9号&#xff0c;个人很荣幸参加了China.NET Conf 2019 , 中国.NET开发者峰会&#xff0c;同时分享了技术专题《.NET技术架构下的混沌工程实践》&#xff0c;给广大的.NET开发小伙伴介绍混沌工程和高可用性改造实践。会后大家伙聚餐的时候&#xff0c;陈计节老师建…

分布式应用框架 Dapr

微服务架构已成为构建云原生应用程序的标准,微服务架构提供了令人信服的好处&#xff0c;包括可伸缩性&#xff0c;松散的服务耦合和独立部署&#xff0c;但是这种方法的成本很高&#xff0c;需要了解和熟练掌握分布式系统。为了使用所有开发人员能够使用任何语言和任何框架轻松…

.NET Core on K8S 学习与实践系列文章索引 (更新至20191116)

更新记录&#xff1a;-- 2019-11-16 增加Docker容器监控系列文章// 此外&#xff0c;今天是11月17日&#xff0c;我又老了一岁&#xff0c;祝我自己生日快乐&#xff01;近期在学习Kubernetes&#xff0c;基于之前做笔记的习惯&#xff0c;已经写了一部分文章&#xff0c;因此给…

身边的设计模式(一):单例 与 RedisCacheManager

大家好&#xff0c;以后我会用23篇文章&#xff0c;来给大家讲解设计模式&#xff0c;当然如果你看过我的项目&#xff0c;很多设计模式已经很会了&#xff0c;只是没有注意到&#xff0c;我这里会讲解一下&#xff0c;大家就会发现&#xff0c;如果你看懂了我的项目&#xff0…

Kubernetes包管理器Helm发布3.0版本

Helm 3.0 已经发布&#xff0c;该版本是 CLI 工具的最新主要版本&#xff0c;主要关注简单性、安全性和可用性&#xff0c;内容如下&#xff1a;新特性移除 Tiller&#xff08;Helm 2 是一种 Client-Server 结构&#xff0c;客户端称为 Helm&#xff0c;服务器称为 Ti…

“兼职”运维的常用命令

自从产品转到了 dotNET Core 之后&#xff0c;更深入的接触 Linux和 Docker &#xff0c;而我每天的工作中&#xff0c;有一部分时间相当于在“兼职”做一些运维的事情。下面是一些在日常中常用的命令&#xff0c;算是个备忘吧。环境操作系统&#xff1a;CentOS7Docker&#xf…

rabbitmq死信队列详解与使用

先从概念解释上搞清楚这个定义&#xff0c;死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;字面意思可以这样理解&#xff0c;一般来说&#xff0c;producer将消息投递到broker或者直接到queue里了&#xff0c;consumer从queue取出消息进行消费&#xff0c;但某些时…

使用ASP.NET Core 3.x 构建 RESTful API - 3.2 开始建立Controller和Action

Demo下面我们就来实践一下。打开之前的项目&#xff0c;并建立CompaniesController&#xff1a; 这里有6个地方比较关键&#xff0c;我们挨个看一下&#xff1a; RESTful API 或者其它Web API的Controller都应该继承于 ControllerBase 这个类&#xff08;点此查看详细的官方文档…

C++ 链表

线性表&#xff08;顺序表&#xff09;有两种存储方式&#xff1a;链式存储和顺式存储&#xff0c;顺式存储如数组&#xff0c;其内存连续分配&#xff0c;且是静态分配。链式存储&#xff0c;内存是不连续的&#xff0c;且是动态分配。前一个元素存储数据&#xff0c;后一个元…

波拉契尔数列 C++

题目&#xff1a;写一个函数&#xff0c;输入n, 求斐波那契数列的第n项。 分析&#xff1a;该题有两种实现方式递归或循环。当n比较大的时候f(n)结果也会比较大&#xff0c;故定义的时候可以采用long(int 也行)。递归会有大量的重复计算&#xff0c;而循环可以把f(n-1)和f(n-2)…

Deepin 下 使用 Rider 开发 .NET Core

国产的 Deepin 不错&#xff0c;安利一下。Deepin 用了也有一两年&#xff0c;也只是玩玩&#xff0c;没用在开发上面。后来 Win10 不太清真了&#xff0c;就想着能不能到 Deepin下撸码。要搞开发&#xff0c;首先少不了 IDE&#xff0c;VS2019 用不来&#xff0c;Vs Code 太复…

[视频演示].NET Core开发的iNeuOS物联网平台,实现从设备PLC、云平台、移动APP数据链路闭环...

此次我们团队人员对iNeuOS进行了全面升级&#xff0c;主要升级内容包括&#xff1a;&#xff08;1&#xff09; 设备容器增加设备驱动&#xff0c;包括&#xff1a;西门子&#xff08;S7-200smart、S7-300、S7-400、S7-1200、S7-1500&#xff09;、三菱&#xff08;FxSerial…

选择开源项目什么最重要?

开发人员在决定是否使用某个开源项目时考虑到的最重要事项是什么&#xff1f;代码质量&#xff1f;安全性&#xff1f;好的文档&#xff1f;上述因素都很重要&#xff0c;但根据 Tidelift 和 The New Stack 的联合调查&#xff0c;控制着开源项目的开源许可证才是最需要考量的因…

居然不知道和的区别?

前言那年刚找工作那会&#xff0c;就碰到过这么一个简单的题目“&和&&的区别” 那时知识面窄&#xff0c;大概也就知道1.都是作为逻辑与的运算符。2.&&具有短路功能&#xff0c;计算出前者false&#xff0c;就不需计算后者的true or false。后来在微信群里…

【DevOps进行时】自动化测试之单元测试

在DevOps建设中&#xff0c;主流的测试分层体系可以分为单元测试、接口测试和界面测试。Google曾提出一个经验法则&#xff1a;70%的小型测试&#xff0c;20%的中型测试&#xff0c;10%大型测试。当然&#xff0c;这个比例不是确定的&#xff0c;不同类型的项目&#xff0c;测试…

Zongsoft.Data 发布公告

很高兴我们的 ORM 数据访问框架(Zongsoft.Data)在历经两个 SaaS 产品的应用之后&#xff0c;今天正式宣布对外推广。它是一个类 GraphQL 风格的 ORM(Object/Relational Mapping) 数据访问框架。又一个轮子&#xff1f;在很长时间里&#xff0c;.NET 阵营似乎一直缺乏一个被普遍…

使用 .NET Core模板引擎创建自定义的模板和项目

本文要点.NET CLI 包含了一个模板引擎&#xff0c;它可以直接利用命令行创建新项目和项目项。这就是“dotnet new”命令。默认模板集涵盖了默认控制台和基于 ASP.NET 的应用程序以及测试项目所需的基本项目和文件类型。自定义模板可以创建更加有趣或定制化的项目和项目项&#…

.NET Core前后端分离快速开发框架(Core.3.0+AntdVue)

时间真快&#xff0c;转眼今年又要过去了。回想今年&#xff0c;依次开源发布了Colder.Fx.Net.AdminLTE(254Star)、Colder.Fx.Core.AdminLTE(335Star)、DotNettySocket(82Star)、IdHelper(47Star)&#xff0c;这些框架及组件都是本着以实际出发&#xff0c;实事求是的态度&…

.Net开发3年,应聘大厂惨遭淘汰,如何翻身打脸面试官?

(设计师忘记了&#xff0c;这里还有个双十一福利课&#xff0c;还能1元秒杀&#xff01;)

面对金九银十铜十一你真的准备好了吗?

作者&#xff1a;回首笑人间&#xff0c;高级Java工程师一枚&#xff0c;热爱研究开源技术&#xff0c;架构师社区合伙人&#xff01;前言&#xff1a;又是一年一度的金九银十跳槽季&#xff0c;回首在经历了半个月的求职奔波后&#xff0c;终于又能安稳的静下心来敲代码了&…