技术演进中的开发沉思-53 DELPHI VCL系列:windows的消息(下):TApplication窗体

今天我们梳理下关于TApplication的窗体消息下半部分的内容。前面也说过,在 Delphi 的世界里,TApplication 就像一位经验丰富的总工程师,而主窗体则是它倾注心血打造的核心建筑。如果你第一次在实验室里敲出 Delphi 代码时,屏幕上弹出的空白窗体像块刚裁好的画布,其实这块画布的诞生藏着一整套精密的 "施工流程"。今天我们就循着TApplication代码的脉络,揭开这场数字建造的神秘面纱,感受那些隐藏在函数调用背后的匠心与智慧。

一、主窗体的诞生

创建主窗体的过程,恰似老木匠打造一张八仙桌 —— 先立框架,再装榫卯,最后上漆打磨,每一步都暗藏玄机,缺一不可。

1、TWinControl.Create

当你在 Delphi 里拖出第一个 TForm 时,调试窗口闪过的 TWinControl.Create 这就像盖房子时挖掘机挖出的第一方土。它在内存里开辟出一块专属空间,给窗体的所有 "器官"—— 按钮、文本框、滚动条都预留了位置。就像老家盖房时,父亲总要先丈量地基尺寸,用石灰画出轮廓,TWinControl.Create 就是那个用代码画轮廓的 "老把式"。

记得大学里用的Delphi 4,每次新建工程都会自动生成这句隐含的创建代码。有次误删了窗体的父类引用,编译时跳出的 "Cannot create form" 错误,像极了地基没打好就想砌墙的荒唐。

2、TForm.HandleNeeded

当窗体刚创建时读不到句柄值。这就像去传达室领访客证,你不主动要,人家不会给。当程序需要调用 Windows API 操作窗体时,就得靠 HandleNeeded"提醒" 系统准备好句柄。记得有次做屏幕截图功能,刚创建的窗体还没显示就调用 GetDC,结果返回了 0。加上 HandleNeeded 后,就像给保安出示了身份证,系统才肯交出操作权限。这函数本身不生成句柄,却像个尽责的秘书,确保你要用的时候,"通行证" 已经备好。

3、TForm.CreateHandle

HandleNeeded 只是提醒,真正打造句柄的是 TForm.CreateHandle。这就像派出所制作身份证的过程 —— 把你的个人信息(窗体属性)录入系统,生成唯一编号(句柄值)。在多窗体程序,发现两个窗体的句柄偶尔会重复。跟踪源码才发现,是自定义窗体类里重写 CreateHandle 时忘了调用 inherited。就像补办身份证时没走正规流程,拿到的号码自然可能冲突。正常情况下,这个函数会调用 Windows 的 CreateWindowEx,把窗体的各种属性翻译成系统能理解的语言,最终生成那个独一无二的整数句柄。

4、TWinControl.CreateWnd 与 TForm.CreateWnd

如果说句柄是身份证,那 CreateWnd 就是搭建窗体的 "钢筋骨架"。TWinControl.CreateWnd 是所有窗口控件的通用骨架,而 TForm.CreateWnd 则在此基础上增加了窗体特有的结构。记得做异形窗体时的经历:想做个圆形登录界面,重写了 CreateWnd 却总失败。后来发现,TWinControl.CreateWnd 已经处理了窗口的基本创建逻辑,我直接覆盖父类方法相当于拆了承重墙。正确的做法是先调用 inherited 执行父类的创建流程,再在后面添加自定义形状的代码,就像先按标准图纸建起框架,再切割出圆形的门窗。

5、TForm 的父代类别 TScrollingWinControl

TScrollingWinControl 是个特别的存在,它就像给窗体装了可伸缩的阳台。如数据一多就超出窗体范围,这时候就可用这个父类自带的滚动功能。它内部维护着滚动条的状态,当控件内容超过显示区域时自动显示滚动条,就像阳台根据需要伸出缩进。记得有次自定义滚动逻辑,发现即使隐藏滚动条,它依然在后台计算内容偏移量。这种封装特别巧妙 —— 开发者不用关心滚动条如何与内容联动,只需设置 AutoScroll 属性,剩下的交给这个父类处理,就像按下按钮,阳台自动根据需要调整长度。

