GDI+ 代码生成器






4.88/5 (59投票s)
允许用户绘制矢量图形形状,然后将它们转换为 GDI+ 代码。
引言
本项目允许用户使用矢量图形界面来绘制和编辑形状。然后可以将这些形状转换为 GDI+ 代码,您可以将其拖到您的应用程序项目中。
背景
多年来,有许多应用程序需要比标准 MS 控件所提供的更多图形功能。我总是不得不手动编写所有图形代码,或者创建一个第三方应用程序的图像并在我的控件上绘制。然而,这两种方法都存在问题。第一种方法需要大量时间。第二种方法不允许程序进行任何主题更改,除非您绘制并加载同一图像的多个变体。这个 C# 项目解决了使用以前方法带来的许多令人沮丧的时间。我相信许多人会发现这些代码对我来说一样有用。
使用代码
我已经定义了以下充当矢量图形形状的形状。所有形状都继承自基类 Shape
。
ShapeRectangle
ShapeTriangle
ShapeCircle
ShapeLine
ShapePolygon
ShapeText
为了避免代码结构上的任何混淆,让我解释一下。我们将从 Shape.cs 文件开始,并重点介绍一些亮点。
鼠标事件
有三个鼠标方法被创建,它们的名字与 MS 控件的鼠标事件处理程序相同。然而,为了减小代码的尺寸和复杂性,Shape.cs 的鼠标代码的行为与 MS 的代码略有不同。MouseDown
、MouseMove
和 MouseUp
事件会被传递给每个控件,然后由控件决定如何处理这些信息。例如,当一个形状接收到 MouseDown
消息时,它会执行验证以确定是否需要更新自身。让我们看看 Shape.cs 类中的 MouseDown
处理程序。
public virtual EventData MouseDown(MouseEventArgs e)
{
eventData.WasHit = HitTest(e);
if (eventData.WasHit)
{
isSelected = true;
painter.State.IsSelected = true;
...(additional code)
}
else if(isSelected)
{
isSelected = false
painter.State.IsSelected = false;
eventData.NeedsPainted = true;
}
eventData;
}
上面的代码有几点值得注意。请注意,该方法被标记为 virtual
。这样,任何继承自 Shape
的形状都可以重写 MouseDown
方法并执行自定义验证。这对于像 ShapePolygon
这样的形状是必要的。返回类型 EventData
承担了一些重要责任。正如您在代码示例的顶部所看到的,EventData
的一个属性是通过调用 HitTest()
函数来设置的,该函数返回一个布尔值。如果 EventData
表示鼠标点击了该形状,则会设置一些内部形状变量。如果鼠标未命中区域且该形状先前已被选中,我们会将 EventData.NeedsPainted
设置为 true
。“NeedsPainted
”变量将决定父控件是否会使其自身失效。一旦我们到达方法末尾,EventData
将被传递给调用者,在我们的例子中是管理形状对象集合的“ShapeManager.cs”文件。形状围绕的核心方法之一是抽象方法 GeneratePath()
。每个继承的形状都必须重写并提供形状的绘制逻辑。例如,ShapeTriangle
的绘制逻辑是
protected override void GeneratePath()
{
//Creates a triangle shape
_Path = new System.Drawing.Drawing2D.GraphicsPath();
_Path.AddLine(Location.X + (Size.Width/2), Location.Y,
Location.X + Size.Width, Location.Y+Size.Height);
_Path.AddLine(Location.X + Size.Width, Location.Y+Size.Height,
Location.X, Location.Y+Size.Height);
_Path.AddLine(Location.X, Location.Y+Size.Height,
Location.X + (Size.Width});
}
另一个重要项是 Painter
对象。在创建此项目时,我注意到当您从传统的纯色转向渐变时,代码可能会变得相当混乱。考虑到这一点,我创建了一个 Painter
对象,它连接到形状。这种设计的优点是,形状的渲染可以像您编写 Painter
对象一样复杂。如果需要修改绘制算法,则无需更改形状。除了 Painter
,我还创建了一个 Windows 窗体,它提供了设置 Painter
属性的界面。您有两种选择用于绘制:Solid
(纯色)或 Gradient
(渐变)。让我们看看最复杂的属性,Gradient
。Painter
类已经过注释,解释了大部分代码部分,但我将重点介绍两个用于帮助设置 Blend
属性的对象:BlendSmoothness
和 Coverage
。如果您不熟悉 LinearGradientBrush
的 Blend
属性,Blend
属性允许程序员指定两种颜色混合在一起的位置和量。该主题超出了本文档的范围,但我鼓励您阅读一篇关于该主题的优秀文章。请注意,以下两个属性**不**属于 Blend
属性!
顾名思义,BlendSmoothness
是一个 byte
属性,它决定了两种颜色混合在一起的平滑度。它接受 0 到 100 之间的值,代表 0% 到 100%。Coverage
属性也接受 0 到 100 之间的值。它决定第一种颜色在整个形状中占据多少空间。值为 100 将用第一种颜色完全填充形状。通常通过将两个属性都设置为 50 来获得良好的混合效果。
绘制矢量形状
虽然我可以创建一个庞大的复杂形状和转换方法库,但我希望在本篇文章中减小项目的大小。这个框架应该为您提供扩展或创建其他形状和绘制功能的基础。多边形是项目中迄今为止最复杂的形状。我看到的其他代码示例不允许用户将线条转换为贝塞尔曲线。在我看来,贝塞尔曲线比短直线更能改善图像的外观。我还要指出,这是我创建的第一个多边形对象。如果有人对改进多边形或任何其他代码部分有建议,我非常欢迎您的意见。创建多边形时,形状将通过 MouseDown
事件继续添加节点,直到您按 Enter 键或切换工具。一旦形状完成,您可以通过双击多边形的轮廓来添加其他节点。当一个图像由多个形状组成时,在代码生成后很容易混淆哪个形状是哪个。例如,如果您有十五个形状,生成的代码可能看起来像:“Rectangle1、Rectangle2、Polygon1 等”。一个更好的方法是:“CarBody、RightWheel、LeftFender 等”。要编辑形状的名称,请导航至:Shape -> Properties。
您可以看到,您可以更改形状的名称并设置有关代码生成的一些其他重要属性。当您选择每个属性时,右侧将显示该属性的描述。仔细阅读每一项,以了解它们将如何影响代码输出!需要注意的是,GDI 生成属性在您选择它们时会更新。但是,在编辑“Name
”属性时,您必须单击“Update”按钮。
追踪图像
我添加了一个很酷的功能,可以用来追踪第三方图像。在尝试绘制 JPEG 中的复杂形状时,我发现很难猜测形状区域的确切坐标。由于这种沮丧,我插入了一个功能,您可以在绘图区域的背景中导入图像:BMP、JPG、GIF 或 PNG。要访问此功能,请导航至:File -> Set Background Image。
生成 GDI+ 代码
一旦绘制了所有形状并设置了所需的值,就可以开始代码生成了!在代码生成过程中,有一个重要的属性会影响所有形状的输出:“Clipping
”(裁剪)属性。默认情况下,裁剪矩形是关闭的,GDI+ 生成工具将确保所有形状都包含在最终边界中。但是,在某些情况下,您可能需要裁剪输出。要打开裁剪矩形,请导航至 File -> Show Clipping。
步骤 1. 要生成形状代码,请导航至:File -> View GDI+ Code。这将生成 C# 类文件的代码,并将其加载到代码查看器中,代码查看器只是一个多行文本框。为什么我们要生成定义类文件的代码?简单地说,任何中等复杂的 GDI+ 图像都可能相当冗长。例如,您在开头看到的“Toucan.cs”文件大约有 600 行代码!这也不是松散生成的代码。除了一两个小的例外,我不会以与手动编写类不同的方式来编写它。如果您是第一次生成项目,您将收到一条消息,告知您为输出类指定一个名称。请确保遵循 Microsoft 制定的命名约定,以避免非法类名。代码查看器现在将打开,所有相关的形状和绘制代码都将加载到其中。
步骤 2. 复制所有内容,然后将代码粘贴到**空**的 C# 类文件中。请务必将类文件名命名为与您生成的类名相同,以避免混淆。
步骤 3. 要开始使用新类,请创建一个 Windows 窗体,并在顶部创建一个生成的 GDI+ 类的实例。已经编写了一个默认命名空间“MyCustomGraphics
”来将您的类与其他代码分开。初始化“Toucan.cs”类的语法是:包装窗体的 OnPaint
事件并插入绘制您的类的代码。绘制“Toucan.cs”类的代码将是
//Create a toucan object
MyCustomGraphics.Toucan toucan = new MyCustomGraphics.Toucan();
步骤 4. 接下来,捕获窗体的 OnPaint
事件并插入绘制类的代码。绘制“Toucan.cs”类的代码将是
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//paint the toucan
e.Graphics.DrawImageUnscaled(toucan.Image, 0, 0);
}
任何已编写为接受更改(颜色、边界等)的形状都分配有一个枚举值。例如,如果“Toucan
”类的“LeftBeak
”多边形的属性设置为接受对其填充颜色的更改,则将生成枚举值“eShapeColor.LeftBeakFillColor
”。还有一个名为 SetColor
的辅助方法,它接受一个“eShapeColor
”枚举和一个新颜色。此酷功能可确保不会发生任何拼写错误。设置属性错误的唯一方法是传递不正确的枚举。
//Sets the wing color
toucan.SetColor(MyCustomGraphics.Toucan.eShapeColor.LeftBeak, Color.Brown);
toucan.RefreshImage();
//refresh the form's surface area
this.Invalidate();
让我们看上面的代码示例。请注意,在调用 RefreshImage()
方法之前,不会进行任何渲染更新。原因应该很明显,但如果您要更新 20 个形状,您不希望图像刷新 20 次!您想完成所有编辑,然后告诉图像使用更新刷新自身。在刷新图像后,我们现在告诉父控件使其表面区域无效。如果您不熟悉失效,我鼓励您对它所涉及的内容有一个充分的了解。此主题对于执行复杂绘制工作的图形程序至关重要。
大家伙们,就是这样!我相信这篇文章内容丰富且信息丰富。所讨论的主题每一个都需要一整篇文章来彻底涵盖所有信息。希望这篇简短的文章回答了一些主要问题。密切关注,因为我可能会在未来撰写一篇关于高效实现生成类文件的文章。