Windows 程式設計/介面
當按下鍵盤上的某個鍵時,訊號會傳送到計算機,核心會接收到它。這些訊號,或者稱為“鍵碼”,是需要轉換為 ASCII 字元的原始資料。核心執行此轉換,並獲取有關按鍵的其他資訊。必要的資訊被編碼到一條訊息中,併發送到當前活動的視窗。同時,訊息也會發送到當前活動的插入符。
在 Windows 內部,存在一個很大的術語問題。如此多的不同事物都需要擁有自己的名稱,以便我們可以用它們進行程式設計,並保持一切井然有序。一個完美的例子是游標和插入符之間的區別。游標是表示滑鼠的圖形影像。它可以是用於指向的箭頭、手形、沙漏或 I 形文字選擇器。另一方面,插入符是用於輸入文字的閃爍物件。當您鍵入時,字母會出現在插入符處,並且插入符會向前移動 1 個空格。務必區分這些術語,因為如果在程式中混淆了它們,則可能需要進行大量除錯。
程式可以選擇處理一些按鍵訊息。需要注意的是,並非所有這些訊息都需要在程式中處理,實際上,通常最好不要處理其中的許多訊息。
- WM_KEYDOWN
- WM_KEYDOWN 訊息指示已按下某個鍵,或已按下並按住該鍵。如果按住該鍵,鍵盤將進入“自動重複”模式,並以一定頻率重複生成按鍵事件。核心將為每個事件生成 WM_KEYDOWN 訊息,其中 LPARAM 訊息元件包含核心連續傳送的訊息數量,以便程式可以選擇忽略某些訊息而不會錯過任何資訊。除了鍵計數之外,LPARAM 還將包含以下資訊
LPARAM 的位 用途 0-15 鍵計數 16-23 掃描碼 29 上下文程式碼 30 先前狀態 31 鍵轉換
- 掃描碼是來自鍵盤的原始二進位制訊號,它可能與字元的 ASCII 值不對應。請注意,鍵盤上的所有按鈕都會生成掃描碼,包括操作按鈕(Shift、ALT、CTRL)。除非您嘗試以特殊方式與鍵盤互動,否則您需要忽略掃描碼。上下文程式碼確定是否同時按下了 ALT 鍵。如果同時按下了 ALT 鍵,則上下文程式碼為 1。先前狀態是生成訊息之前按鈕的狀態。鍵轉換確定是正在按下鍵還是正在釋放鍵。對於大多數程式而言,WM_KEYDOWN 訊息中的大多數字段都可以安全地忽略,除非您嘗試使用自動重複功能,或嘗試在低階與鍵盤互動。
- WM_KEYUP
- 當已按下某個鍵被釋放時,會發送此訊息。每個按鍵至少會生成兩條訊息:按鍵按下(按下按鈕時)和按鍵釋放(釋放按鈕時)。通常對於大多數文字處理應用程式,可以忽略按鍵釋放訊息。WPARAM 是虛擬字元程式碼的值,而 LPARAM 與 WM_KEYDOWN 訊息中的相同。
Windows 使用者無疑會熟悉一些與大型 Windows 程式一起使用的常見鍵組合。CTRL+C 將物件複製到剪貼簿。CTRL+P 列印當前文件。CTRL+S 儲存當前文件。還有數十種其他組合,並且每個程式似乎都有其自己的特定鍵組合。
在 Windows 世界中,這些鍵組合稱為“加速鍵”。使用加速鍵的程式將定義一個加速鍵表。此表將包含所有不同的鍵組合以及它們各自對映到的命令識別符號。當按下鍵盤上的加速鍵時,程式不會接收按鍵訊息,而是接收 WM_COMMAND 訊息,其中 WPARAM 欄位包含命令識別符號。
要將加速鍵組合轉換為 WM_COMMAND 訊息,需要在訊息迴圈中結合使用 TranslateAccelerator 函式,如下所示
while(GetMessage(&msg, NULL, 0, 0))
{
if(!TranslateAccelerator(&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
TranslateAccelerator 函式將自動將 WM_COMMAND 訊息分派到相應的視窗。
每個程式只能擁有 1 個活動插入符,更糟糕的是,整個系統一次只能在螢幕上擁有 1 個活動插入符。使用插入符時,程式設計師需要注意在不使用時銷燬插入符,並在需要時重新建立插入符。這可以透過在 WM_SETFOCUS 訊息(當視窗變為活動狀態時)建立插入符,並在 WM_KILLFOCUS 和 WM_DESTROY 訊息中銷燬插入符來相對輕鬆地完成。
滑鼠關聯的訊息更多,因為滑鼠能夠執行比鍵盤更多的獨特任務。例如,滑鼠至少有 2 個按鈕(通常為 3 個或更多),它通常有一個軌跡球,並且可以懸停在螢幕上的物件上。滑鼠的每個功能都可以透過訊息處理,因此我們需要幾種訊息
程式可以處理許多滑鼠訊息。
- WM_LBUTTONDBLCLK
- 使用者雙擊了滑鼠左鍵。
- WM_LBUTTONDOWN
- 使用者按下了滑鼠左鍵。
- WM_LBUTTONUP
- 使用者釋放了滑鼠左鍵。
- WM_MBUTTONDOWN
- 使用者按下了滑鼠中鍵。
- WM_MBUTTONUP
- 使用者釋放了滑鼠中鍵。
- WM_MOUSEMOVE
- 使用者在視窗的客戶區內移動了滑鼠游標。
- WM_MOUSEWHEEL
- 使用者旋轉或按下了滑鼠滾輪。
- WM_RBUTTONDOWN
- 使用者按下了滑鼠右鍵。
- WM_RBUTTONUP
- 使用者釋放了滑鼠右鍵。
此外,LPARAM 欄位將包含有關游標位置的資訊(以 X-Y 座標表示),而 WPARAM 欄位將包含有關 Shift 和 CTRL 鍵狀態的資訊。
系統將處理圖形滑鼠移動,因此您無需擔心程式會鎖定滑鼠。但是,程式能夠更改滑鼠游標,並在需要時將滑鼠訊息傳送到其他視窗。
計時器用於透過暫停來間隔程式的流程,以便在另一組與結果互動之前允許一組操作進行處理。從嚴格意義上講,Windows 計時器不是使用者輸入裝置,儘管計時器可以向視窗傳送輸入訊息,因此通常以與滑鼠和鍵盤相同的方式對其進行處理。具體來說,Charles Petzold 的著名著作“Programming Windows”將計時器視為輸入裝置。
計時器的常見用法是通知程式暫停結束,以便它可以擦除先前繪製在螢幕上的影像,例如在顯示來自一個資料夾的各種影像的螢幕保護程式中。但是,本機計時器功能對於遊戲或時間關鍵型響應而言並不被認為是準確的。DirectX API 適用於遊戲。
每次分配給計時器的指定時間間隔過去時,系統都會向與計時器關聯的視窗傳送 WM_TIMER 訊息。
新的計時器在由SetTimer函式建立後立即開始計時間隔。建立計時器時,您將檢索一個唯一的識別符號,該識別符號可由KillTimer函式用於銷燬計時器。此識別符號也存在於 WM_TIMER 訊息的第一個引數中。
讓我們看看函式語法。
UINT_PTR SetTimer(
HWND hWnd, //Handle of the window associated to the timer
UINT nIDEvent, //an identifier for the timer
UINT uElapse, //the time-out value, in ms
TIMERPROC lpTimerFunc //the address of the time procedure (see below)
);
BOOL KillTimer(
HWND hWnd, // Handle of the window associated to the timer
UINT_PTR uIDEvent // the identifier of the timer to destroy
);
如果要重置現有計時器,則必須將第一個引數設定為 NULL,並將第二個引數設定為現有計時器 ID。
您可以透過兩種不同的方式處理 WM_TIMER 訊息
- 透過處理作為第一個引數傳遞的視窗的視窗過程中收到的 WM_TIMER 訊息。
- 透過定義一個 TimerProc 回撥函式(第四個引數)來處理訊息,而不是使用視窗過程。
讓我們看看第一個。
#define IDT_TIMER1 1001
#define IDT_TIMER2 1002
...
SetTimer(hwnd, //handle of the window associated to the timer
IDT_TIMER1, //timer identifier
5000, // 5 seconds timeout
(TIMERPROC)NULL); //no timer procedure, process WM_TIMER in the window procedure
SetTimer(hwnd, IDT_TIMER2, 10000, (TIMERPROC)NULL);
...
LRESULT CALLBACK WinProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
...
case WM_TIMER:
switch(wParam)
{
case IDT_TIMER1:
//process the 5 seconds timer
break;
case IDT_TIMER2:
//process the 10 seconds timer
break;
}
...
/* to destroy the timers */
KillTimer(hwnd, IDT_TIMER1);
KillTimer(hwnd, IDT_TIMER2);
現在使用一個 TimerProc。
VOID CALLBACK TimerProc(
HWND hwnd, // handle of window for timer messages
UINT uMsg, // WM_TIMER message
UINT idEvent, // timer identifier
DWORD dwTime // current system time
);
它由系統呼叫,以處理關聯的 Timer 的 WM_TIMER 訊息。讓我們看一些程式碼。
#define IDT_TIMER1 1001
...
/* The Timer Procedure */
VOID CALLBACK TimerProc(HWND hwnd,
UINT uMsg,
UINT idEvent,
DWORD dwTime)
{
MessageBox(NULL, "One second is passed, the timer procedure is called, killing the timer", "Timer Procedure", MB_OK);
KillTimer(hwnd, idEvent);
}
...
/* Creating the timer */
SetTimer(hwnd, IDT_TIMER1, 1000, (TIMERPROC)TimerProc);
...
定時器只帶有一個訊息,WM_TIMER,並且 WPARAM 欄位將包含定時器 ID 號。