6、VCL Framework 的窗口 thunk 回叫函式

InitWndProc 是 VCL 里的 "通信兵",负责把 Windows 系统的消息传递给 Delphi 的对象方法。这个 thunk 技术特别精妙,能把 C 风格的回调函数转换成面向对象的方法调用。系统发送的 WM_PAINT 消息,正是通过 InitWndProc 这个中转站,最终变成了 TForm 的 OnPaint 事件。它就像跨国电话的转接站,把系统的 "信号" 翻译成 VCL 能理解的 "语言",确保消息准确送达对应的处理方法。当年为了理解这个机制,我对着汇编代码啃了三天,才明白这层转换背后的匠心。

7、TForm.CreateParams

每次创建窗体前,CreateParams 都会先画好 "施工蓝图"。它设置的参数决定了窗体的样式 —— 是对话框还是主窗口,有没有边框,能否最大化。如果你做一个控制界面,需要去掉标题栏。修改 CreateParams 里的 Style 参数,把 WS_CAPTION 去掉,窗体立刻变成了无边框的样子,就像按图纸拆掉了屋顶的房檐。这个函数的神奇之处在于,它把复杂的窗口样式常量,用面向对象的方式组织起来,开发者不用记住那些晦涩的常量值,只需设置 BorderStyle 等属性,CreateParams 会自动翻译成对应的样式参数。

8、TCustomForm.CreateWindowHandle

经过前面这么多步骤,最后由 TCustomForm.CreateWindowHandle 完成 "最后一千米" 的施工。它拿着 CreateParams 生成的 "图纸",调用系统 API 真正创建窗口。例如:你开发中窗体创建后总是在屏幕外。跟踪到 CreateWindowHandle 会发现,是自定义的位置参数计算错误。这个函数就像施工队的最后一道工序,把所有设计参数落实到实际的窗口创建中,最终让窗体在屏幕上显现出具体的样子。看着调试器里它返回的 True 值,就像看到建筑竣工验收合格的证书。

二、窗体的 “通信系统”:窗口讯息处理机制

窗体处理窗口讯息的机制,宛如一个高效的邮局,能够精准地分拣和处理各种信息。

想象一下,当你点击窗体上的关闭按钮时,一个 “关闭” 讯息就像一封信件被发送到窗体的 “邮局”。窗体内部的讯息处理机制会迅速接收这封信,并按照既定的规则进行处理。

这里有个有趣的例子,我们可以通过拦截窗口讯息来改变窗体的属性。比如,我们可以拦截 WM_CLOSE 讯息,让窗体在收到关闭指令时不立即关闭,而是弹出一个提示框询问用户是否真的要关闭。就像邮局收到一封加急信件,我们可以先检查信件内容,再决定是否投递。

以下是一个简单的代码示例,用于拦截 WM_CLOSE 讯息:


unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls;typeTForm1 = class(TForm)procedure FormCreate(Sender: TObject);private{ Private declarations }procedure WMCLOSE(var Msg: TWMClose); message WM_CLOSE;public{ Public declarations }end;varForm1: TForm1;implementation{$R *.dfm}procedure TForm1.FormCreate(Sender: TObject);beginend;procedure TForm1.WMCLOSE(var Msg: TWMClose);beginif MessageDlg('确定要关闭窗体吗?', mtConfirmation, [mbYes, mbNo], 0) = mrYes thenbegininherited; // 调用父类的处理方法,执行关闭操作end;end;end.

在这个例子中,我们通过定义 WMCLOSE 方法并指定它处理 WM_CLOSE 讯息,实现了对关闭讯息的拦截和自定义处理。

三、TApplication 的设计思想

总的来说,TApplication 就像一位指挥家,在整个应用程序中发挥着统筹协调的作用。它负责管理应用程序的生命周期,协调各个窗体和组件之间的工作,确保整个程序能够有条不紊地运行。在桌面开发时代,Delphi 的 TApplication 设计展现出了巨大的优势。它将复杂的底层操作封装起来,让开发者能够专注于业务逻辑的实现,就像指挥家不需要亲自演奏每一种乐器,却能让整个乐队奏出和谐的乐章。

