前言
在 Windows 图形用户界面中,屏幕上显示的所有元素——包括文字、图像、窗口、控件等——都是通过代码“绘制”出来的。VisualFreeBasic(VFB)通过封装 GDI/GDI+ 和集成 Cairo 图形库,让图形绘制变得简单直观。理解 Windows 的消息驱动机制和绘画事件,是掌握自定义图形界面的关键。
一、绘画机制概述
1.1 Windows 消息驱动机制
Windows 采用消息驱动模型,当窗口需要重绘时(如首次显示、从覆盖状态恢复、大小改变等),系统会向窗口发送 WM_PAINT 消息。开发者通过处理这些消息,在相应的事件中进行绘画操作。
1.2 绘画主体
在 VFB 中,绘画的主体可以是:
- 窗口:直接在窗口客户区进行绘制
- 控件:特定控件(如 Picture 控件)支持自定义绘制
1.3 图形库选择
-
yGDI:封装了 GDI 和 GDI+ 的类库,适合大多数 2D 图形操作
-
Cairo:跨平台的 2D 矢量图形库,支持高质量图形和透明效果
-
直接 GDI:使用 Windows API 直接操作设备上下文
二、窗口绘画
2.1 绘画事件
窗口提供两个主要的绘画事件:
2.1.1 FormPaintStart 事件
在系统绘制背景色之后、绘制虚拟控件之前触发,适合绘制背景或底层图形。
'[Form1]事件 : 重绘开始,刚画好背景色,还没画虚拟控件,用 gg 来画。
'hWndForm 当前窗口的句柄(WIN系统用来识别窗口的一个编号,如果多开本窗口,必须 Me.hWndForm = hWndForm 后才可以执行后续操作本窗口的代码)
'gg yGDI画画类,封装了 GDI和GDI+的 API操作类,方便绘画。
'nBackColor 窗口背景颜色(GDI颜色值,由设计时设置)
'若要用Cairo绘图:Dim ca As Cairo = gg.m_Dc
Function Form1_FormPaintStart(hWndForm As hWnd,gg As yGDI,nBackColor As Long) As LResult
Function = FALSE ' 若不想系统继续画虚拟控件等,则应返回 TRUE 。
End Function
返回值说明
- FALSE: 允许系统继续默认的绘画流程
- TRUE: 停止系统绘画流程,完全由自定义代码控制
2.1.2 FormPaintEnd 事件
在所有虚拟控件绘制完成后触发,适合绘制覆盖在控件之上的图形。
'[Form1]事件 : 重绘最后,已经画好虚拟控件,用 gg 来画。
'hWndForm 当前窗口的句柄(WIN系统用来识别窗口的一个编号,如果多开本窗口,必须 Me.hWndForm = hWndForm 后才可以执行后续操作本窗口的代码)
'gg yGDI画画类,封装了 GDI和GDI+的 API操作类,方便绘画。
'nBackColor 窗口背景颜色(GDI颜色值,由设计时设置)
Sub Form1_FormPaintEnd(hWndForm As hWnd,gg As yGDI,nBackColor As Long)
End Sub
三、控件绘画
3.1 Picture 控件
Picture 控件是专门用于自定义绘制的控件,常用于图表、游戏、动态图形等场景。
'[Form1.Picture1]事件 : 重绘,系统通知控件需要重新绘画。
'hWndForm 当前窗口的句柄(WIN系统用来识别窗口的一个编号,如果多开本窗口,必须 Me.hWndForm = hWndForm 后才可以执行后续操作本窗口的代码)
'hWndControl 当前控件的句柄(也是窗口句柄,如果多开本窗口,必须 Me.控件名.hWndForm = hWndForm 后才可以执行后续操作本控件的代码 )
Function Form1_Picture1_WM_Paint(hWndForm As hWnd, hWndControl As hWnd) As LResult
1.使用yGDI库
Dim gg As yGDI = yGDI(hWndControl, GetSysColor(COLOR_WINDOW), True)
2.使用Cairo库
Dim ca As Cairo = cairo(hWndControl, 1)
3.直接DC
Dim ps As PAINTSTRUCT
Dim nDC As HDC = BeginPaint(hWndControl, @ps) '初始化并获取DC
EndPaint(hWndControl, @ps) '结束绘画
Function = TRUE ' 告诉系统,我们自绘了,不需要系统绘画。
End Function
3.2 其他控件的自定义绘制
某些控件(如 Button、ListBox)也支持自定义绘制,通常通过 OwnerDraw 模式实现,这涉及更高级的编程技巧。
四、常用绘画操作
4.1 基本图形绘制(使用 yGDI)
' 绘制线段
gg.Line(x1, y1, x2, y2, [颜色], [线宽])
' 绘制矩形
gg.Rectangle(x, y, width, height, [边框颜色], [线宽], [填充颜色])
' 绘制椭圆/圆形
gg.Ellipse(x, y, width, height, [边框颜色], [线宽], [填充颜色])
' 绘制圆角矩形
gg.RoundRect(x, y, width, height, 圆角X, 圆角Y, [边框颜色], [线宽], [填充颜色])
' 绘制多边形
Dim points(5) As POINT
points(0).x = 100: points(0).y = 100
points(1).x = 150: points(1).y = 50
points(2).x = 200: points(2).y = 100
points(3).x = 180: points(3).y = 150
points(4).x = 120: points(4).y = 150
gg.Polygon(@points(0), 5, RGB(0, 0, 0), 2, RGB(200, 200, 255))
' 绘制文字
gg.TextOut(x, y, "文本内容", [颜色], [字体名], [字体大小], [格式])
gg.TextOut(10, 10, "居中对齐示例", RGB(0, 0, 0), "宋体", 12, DT_CENTER)
' 绘制图像
Dim img As yImage = yImage("image.png")
gg.DrawImage(img, x, y, [宽度], [高度])
4.2 使用 Cairo 绘制
Dim ca As Cairo = cairo(hWndControl, 1) ' 1表示不缩放
' 设置颜色(RGBA,0-1范围)
ca.SetSourceRGB(0.2, 0.4, 0.8) ' 不透明
ca.SetSourceRGBA(0.2, 0.4, 0.8, 0.5) ' 半透明
' 绘制
ca.Rectangle(10, 10, 100, 50)
ca.Fill() ' 填充
ca.Stroke() ' 描边
' 设置线宽
ca.SetLineWidth(2.5)
' 绘制曲线
ca.MoveTo(10, 10)
ca.CurveTo(50, 50, 100, 20, 150, 80)
ca.Stroke()
' 绘制文字
ca.SelectFont("Arial", 12, , , , TRUE)
ca.TextOut(20, 30, "Cairo 文字")
五、高级技巧
5.1 预防闪耀
WIN系统里有一个背景刷新机制,就是需要刷新控件背景,发一个消息,是否要系统为你画背景,你不处理,系统就为你画一次背景。造成闪耀。
因此,需要在自定义消息中,把 WM_ERASEBKGND 消息返回 True ,操作系统就不去绘画了,也不会产生闪耀。
Function Form1_Picture1_Custom(hWndForm As hWnd, hWndControl As hWnd, wMsg As UInteger, wParam As wParam, lParam As lParam) As LResult
Select Case wMsg
Case WM_ERASEBKGND
Return True '防止擦除背景
End Select
Function = FALSE ' 若不想系统继续处理此消息,则应返回 TRUE (俗称吃掉消息)。
End Function
5.2 局部重绘优化
对于复杂图形,只重绘需要更新的区域可以提高性能。
' 只重绘指定区域
Dim rect As RECT
rect.left = 50: rect.top = 50
rect.right = 150: rect.bottom = 150
InvalidateRect(hWndControl, @rect, TRUE)
' 在绘画事件中获取需要重绘的区域
Function WM_Paint_WithUpdateRegion(hWndForm As hWnd, hWndControl As hWnd) As LResult
Dim ps As PAINTSTRUCT
Dim hDC As HDC = BeginPaint(hWndControl, @ps)
' ps.rcPaint 包含了需要重绘的区域
If ps.rcPaint.right - ps.rcPaint.left > 0 Then
' 只绘制脏矩形区域内的内容
Dim gg As yGDI = yGDI(hWndControl, , True)
' ... 绘制代码 ...
End If
EndPaint(hWndControl, @ps)
Function = TRUE
End Function
评论一下?