焦點究竟是什么呢?簡單的說,焦點決定了由哪個窗口或者控件接收鍵盤輸入信息,因此,它又被稱作輸入焦點。對于用戶來說,最直觀的感覺是,有光標(biāo)閃動的窗口或者被高亮的控件就有焦點。
很多初級應(yīng)用程序員或者 Windows 用戶有這樣一個誤解,認(rèn)為凡是鼠標(biāo)點擊的窗口就是焦點窗口。當(dāng)出現(xiàn)有的窗口或者控件點擊后沒有反應(yīng)的現(xiàn)象時,就認(rèn)為是焦點出現(xiàn)了問題。事實上,焦點僅僅控制著鍵盤的輸入,而鼠標(biāo)輸入與焦點沒有直接關(guān)系。用戶之所以有這樣的誤解是由于另一個概念,系統(tǒng)的焦點模式(focus mode )。焦點模式?jīng)Q定了鼠標(biāo)如何使一個窗口獲得焦點。一般來說,焦點模式被分為三種:
不同的系統(tǒng)對焦點模式的支持不同,所使用的焦點模型也有很大的區(qū)別。
![]() |
Windows 上默認(rèn)采用 click-to-focus 的焦點模式。這是因為 Windows 操作系統(tǒng)采用的資源管理器 explorer.exe 只支持這一種焦點模式。這也是造成之前所提到的用戶認(rèn)為鼠標(biāo)點擊的窗口就是焦點窗口的錯覺的原因之一。
現(xiàn)在有一些基于 X 的 Windows 窗口管理器,如 blackbox for Windows 等,可以替代explorer。這些窗口管理器就可以支持以上提到的焦點模式。
正如前面所介紹的,焦點決定了哪個窗口可以獲得鍵盤輸入。那么,介紹系統(tǒng)的焦點模型就不能不提到鍵盤輸入。下圖展示的就是 Windows 上的鍵盤輸入模型。
當(dāng)鍵盤中的一個鍵被按下或者被釋放時,鍵盤驅(qū)動會收到鍵盤中斷,獲得該按鍵的掃描碼( scan code )。這是一個與硬件相關(guān)的數(shù)值。驅(qū)動會根據(jù)鍵盤布局將這個掃描碼轉(zhuǎn)換成設(shè)備無關(guān)的虛擬鍵盤碼( virtual-key code ),并生成一個鍵盤消息( WM_KEYDOWN 或者 WM_KEYUP 消息)放在系統(tǒng)輸入隊列中。在任何給定的時刻,只有一個線程與系統(tǒng)輸入隊列連接。系統(tǒng)會將這個消息從系統(tǒng)輸入隊列中取出,發(fā)送給這個線程的輸入消息隊列。該線程的消息循環(huán)又會從本線程的消息隊里取出這個消息,傳遞給合適的窗口處理過程。這樣的輸入模型保證了一個線程的行為不會對其它前程產(chǎn)生影響。例如,如果一個線程掛起了,不會妨礙其他線程接收鍵盤輸入。
那么,哪個線程是“與系統(tǒng)輸入隊列連接的線程”呢,哪個窗口又是這個“合適的窗口”呢?Windows 有它自己的管理方式。
在 Windows 上,窗口消息是以線程為單位進(jìn)行管理的。每個進(jìn)程可能有多個線程在執(zhí)行,每個線程都可以創(chuàng)建窗口。用戶當(dāng)前正在使用的頂層窗口被稱為前景窗口( foreground window ),它位于所有窗口的上面。而創(chuàng)建該窗口的線程就被稱為前景線程( foreground thread )。相應(yīng)地,別的窗口被稱為背景窗口( background window ),創(chuàng)建它們的線程則被稱為背景線程( background thread )。應(yīng)用程序可以使用 SetForegroundWindow 來設(shè)置前景窗口。用戶也可以用鼠標(biāo),或者 ALT+TAB,ALT+ESC 來切換前景窗口。
每個線程內(nèi)部還維護(hù)著自己的活動窗口( active window )和焦點窗口( focus window )。焦點窗口( focus window )實際上是一個窗口的臨時的屬性。擁有焦點的窗口可以從線程的消息隊列中獲得鍵盤消息。焦點窗口的頂層窗口被稱為活動窗口( ActiveWindow )。程序員可以使用 SetFocus 和 SetActiveWindow 來為該線程設(shè)置焦點窗口和活動窗口。
但是,焦點窗口只是一個局部的概念,并不是所有的焦點窗口都可以獲得鍵盤事件。只有前景線程的焦點窗口才能從系統(tǒng)隊列中得到鍵盤事件,而前景線程中的活動窗口是前景窗口。在任何時刻系統(tǒng)中都只可能有一個被激活的窗口,這就是前景窗口。這也就回答了上一節(jié)中的問題:與系統(tǒng)隊列相連接的線程就是前景線程,而那個可以得到鍵盤事件的窗口就是前景線程的焦點窗口。當(dāng)然,Windows 還提供了 AttachThreadInput 方法來合并兩個線程的輸入隊列。本文主要介紹焦點系統(tǒng),對輸入也就不做過多介紹了。
當(dāng)一個線程的焦點窗口從一個窗口改變到另一個窗口的時候,失去焦點的窗口會收到系統(tǒng)發(fā)出的 WM_KILLFOCUS 消息,而得到焦點的窗口會收到 WM_SETFOCUS 消息。
當(dāng)另一個窗口被激活時,系統(tǒng)會向這兩個窗口發(fā)送 WM_ACTIVATE ,并用 wParam 來通知窗口是被激活了或者去激活了。如果被激活的窗口屬于另一個應(yīng)用,那么系統(tǒng)將給這兩個應(yīng)用發(fā)送 WM_ACTIVATEAPP 消息。
事實上,Windows 上采用的是一套簡單、易于理解的焦點系統(tǒng)