- 图形学
- 桌面应用
【免费下载链接】RGF_CJ
RGF是仓颉Windows系统下的通用渲染框架(兼应用开发框架)。RGF仓颉版(后续简称"RGF")基于RGF(C/C++版)封装优化而来。RGF为开发者提供轻量化、安全、高性能以及高度一致性的2D渲染能力以及常用的桌面应用开发工具,并且提供对接Direct3D的相关接口,以满足开发者对3D画面渲染的需求。在Windows开发方面,RGF在子包内也提供开箱即用的Windows API,确保熟悉Win32的开发者快速上手。此外,RGF还提供一些常用功能的封装(如XAudio2、MF),以辅助开发者实现音频、摄像头等扩展功能的管理。
RGF(仓颉版)在rgf.rgf_core子包中提供了面向 Windows 的窗口事件参数体系,EvCalcSize正是用于承载WM_NCCALCSIZE(0x0083)消息的事件参数类,负责在系统计算窗口客户区(Client Area)大小与位置时向开发者暴露完整的计算上下文。本文以 EvCalcSize 官方 API 文档 为骨架,结合 rgf_proc.cj 与 hwinuser.cj 的源码实现,完整讲解该类的字段语义、构造解析过程、postProcess()写回机制以及自定义客户区布局的实战方法,帮助读者掌握在 RGF 框架中精细控制窗口非客户区/客户区边界的核心能力。
一、EvCalcSize 在 RGF 事件体系中的定位
RGF 的事件体系遵循一套明确的命名约定(见 rgf_proc.cj 头部注释):
- 事件以
On开头接具体含义,如onCalcSize; - 事件参数类命名时使用
Ev+ 具体含义,如EvCalcSize; - 事件相关枚举命名时使用
Em+ 具体含义。
EvCalcSize对应 Windows 的WM_NCCALCSIZE消息。当窗口的大小、位置或样式发生变化,系统需要重新计算窗口工作区(客户区)的大小和位置时,该消息会被发送。RGF 在内部消息分发时将其包装为EvCalcSize对象交给开发者,类定义如下:
public open class EvCalcSize <: EvCore它继承自核心事件参数基类 EvCore。从源码看,EvCore保存了三条原始消息信息(rgf_proc.cj#L37-L48):
public open class EvCore{ internal var isOverride:Bool = true; // 当前方法是否已被开发者覆盖 public let message:UInt32 // 原始消息编号 public let wParam:UInt64 // 原始 wParam public let lParam:Int64 // 原始 lParam public init(uMsg:UInt32, wParam:UInt64, lParam:Int64) { ... } }其中isOverride是框架内部标志:当开发者真正重写了对应事件回调时它为true,消息分发代码据此决定是否调用postProcess()把修改结果写回系统。
二、构造函数:wParam 与 lParam 的双重语义
EvCalcSize的构造函数签名与基类一致,但在内部对wParam/lParam做了精细的语义拆解(源码见 rgf_proc.cj#L2626-L2664):
@OverflowWrapping public init(wParam: UInt64, lParam: Int64) { super(0x0083 /*WM_NCCALCSIZE*/, wParam, lParam) ptr = ruUllToPtr(ruInt64SafeToUIntNative(lParam)) calcValidRects = wParam != 0 ... }| 参数 | 类型 | 语义 |
|---|---|---|
wParam | UInt64 | 非零表示应计算窗口的有效矩形(calcValidRects = true),为零表示仅计算客户区整体大小 |
lParam | Int64 | 指向NCCALCSIZE_PARAMS结构体(wParam非零时)或RECT结构体(wParam为零时)的指针 |
2.1 wParam 非零:完整 NCCALCSIZE_PARAMS 解析
当wParam != 0时,系统要求应用程序指定工作区中哪些部分包含有效信息,lParam指向 tagNCCALCSIZE_PARAMS:
@C public struct tagNCCALCSIZE_PARAMS { public var rgrc:VArray<RECT,$3> = [RECT(),RECT(),RECT()]; public var lppos:PWINDOWPOS = PWINDOWPOS(); }RGF 的构造函数会把结构体整体读出并暂存到内部Obj字段中,同时展开为三个Rect和一个WINDOWPOS视图:
let tmp:NCCALCSIZE_PARAMS = unsafe{ CPointer<NCCALCSIZE_PARAMS>(ptr).read() } Obj = tmp rect1 = Rect.toClass(tmp.rgrc[0]) // 窗口当前位置矩形(返回:窗口返回的工作区矩形) rect2 = Rect.toClass(tmp.rgrc[1]) // 移动窗口之前窗口的坐标(返回:有效的目标矩形) rect3 = Rect.toClass(tmp.rgrc[2]) // 移动窗口之前窗口工作区的坐标(返回:有效的源矩形) let wPos:WINDOWPOS = unsafe{ tmp.lppos.read() } hwnd = wPos.hwnd.toUIntNative() hwndInsertAfter = wPos.hwndInsertAfter.toUIntNative() x = wPos.x; y = wPos.y cx = wPos.cx; cy = wPos.cy flags = wPos.flags其中WINDOWPOS结构体同样在 hwinuser.cj#L1191-L1202 中定义,字段依次为hwnd、hwndInsertAfter、x、y、cx、cy、flags。
2.2 wParam 为零:仅返回客户区矩形
当wParam == 0时,消息仅用于获取/设定客户区整体矩形,lParam指向单个RECT。此时只有rect1有实际意义,其余字段全部置零:
let tmp:RECT = unsafe{ CPointer<RECT>(ptr).read() } Obj = tmp rect1 = Rect.toClass(tmp) rect2 = Rect() // 空矩形 rect3 = Rect() hwnd = 0; hwndInsertAfter = 0 x = 0; y = 0; cx = 0; cy = 0; flags = 0三、字段成员完整速查
原文档共列出 11 个公开成员,逐一说明其类型与语义:
| 成员 | 类型 | 可变性 | 描述 |
|---|---|---|---|
calcValidRects | Bool | let | 指定应用程序是否应指定工作区的哪部分包含有效信息(即原始wParam是否非零) |
cx | Int32 | var | 窗口新宽度(来自WINDOWPOS.cx,仅在calcValidRects为 true 时有值) |
cy | Int32 | var | 窗口新高度(来自WINDOWPOS.cy) |
flags | UInt32 | var | WINDOWPOS.flags,携带SWP_*窗口位置标志位 |
hwndInsertAfter | RgfHwnd | let | Z 序中插入位置的后置窗口句柄(WINDOWPOS.hwndInsertAfter) |
hwnd | RgfHwnd | let | 发生变更的窗口句柄(WINDOWPOS.hwnd) |
rect1 | Rect | var | 窗口当前位置矩形(返回:窗口返回的工作区矩形) |
rect2 | Rect | var | 移动窗口之前窗口的坐标(返回:有效的目标矩形) |
rect3 | Rect | var | 移动窗口之前窗口工作区的坐标(返回:有效的源矩形) |
x | Int32 | var | 窗口新 X 坐标(WINDOWPOS.x) |
y | Int32 | var | 窗口新 Y 坐标(WINDOWPOS.y) |
其中RgfHwnd是 RGF 对窗口句柄的类型别名,定义为UIntNative(见 rgf_type.cj#L27)。Rect是 RGF 的矩形值类,提供left/top/right/bottom与派生的width/height/size/point属性(见 rgf_struct.cj#L127),并支持与 Win32RECT结构之间的toClass/toStruct双向转换——这正是EvCalcSize与底层内存互操作的桥梁。
需要注意:hwnd、hwndInsertAfter、x、y、cx、cy、flags这七个字段仅在calcValidRects为 true 时才携带有效数据,它们直接透传WINDOWPOS结构的内容,帮助开发者了解窗口在本次尺寸计算前的位置与尺寸上下文。
四、postProcess():将计算结果写回系统
EvCalcSize除了提供只读上下文,还允许开发者在回调中修改上述字段,并通过postProcess()把修改结果反向写回系统内存(源码见 rgf_proc.cj#L2665-L2688):
public func postProcess() { if(calcValidRects){ var tmp:NCCALCSIZE_PARAMS = (Obj as NCCALCSIZE_PARAMS).getOrThrow() tmp.rgrc[0] = rect1.toStruct() // 新的客户区矩形 tmp.rgrc[1] = rect2.toStruct() // 有效目标矩形 tmp.rgrc[2] = rect3.toStruct() // 有效源矩形 var wPos:WINDOWPOS = unsafe{ tmp.lppos.read() } wPos.x = x; wPos.y = y wPos.cx = cx; wPos.cy = cy wPos.flags = flags unsafe{ tmp.lppos.write(wPos) } unsafe{ CPointer<NCCALCSIZE_PARAMS>(ptr).write(tmp) } } else { var tmp:RECT = (Obj as RECT).getOrThrow() tmp = rect1.toStruct() unsafe{ CPointer<RECT>(ptr).write(tmp) } } }这一机制的意义在于:RGF 将“系统内存 → 对象字段”的解析和“对象字段 → 系统内存”的回写都封装在了类的内部。开发者只需要修改EvCalcSize的公开字段,无需接触任何指针操作,即可在下一个窗口布局周期生效。这也解释了为什么rect1/rect2/rect3、x/y/cx/cy/flags都是var(可修改),而calcValidRects、hwnd、hwndInsertAfter是let(只读上下文)。
五、消息分发链路:onCalcSize 与 bindCalcSize
EvCalcSize在框架中有两条触发路径,分别对应 RGF 的两种事件绑定风格。
5.1 重写风格:onCalcSize
开发者继承窗口基类后重写onCalcSize,默认实现返回 0(rgf_proc.cj#L6278-L6281):
func onCalcSize(e:EvCalcSize):Int64{ e.isOverride = false return 0 }在消息分发代码中(rgf_proc.cj#L5077-L5085),框架将0x0083消息包装为EvCalcSize并调用回调,仅当开发者真正覆盖了该方法(isOverride == true)时才执行postProcess()写回:
case 0x0083 /*WM_NCCALCSIZE*/ =>{ let eObj:EvCalcSize = EvCalcSize(wParam,lParam) let retv = onCalcSize(eObj) if(eObj.isOverride){ eObj.postProcess() } return (retv, eObj.isOverride) }5.2 回调属性风格:bindCalcSize
RGF 还提供bindCalcSize回调属性(定义于 rgf_proc.cj#L8149),绑定后走另一条分发分支(rgf_proc.cj#L7285-L7289):
case 0x0083 /*WM_NCCALCSIZE*/ =>{ let eObj:EvCalcSize = EvCalcSize(wParam,lParam) return if(let Some(v) <- bindCalcSize?(eObj)){ eObj.postProcess(); v }else{ Option<Int64>.None } }值得一提的是,这两条分发分支(含上述 case 代码)并非手写,而是由 RGF 的宏在编译期生成,宏模板位于 rgf_proc_utils.cj(onCalcSize版本)与 同一文件的 bind 版本。宏根据(事件名, 参数类, 返回类型)三元组自动展开为对应的消息分发代码,这也是 RGF 能以极低样板代码支撑大量窗口消息的原因。
六、返回值:WVR_* 标志与保留策略
onCalcSize/bindCalcSize的返回值会被作为WM_NCCALCSIZE的处理结果返回给系统。源码注释中对返回值标志给出了完整定义(rgf_proc.cj#L6264-L6277):
| 标志 | 值 | 含义 |
|---|---|---|
WVR_ALIGNTOP | 0x0010 | 保留窗口的工作区,并与窗口新位置的顶部对齐(如与WVR_ALIGNLEFT组合可实现左上角对齐) |
WVR_ALIGNLEFT | 0x0020 | 保留工作区并与新位置左侧对齐 |
WVR_ALIGNBOTTOM | 0x0040 | 保留工作区并与新位置底部对齐 |
WVR_ALIGNRIGHT | 0x0080 | 保留工作区并与新位置右侧对齐 |
WVR_HREDRAW | 0x0100 | 客户区矩形水平尺寸变化时完全重绘窗口(类似CS_HREDRAW类样式) |
WVR_VREDRAW | 0x0200 | 客户区矩形垂直尺寸变化时完全重绘窗口(类似CS_VREDRAW类样式) |
WVR_REDRAW | 0x0300 | WVR_HREDRAW与WVR_VREDRAW的组合,重绘整个窗口 |
WVR_VALIDRECTS | 0x0400 | 表示rgrc[1](有效目标矩形)与rgrc[2](有效源矩形)有效,系统将合并二者计算保留的窗口区域;不能与任何其他标志组合使用 |
返回 0 表示采用系统默认计算(整个窗口尺寸减去除非客户区后即为客户区);返回WVR_VALIDRECTS并配合修改后的rect2/rect3,则可以实现居中保留工作区、保留工作区子集等更精细的策略。
七、实战:自定义客户区布局
下面给出一个最小实战示例:在 RGF 窗口类中重写onCalcSize,将客户区整体下移 20 像素(常用于给自定义非客户区/自绘标题栏预留空间)。示例基于上文源码确认的签名与语义编写:
public class MyWindow <: WinCore { // 或继承你项目使用的 RGF 窗口基类 // 重写 WM_NCCALCSIZE 事件:把客户区整体向下收缩 20 像素 public override func onCalcSize(e: EvCalcSize): Int64 { // 原始 wParam 非零时,rect1 是系统给出的工作区矩形,可在此修改 if (e.calcValidRects) { let oldTop = e.rect1.top e.rect1 = Rect(e.rect1.left, oldTop + 20, e.rect1.right, e.rect1.bottom) } // 返回 0:其余区域仍由系统默认计算 return 0 } }如需使用回调属性风格,可等价地绑定:
win.bindCalcSize = { e: EvCalcSize => if (e.calcValidRects) { // 例如:强制客户区最小宽度为 640 if (e.cx < 640) { e.cx = 640 } } 0 }若要实现“居中保留工作区”,则在calcValidRects为 true 时设置rect2(有效目标矩形)为窗口中央的子矩形、rect3(有效源矩形)为原工作区对应区域,并返回WVR_VALIDRECTS(0x0400),系统会自动将源矩形内的窗口图像复制并裁剪到目标矩形。
八、注意事项与调试要点
- 字段的有效性分两种模式:只有
calcValidRects为 true 时,rect2、rect3及hwnd/x/y/cx/cy/flags才携带真实数据;该值为 false 时仅rect1有意义。读取前务必先判断calcValidRects。 postProcess()由框架自动调用:在onCalcSize/bindCalcSize两条分发路径中,只要isOverride为 true 或回调被绑定,框架都会在回调返回后自动调用postProcess()。开发者无需(也不应)手动重复调用。- 不要与
EvSize混淆:WM_SIZE(窗口尺寸已变更后的通知)对应 EvSize,其字段为width/height/state;而EvCalcSize服务于WM_NCCALCSIZE(计算阶段),是修改客户区布局的关键时机。 - 内存安全:
EvCalcSize通过unsafe指针读取NCCALCSIZE_PARAMS/RECT,属于框架内部实现;面向开发者的公开接口只暴露类型安全的Rect字段,因此日常开发无需接触指针。 - 调试建议:客户区计算错误通常表现为窗口内容绘制偏移、尺寸跳动或重绘异常。可在
onCalcSize中打印rect1/cx/cy观察系统给出的原始值,再逐步调整返回标志与矩形修改逻辑。
九、总结
EvCalcSize是 RGF 对WM_NCCALCSIZE的完整封装:构造阶段依据wParam将原始消息解析为calcValidRects、三个Rect与一组WINDOWPOS字段;回调阶段允许开发者自由修改这些字段并通过postProcess()原子写回;返回值则通过WVR_*标志向系统声明工作区保留策略。从 rgf_proc.cj 的实现可以看出,RGF 在保持 Win32 语义完整性的同时,将指针操作与结构体细节全部收敛进框架内部,让仓颉开发者可以用纯面向对象的方式精确控制窗口客户区布局——这正是实现自定义标题栏、异形窗口、可缩放边框等桌面应用高级效果的技术基石。
- 图形学
- 桌面应用
【免费下载链接】RGF_CJ
RGF是仓颉Windows系统下的通用渲染框架(兼应用开发框架)。RGF仓颉版(后续简称"RGF")基于RGF(C/C++版)封装优化而来。RGF为开发者提供轻量化、安全、高性能以及高度一致性的2D渲染能力以及常用的桌面应用开发工具,并且提供对接Direct3D的相关接口,以满足开发者对3D画面渲染的需求。在Windows开发方面,RGF在子包内也提供开箱即用的Windows API,确保熟悉Win32的开发者快速上手。此外,RGF还提供一些常用功能的封装(如XAudio2、MF),以辅助开发者实现音频、摄像头等扩展功能的管理。
相关推荐
RGF 仓颉版 FSize 类详解:2D 渲染框架中浮点尺寸的精确表达
RGF 仓颉版 FSize 类详解:2D 渲染框架中浮点尺寸的精确表达 FSize 是 RGF(仓颉 Windows 通用渲染框架)核心几何值体系中的浮点尺寸类
图形学桌面应用RGF 仓颉版非客户区鼠标事件:EvNcMouse 类解析与实战指南
RGF 仓颉版非客户区鼠标事件:EvNcMouse 类解析与实战指南 导读 :本文深入解析 RGF(仓颉 Windows 通用渲染框架) rgf.rgf_cor
图形学桌面应用RGF 仓颉版事件参数类解析:EvNcActivate 与 WM_NCACTIVATE 非客户区激活处理
RGF 仓颉版事件参数类解析:EvNcActivate 与 WM_NCACTIVATE 非客户区激活处理 本指南深入讲解 RGF 仓颉版(Cangjie)渲染/
图形学桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考