在过去,我们从头开始进行大量 GUI 编程。这并不像看起来那么难,但需要几周的时间才能得出结果。
首先你需要一个好的绘图库。这个库的最小功能是绘制剪裁矩形(使用图案)、线条、位图和字体。您可以通过将字体创建为位图来作弊,而裁剪的矩形只是一堆水平线。
现在您至少需要鼠标、键盘和计时器驱动程序(如果操作系统尚未提供)。通常,您将需要检测键、符号键(如 shift 等)、鼠标移动和鼠标点击。基本计时器功能将允许您检测双击。
然后你需要创建一个窗口数据结构。这个数据结构需要有坐标,即一个矩形,链接到父窗口(如果不是顶部窗口),以及窗口函数,即当这个窗口应该处理一些事件时将调用的函数。
一旦您可以在屏幕上绘图,您就需要一些矩形代数函数。您至少需要一个好的函数来计算矩形的交集,以及相对于绝对坐标的快速分辨率。例如 - 如果您的子窗口有父窗口,那么它的 x 和 y 应该递归地添加到父 x 和 y 直到您到达顶部窗口。
此时,您拥有:
- 原始图形功能,
- 窗口结构,
- 鼠标驱动程序、键盘驱动程序和计时器,
- 矩形算术。
现在您可以编写您的主事件收集函数。此功能将一直运行。其目的是检测事件并将消息发送到正确的窗口。什么是事件?好吧,当您启动程序时,存储鼠标 x 和 y 坐标。然后循环检查它们是否发生了变化。如果它们已更改,请在该位置找到窗口...并向其发送 WM_MOUSEMOVE 事件。您的收割功能应处理:
- 鼠标移动
- 鼠标点击
- 鼠标双击(记住最后一次单击和位置,测量时间并确定是否是双击)
- 定时器事件
- 键盘缓冲区更改
...
现在您应该可以向窗口发送事件了。但是你真的需要一个机制。它是消息队列和窗口过程的组合。它通常是这样工作的:每个窗口都有一个窗口过程,它通常接受四个参数:消息 id(即是否是鼠标移动,是否绘制消息)、窗口句柄、参数 1 和参数 2。您可以使用直接调用此窗口过程类似于 send_message 函数的东西。或者您可以通过 post_message 函数向该窗口发送消息。这会将消息放入队列,窗口将一一处理消息,最终接收到这一消息。那么,为什么要直接调用一条消息并将其他消息放入队列中呢?因为优先。您会看到,键盘单击可能会等待一段时间才能被处理。但是窗口重绘必须立即完成,以防止屏幕上出现闪烁和错误数据。
因此,您的 Harvest_events 函数使用 post_message 和 send_message 将消息发送到窗口。您的窗口消息泵使用典型的消息泵来获取它们,如下所示:
while (pmsg = get_message() != NULL) send_message(pmsg->id, pmsg->hwnd, pmsg->p1, pmsg->p2);
get_message 只是从队列中获取消息,然后调用 send message。很简单吧?好吧,并非如此。这样你就只会接收到windows的驱动消息,但你还需要一些函数来重绘窗口、移动它们等。当你创建move_window函数时,resize_window、show_window , 和 hide_window 函数,你的窗口坐标会改变。其他窗口的部分将被覆盖(如果顶部窗口被移动或关闭)。您需要计算哪些窗口受到坐标变化的影响并将绘制消息发送到这些窗口(仅重新绘制未覆盖的部分 - 请记住,您有剪裁绘图功能,所以这将起作用)。
这些函数引入了消息msg_paint、msg_move、msg_resize、msg_hide...
最后,您需要维护窗口的层次结构。您的顶部窗口应该是桌面。它应该有子窗口(应用程序顶部窗口)。这些窗口可能还有更多的子窗口(按钮、编辑框等)。保存这些窗口的明显结构是窗口树。当您检测到鼠标点击时,您必须遍历窗口树并以一种聪明的方式(找出谁有焦点、谁是模态等等)来将消息发送到正确的窗口。当你画画时,你还必须遍历所有的孩子,看看谁没有被发现,谁没有。最后但同样重要的是,您需要将鼠标矩形作为顶部窗口处理,以防止在重新绘制窗口或(使用计时器和 msg_paint 事件)动画时鼠标闪烁。
大概就是这样。