适用于 .NET 的形状控件
实现支持透明度、自定义设计时编辑器和简单动画的形状控件
引言
当 Microsoft 将 Visual Studio 6 升级到 Visual Studio 7 (.NET) 时,许多熟悉的图形控件(Image
控件、Shape
控件)都被弃用了。尽管以前这些控件实现的功能可以使用 .NET 库中的相应类来实现,但需要付出更多的努力。
熟悉 .NET 中的图形编程后,我能够将我的大多数 VB6 图形程序移植到 C#。然而,我发现我需要一次又一次地为 .NET 中的 VB6 Shape
控件实现替代功能。第三方可能已经有很多适用于 .NET 的 Shape
控件,但我找不到一个令我满意的。
在这篇文章中,我想与读者分享我是如何尝试为 .NET 实现 Shape
控件的。
背景
在 VB6 中,Shape
控件是一个图形轻量级控件,它不是一个真正的控件,并且没有 Hwnd
属性。在 .NET 中,所有控件都是具有 Hwnd
属性的真实控件。尽管 Visual Studio .NET 中没有等效的 Shape
控件,但 .NET 库中有各种类可以共同用于实现 Shape
控件。
基本上,Shape
控件是具有特定形状的图像。为了处理图像功能,有 Image
和 Bitmap
类。对于形状,有 GraphicsPath
类。
这些类是实现 Shape
控件的关键组件。
透明度
在 VB6 中,我们可以设置 DrawMode
属性来实现透视 VB6 Shape
控件的效果。DrawMode
属性仍然可以使用 .NET 中的 GDI/GDI+ 函数实现,但有一种更简单、更优雅的方法。.NET 支持 32 位 ARGB 渲染。在 VB6 中,虽然可以将 32 位 ARGB 值分配给颜色,但从未用到值的 A (Alpha) 分量。
对于任何从 System.Windows.Form.Control
派生的标准控件,至少有两个属性采用 Color
值:BackColor
和 ForeColor
。这些属性都可以分配 ARGB 值。
如何测试透明度?答案是背景。所有控件都必须位于容器内。在大多数情况下,窗体是容器。但是,也有一些控件充当容器,例如 Panel
控件。为了测试透明度,我们可以将容器的背景设置为图像,并将容器内控件的背景颜色设置为 Alpha 小于 255(可能是 100)的颜色。然后,您将能够透过控件看到背景图像。Alpha 越低,看到的背景越多。
形状
在 VB6 中,我们还可以创建具有不同形状的自定义控件。但是,这只能通过调用 Win32 API 函数来完成。在 .NET 中,每个控件都有一个 Region
属性,可用于指定其形状。当控件渲染时,Windows 将只在区域内的像素上绘制。可以通过指定其轮廓来创建 Region
。轮廓可以使用 GraphicsPath
对象构造。下面的代码创建一个 GraphicsPath
对象,向路径添加一个椭圆形状,然后使用该路径实例化一个新的 Region
对象,该对象将分配给控件的 Region
属性。最终结果是控件将采用 Region
的形状,在本例中是一个椭圆。
GraphicsPath _outline=new GraphicsPath();
_outline.AddEllipse(0,0,100,100);
this.Region=new Region(_outline);
自定义设计时编辑器
当您使用 Visual Studio .NET IDE 分配 Color
属性时,可以使用标准颜色编辑器选择一系列颜色,也可以直接输入 ARGB 值。当您使用标准颜色编辑器时,无法指定 Alpha 值。当您直接输入 ARGB 值时,您不知道颜色将如何显示。无论哪种方式,您都没有理想的方法来输入 Color
值。
但是,Visual Studio .NET 允许您创建自己的编辑器来编辑属性值。对于 Shape
控件,我创建了两个自定义设计时编辑器。一个用于编辑 ARGB 值,另一个用于为 Shape
控件选择形状。
在这篇文章中,我不会讨论如何创建自定义设计时编辑器。您可以从 .NET 文档中获取有关 System.Drawing.Design
命名空间的更多信息。
扩展形状控件
形状控件的源代码 (ShapeControl.cs) 允许轻松添加/删除形状。所有形状都在 ShapeType
enum
块中枚举。您可以编辑此块以添加/删除形状。
public enum ShapeType{
Rectangle,
RoundedRectangle,
Diamond,
Ellipse,
TriangleUp,
TriangleDown,
TriangleLeft,
TriangleRight,
BallonNE,
BallonNW,
BallonSW,
BallonSE,
CustomPolygon,
CustomPie
}
相应地添加/编辑为 shape
创建轮廓路径的块。
internal static void updateOutline(ref GraphicsPath outline,
ShapeType shape, int width,int height)
{
Switch (Shape)
{
Case ShapeType.CustomPie:
outline.AddPie(0,0,width,height,180,270);
break;
Case ShapeType.CustomPolygon:
outline.AddPolygon(new Point[]{
new Point(0,0),
new Point(width/2,height/4),
new Point(width,0),
new Point((width*3)/4,height/2),
new Point(width,height),
new Point(width/2,(height*3)/4),
new Point(0,height),
new Point(width/4,height/2)
}
);
break;
Case ShapeType.Diamond:
outline.AddPolygon(new Point[]{
new Point(0,height/2),
new Point(width/2,0),
new Point(width,height/2),
new Point(width/2,height)
});
break;
Case ShapeType.Rectangle:
outline.AddRectangle(new Rectangle(0,0,width,height));
break;
.....
形状选择设计时编辑器的编码方式是,它查询 ShapeType
enum
并在渲染设计时 UI 时调用 updateOutline
函数。它将自动正确显示 shapes
。
ShapeImage、ShapeImageRotation 和 ShapeImageTexture 属性
我添加了 ShapeImage
属性,允许您指定要用于为 Shape
控件生成轮廓的图像。合适的图像是具有清晰轮廓的图像,例如项目中包含的资源。上图显示了 Shape
控件的 ShapeImage
属性的设置,使其呈现蝴蝶的 shape
。请注意,ShapeImage
属性将覆盖 Shape
属性。如果您想使用 Shape
属性,请首先将 ShapeImage
属性设置为(无)。
ShapeImage
属性的轮廓提取实现基于我的文章。
轮廓的乐趣 https://codeproject.org.cn/Articles/760660/Fun-with-Outlines
ShapeImageRotation
属性允许图像在生成轮廓之前旋转。此属性的有效值介于 -180 和 180 度之间。
ShapeImageTexture
属性允许将纹理图像应用于形状。请注意,纹理图像与简单的背景图像不同。纹理就像形状上的皮肤,当形状被调整大小、旋转和拉伸时,纹理也会被调整大小、旋转和拉伸。
Blink、Vibrate、AnimateBorder 属性
设置这些属性会使控件分别闪烁、振动和动画化边框。如果您想将形状控件用作某种信标,这可能很有用。
连接器属性
我添加了 Connecter
属性,用于指定线条形状如何连接到 Shape
控件。Connecter
属性的值为 Center
、Left
、Right
、Top
、Bottom
。
下面的代码演示了如何将线条形状控件连接到 2 个形状控件。ctrl1
是线条形状控件。线条将从源形状控件 (ctrlsrc
) 延伸到目标形状控件 (ctrldest
)。ShapeControl.Line.setConnectors()
为源和目标形状控件设置 Connector
属性的适当 ConnecterType。ShapeControl.Line.setConnectorPoint()
分别从 ctrlsrc
和 ctrldest
提取源和目标的锚点坐标。ShapeControl.Line.setLine()
设置线条形状控件的锚点坐标。
//default src_cam location
ctrlsrc.Connector =ShapeControl.ConnecterType.Center;
int x0 = ctrlsrc.Location.X +ctrlsrc.Width / 2;
int y0 = ctrlsrc.Location.Y +ctrlsrc.Height / 2;
//default dest_cam location
ctrldest.Connector = ShapeControl.ConnecterType.Center;
int x1 = ctrldest.Location.X + ctrldest.Width / 2;
int y1 = ctrldest.Location.Y + ctrldest.Height / 2;
//re-adjust connection points
ShapeControl.Line.setConnectors(ref ctrlsrc, ref ctrldest);
ShapeControl.Line.setConnectorPoint(ref x0, ref y0, ctrlsrc);
ShapeControl.Line.setConnectorPoint(ref x1, ref y1, ctrldest);
//call the generic function
ShapeControl.Line.setLine(ref ctrl1, x0, y0, x1, y1);
ShapeStorage 属性
我为 shape
控件添加了 2 个属性:ShapeStorageSave
和 ShapeStorageLoad
。这些属性的主要目的是使您能够在设计时将 shapes
保存到磁盘并加载它们。当您在属性列表中单击这些属性时,会弹出一个对话框,使您能够查看和选择要使用的文件。shape
文件的默认扩展名为 .shp.jpg
。对于每个 .shp.jpg
文件,都会有一个相应的 .shp
文件。.shp
文件存储了 shape
控件使用的所有属性和资源。请注意,在 Visual Studio 设计器中,这些 ShapeStorage
属性将始终为空,因为它们唯一的目的是为您弹出对话框以进行操作。
演示示例应用程序
Demo1
展示了形状控件的功能。当您在面板周围拖动菱形时,左侧面板演示了透明度。其他形状控件显示了各种不同的形状和设置。Demo2
展示了在典型平面图中,shape
控件的Blink
、Vibrate
和AnimateBorder
属性在触发警报/警告时的使用。Demo3
是一个更高级的演示,展示了一个摄像机放置设计器应用程序。您可以添加摄像机,移动它们,调整它们的大小,更改它们的属性,并通过拖放连接它们。连接方向也可以设置和更改。您还可以测试摄像机和连接线的动画。配置可以保存和重新加载。Demo4
展示了一个简单的认知游戏,其中在两个移动的时钟指针内显示知名人物底层图像的一部分,目标是识别尽可能多的这些人物。用户界面展示了形状控件的创意用法。Demo5
展示了ShapeImageRotation
属性的有趣用法。您可以描绘飞机形状控件的飞行路径。飞机形状控件将沿着飞行路径移动,并通过旋转以朝向飞行方向来保持正确的方向。Demo6
演示了ShapeImageTexture
属性的使用。您可以将无纹理/背景、通用纹理和指定纹理应用于 2 种不同的形状后看到不同的效果。Demo7
展示了在运行时从文件保存和加载形状控件Demo8
展示了ShapeStorageSave
和ShapeStorageLoad
属性的使用。
双缓冲
双缓冲可实现窗体或容器上控件的平滑渲染。自 .NET 2.0 以来,引入了 Control.DoubleBuffered
属性。但是,此属性是 protected
。要在控件中启用此属性,您可以创建一个继承自 Control
类的自定义类,然后在自定义类中设置该属性。但是,您仍然可以使用反射在现有控件上设置此属性,而无需子类化。下面的代码演示了如何为面板控件设置双缓冲。
//invoke double buffer
typeof(Panel ).InvokeMember(
"DoubleBuffered",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.SetProperty,
null,
panel1,
new object[] { true });
结论
我希望读者不仅能从使用 shape
控件创建精美用户界面中受益,还能从探索用于创建形状控件魔力的各种 .NET 类中受益。
历史
- 2014 年 4 月 20 日:更新
Shape
类型(Property
)编辑器以在 Visual Studio 2008 及更高版本中正常工作 - 2014 年 4 月 29 日:添加
ShapeImage
属性 - 2016 年 5 月 7 日:添加
Blink
和Vibrate
属性 - 2016 年 5 月 9 日:高级演示,展示摄像机放置设计器应用程序
- 2016 年 5 月 10 日:在演示中添加双缓冲,以实现控件的平滑拖动
- 2016 年 5 月 10 日:添加由 SharpDevelop(开源 IDE)转换的 VB.NET 代码
- 2016 年 5 月 14 日:添加 4 种线条形状
- 2016 年 5 月 16 日:添加
AnimateBorder
属性 - 2016 年 5 月 17 日:增强
Demo3
(摄像头放置应用程序),允许通过“拖放”技术将摄像头与线条形状控件连接起来 - 2016 年 5 月 20 日:为线条形状添加
Direction
属性 - 2016 年 5 月 24 日:添加
Connecter
属性 - 2017 年 2 月 22 日:在 V4b 中添加
ShapeImageRotation
属性 - 2017 年 2 月 22 日:在 V4b1 中更改旋转形状图像的渲染,以获得更平滑的边缘
- 2017 年 2 月 25 日:添加
Demo5
(描绘蝴蝶形状控件的飞行路径) - 2017 年 2 月 27 日:增强 ShapeImage 形状的轮廓生成。微调
Demo5
。在 V5 中添加Demo6
以展示ShapeImageTexture
属性的使用 - 2017 年 3 月 1 日:更正 VB V4a 的
ColorEditor
- 2017 年 3 月 9 日:在 V6 中添加
ShapeStorageSave
和ShapeStorageLoad
属性 - 2017 年 3 月 24 日:添加 V6 的 VB.NET 源代码