最后小结

从 web1.0 到移动互联网时代,技术在不断变迁,但 TApplication 所体现的封装、协调的设计思想却一直影响着后来的开发框架。它告诉我们,一个优秀的框架应该像一位贴心的助手,为开发者屏蔽复杂的细节,让开发过程变得更加轻松高效。回顾 Delphi VCL Application开发的这些技术点,就像翻开一本记录着数字建筑历史的相册。2如今都成了技术成长的注脚。每一个函数、每一个机制都承载着开发者的智慧和汗水,它们共同构建了 Delphi 辉煌的过去,也为我们今天的技术发展提供了宝贵的借鉴,不能忘记,还要继续前行。未完待续............

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

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

相关文章

cesium FBO(四)自定义相机渲染到Canvas(离屏渲染)

前面几节的例子是将Cesium默认的相机渲染到纹理(RTT)或Canvas,这片文章讲解如何将自定义的一个camera的画面渲染到Canvas上,有了前面几篇的基础了,也能将自定义的画面渲染纹理、也可以灰度处理,原理是一样的…

双机并联无功环流抑制虚拟阻抗VSG控制【simulink仿真模型实现】

双机并联虚拟同步发电机(VSG)系统中,因线路阻抗不匹配及参数差异,易引发无功环流。本方案在传统VSG控制基础上,引入自适应虚拟阻抗环节。其核心在于:实时检测两机间无功环流分量,据此动态调节各…

python测试总结

测试题的基础知识点总结 1.循环求和 for循环步长(range(2,101,2)) while循环条件判断(i%20) 生成器表达式(sum(i for i in range )) 所以:sum(range(1,101,2))(奇数和)和…

识别和分类恶意软件样本的工具YARA

YARA 是一个用于识别和分类恶意软件样本的工具,广泛应用于恶意软件分析、威胁情报、入侵检测等领域。它通过编写规则(YARA Rules)来匹配文件中的特定字符串、十六进制模式、正则表达式等特征。 一、YARA 的基本使用方法 1. 安装 YARA Linux(Ubuntu/Debian) sudo apt-ge…

GaussDB 约束的语法

1 约束的作用约束是作用于数据表中列上的规则,用于限制表中数据的类型。约束的存在保证了数据库中数据的精确性和可靠性。约束有列级和表级之分,列级约束作用于单一的列,而表级约束作用于整张数据表。下面是 GaussDB SQL 中常用的约束。NOT …

SecurityContextHolder 管理安全上下文的核心组件详解

