概述无论是 Windows 应用程序还是 Linux 应用程序的开发人员,焦点( focus )都是一个非常常见的一个概念。那么焦点究竟是什么呢?简单的说,焦点决定了由哪个窗口或者控件接收键盘输入信息,因此,它又被称作输入焦点。对于用户来说,最直观的感觉是,有光标闪动的窗口或者被高亮的控件就有焦点。
很多初级应用程序员或者 Windows 用户有这样一个误解,认为凡是鼠标点击的窗口就是焦点窗口。当出现有的窗口或者控件点击后没有反应的现象时,就认为是焦点出现了问题。事实上,焦点仅仅控制着键盘的输入,而鼠标输入与焦点没有直接关系。用户之所以有这样的误解是由于另一个概念,系统的焦点模式(focus mode )。焦点模式决定了鼠标如何使一个窗口获得焦点。一般来说,焦点模式被分为三种:
- click-to focus :这种模式就是指鼠标点击的窗口就可获得焦点。这是Windows用户最常见的一种模式。鼠标点击的窗口会被激活,被置于所有窗口的最前面,并接收键盘输入。
- focus-follow-mouse :有的地方也称这种模式为 pointer focus 。它是指鼠标下的窗口可以获取焦点。当鼠标移到一个可以获得焦点的窗口的范围内,用户不需要点击窗口的某个地方就可以激活这个窗口,接收键盘输入。但是,这个窗口不一定会被置于所有窗口的最前面。当鼠标移出这个窗口的范围时,这个窗口也会随之失去焦点。
- sloppy focus :这种模式与 focus-follow-mouse 一样,当鼠标移至某窗口的范围内的时候,该窗口会获得焦点。与 focus-follow-mouse 不同的是,当鼠标移出这个窗口范围时,焦点也不会随之改变。只有当鼠标移动到别的可以接收焦点的窗口时,系统焦点才改变。
不同的系统对焦点模式的支持不同,所使用的焦点模型也有很大的区别。
Linux 与 Windows 焦点系统Linux 使用的焦点模型与 Windows 有着很大的区别。对于大多数用户来说,Windows 的焦点模型较为直观、易于理解,因此,本节中我们将以Windows 上的焦点模型为引子,着重介绍 Windows 与 Linux 上焦点系统的基本概念,并讨论两个平台上焦点系统的区别。
Windows 上的焦点系统Windows 上默认采用 click-to-focus 的焦点模式。这是因为 Windows 操作系统采用的资源管理器 explorer.exe 只支持这一种焦点模式。这也是造成之前所提到的用户认为鼠标点击的窗口就是焦点窗口的错觉的原因之一。
现在有一些基于 X 的 Windows 窗口管理器,如 blackbox for Windows 等,可以替代explorer。这些窗口管理器就可以支持以上提到的焦点模式。
正如前面所介绍的,焦点决定了哪个窗口可以获得键盘输入。那么,介绍系统的焦点模型就不能不提到键盘输入。下图展示的就是 Windows 上的键盘输入模型。
图 1 Windows 上键盘输入模型当键盘中的一个键被按下或者被释放时,键盘驱动会收到键盘中断,获得该按键的扫描码( scan code )。这是一个与硬件相关的数值。驱动会根据键盘布局将这个扫描码转换成设备无关的虚拟键盘码( virtual-key code ),并生成一个键盘消息( WM_KEYDOWN 或者 WM_KEYUP 消息)放在系统输入队列中。在任何给定的时刻,只有一个线程与系统输入队列连接。系统会将这个消息从系统输入队列中取出,发送给这个线程的输入消息队列。该线程的消息循环又会从本线程的消息队里取出这个消息,传递给合适的窗口处理过程。这样的输入模型保证了一个线程的行为不会对其它前程产生影响。例如,如果一个线程挂起了,不会妨碍其他线程接收键盘输入。
那么,哪个线程是“与系统输入队列连接的线程”呢,哪个窗口又是这个“合适的窗口”呢?Windows 有它自己的管理方式。
在 Windows 上,窗口消息是以线程为单位进行管理的。每个进程可能有多个线程在执行,每个线程都可以创建窗口。用户当前正在使用的顶层窗口被称为前景窗口( foreground window ),它位于所有窗口的上面。而创建该窗口的线程就被称为前景线程( foreground thread )。相应地,别的窗口被称为背景窗口( background window ),创建它们的线程则被称为背景线程( background thread )。应用程序可以使用 SetForegroundWindow 来设置前景窗口。用户也可以用鼠标,或者 ALT+TAB,ALT+ESC 来切换前景窗口。
每个线程内部还维护着自己的活动窗口( active window )和焦点窗口( focus window )。焦点窗口( focus window )实际上是一个窗口的临时的属性。拥有焦点的窗口可以从线程的消息队列中获得键盘消息。焦点窗口的顶层窗口被称为活动窗口( ActiveWindow )。程序员可以使用 SetFocus 和 SetActiveWindow 来为该线程设置焦点窗口和活动窗口。
但是,焦点窗口只是一个局部的概念,并不是所有的焦点窗口都可以获得键盘事件。只有前景线程的焦点窗口才能从系统队列中得到键盘事件,而前景线程中的活动窗口是前景窗口。在任何时刻系统中都只可能有一个被激活的窗口,这就是前景窗口。这也就回答了上一节中的问题:与系统队列相连接的线程就是前景线程,而那个可以得到键盘事件的窗口就是前景线程的焦点窗口。当然,Windows 还提供了 AttachThreadInput 方法来合并两个线程的输入队列。本文主要介绍焦点系统,对输入也就不做过多介绍了。
当一个线程的焦点窗口从一个窗口改变到另一个窗口的时候,失去焦点的窗口会收到系统发出的 WM_KILLFOCUS 消息,而得到焦点的窗口会收到 WM_SETFOCUS 消息。
当另一个窗口被激活时,系统会向这两个窗口发送 WM_ACTIVATE ,并用 wParam 来通知窗口是被激活了或者去激活了。如果被激活的窗口属于另一个应用,那么系统将给这两个应用发送 WM_ACTIVATEAPP 消息。
事实上,Windows 上采用的是一套简单、易于理解的焦点系统。Linux 的焦点系统远比Windows 的复杂得多。 |