SecurityContextHolder 管理安全上下文的核心组件详解在 Spring Security 中,SecurityContextHolder 是​​安全上下文(Security Context)的核心存储容器​​,其核心作用是​​在当前线程中保存当前用户的认证信息(如用…

c++详解系列(引用指针)

目录 1.什么是引用 2.引用的定义 3.引用的特性 4.引用的使用 4.1引用传参 4.2传引用返回 5.const引用(在引用的定义前用const修饰) 5.1对于引用 5.2对于指针 6.引用&指针 总结 1.什么是引用 引用就是给变量起别名,一个变量可以…

深度学习loss总结(二)

对于目前深度学习主流任务学习,loss的设置至关重要。下面就不同任务的loss设置进行如下总结: (1)目标检测 2D/3D目标检测中的 Loss(损失函数)是训练模型时优化目标的核心,通常包括位置、类别、尺寸、方向等多个方面。以下是目前 常见的 2D 和 3D 目标检测 Loss 分类与…

【Linux网络】netstat 的 -anptu 各个参数各自表示什么意思?

netstat 是一个网络统计工具,它可以显示网络连接、路由表、接口统计、伪装连接和多播成员资格。在 netstat 命令中,不同的参数可以用来定制输出的内容。 你提到的 -anptu 参数组合各自的功能如下: -a (all): 显示所有活动的连接和监听端口。它…

[硬件电路-115]:模拟电路 - 信号处理电路 - 功能放大器工作分类、工作原理、常见芯片

功能放大器是以特定功能为核心的集成化放大电路,通过将运算放大器与外围电阻、电容等元件集成在单一芯片中,实现标准化、高性能的信号放大功能。其核心优势在于简化设计流程、提升系统稳定性,并针对特定应用场景优化性能参数。以下从定义、分…

双网卡UDP广播通信机制详解

UDP广播通信机制详解 一、通信流程分析 发送阶段 通过Client.Bind(192.168.0.3, 60000)将UDP套接字绑定到指定网卡和端口设置RemoteHost "255.255.255.255"实现全网段广播数据流向:192.168.0.3:60000 → 255.255.255.255:50000 接收阶段 设备响应数据应返…

从遮挡难题到精准测量:激光频率梳技术如何实现深孔 3D 轮廓的 2um 级重复精度?

一、深孔 3D 轮廓测量的遮挡困境深孔结构(如航空发动机燃油喷嘴孔、模具冷却孔)因孔深大(常超 100mm)、深径比高(>10:1),其 3D 轮廓测量长期受限于光学遮挡难题。传统光学测量技术&a…

.NET 依赖注入(DI)全面解析

文章目录一、依赖注入核心原理1. 控制反转(IoC)与DI关系2. .NET DI核心组件二、服务生命周期1. 三种生命周期类型三、DI容器实现原理1. 服务注册流程2. 服务解析流程四、高级实现方法1. 工厂模式注册2. 泛型服务注册3. 多实现解决方案五、ASP.NET Core中的DI集成1. 控制器注入2…

K8S部署ELK(二):部署Kafka消息队列

目录 1. Kafka 简介 1.1 Kafka 核心概念 (1)消息系统 vs. 流处理平台 (2)核心组件 1.2 Kafka 核心特性 (1)高吞吐 & 低延迟 (2)持久化存储 (3)分…

Rust进阶-part1-智能指针概述-box指针

Rust进阶[part1]_智能指针概述&box指针 智能指针概述 在Rust中,智能指针是一类特殊的数据结构,它们不仅像普通指针一样可以引用数据,还带有额外的元数据和功能。与普通指针不同,智能指针通常使用结构体实现,并且会实现 Deref 和 Drop 等特定的trait,以提供更强大的…

C++扩展 --- 并发支持库(补充1)

C扩展 --- 并发支持库(下)https://blog.csdn.net/Small_entreprene/article/details/149606406?fromshareblogdetail&sharetypeblogdetail&sharerId149606406&sharereferPC&sharesourceSmall_entreprene&sharefromfrom_link atom…

在Three.js中导入和添加自定义网格的最佳实践 - 综合指南

探索在Three.js中导入和添加自定义网格的最佳实践。本指南涵盖增强 3D 项目的技术、技巧和实际示例。 添加图片注释,不超过 140 字(可选) 强烈建议使用 GLTF 格式来集成 3D 几何体,提供简化的流程,并固有地支持动画、…

Redis知识点(1)

目录 Redis Redis和MySQL的区别 Redis的高可用方案 Redis可以用来做什么 Redis的数据类型 字符串 列表 哈希 集合 有序集合 Bitmap Redis为什么快呢? I/O多路复用 说说select,poll,epoll,kqueue,IOCP的区别 Redis为什么早期选择单线程? …

使用iptables封禁恶意ip异常请求

查看后端日志发现一IP(103.76.250.29)频繁请求不存在的资源路径​​(如 /api/v1/guest/comm/config、/theme/default/assets/compoments.js 等),并伴随对根路径 / 的正常访问。这种行为的可能性包括恶意扫描、自动化工…

BehaviorTree.Ros2 编译教程

1. 源码下载 git clone https://github.com/BehaviorTree/BehaviorTree.ROS2.git2. 编译过程 源码中有3个项目: btcpp_ros2_interfacesbtcpp_ros2_interfacesbtcpp_ros2_samples 2.1 编译btcpp_ros2_interfaces: colcon --packages-select btcpp_ros2_interfaces2.2 编译 …