鍵盤
雖然鍵盤在Windows CE中作用減少了,但鍵盤依然是錄入大量信息的最好方法。即使像在Pocket PC這類沒有物理鍵盤的系統上,用戶使用最多的也還是軟鍵盤--在觸摸屏上模擬鍵盤的控件。基於此,除了極其特殊的Windows CE應用程序外,對鍵盤輸入的適當操作是很重要的。雖然在本書後面章節我會詳細討論軟鍵盤,但有一點應該先提一下。對於應用程序,軟鍵盤的輸入同傳統硬件鍵盤的輸入是沒什麼不同的。
輸入焦點
在Windows操作系統下,同時只有一個窗口擁有輸入焦點。有焦點的窗口接收所有鍵盤輸入,直到焦點切換到另外一個窗口。雖然系統使用很多規則來分配鍵盤焦點,但通常有焦點的窗口就是當前活動窗口。活動窗口就是用戶當前正在交互的頂層窗口。除了極少數例外,活動窗口通常位於Z座標的頂部,也就是說,活動窗口是繪製在系統裏其它窗口上面的。在資源瀏覽器(Explorer)裏,用戶可以按Alt-Esc鍵在程序間切換,以改變活動窗口,或者在任務欄裏點另一個頂層窗口的按鈕來切換活動窗口。焦點窗口是活動窗口或者其子窗口之一。
Windows下,程序可以通過調用GetFocus來判斷哪個窗口擁有輸入焦點,函數原型如下:HWND GetFocus (void);通過調用SetFocus可以把焦點切換到另外一個窗口,函數原型如下:HWND SetFocus (HWND hWnd);
在Windows CE下,對SetFocus的目標窗口有一些限制。通過SetFocus來獲取焦點的窗口必須是調用SetFocus的線程創建的窗口。該規則的一個例外是:如果失去焦點的窗口和即將獲得焦點的窗口是父子或兄弟關係,那即使這兩個窗口是被不同線程創建的,也可以切換焦點。
當窗口失去焦點時,Windows會給該窗口發送WM_KILLFOCUS消息,通知窗口新的狀態信息。wParam參數則包含即將獲得焦點的窗口句柄。獲得焦點的窗口會收到WM_SETFOCUS消息,消息的wParam參數包含了失去焦點的窗口的句柄。
還要再叮囑一下。程序不應該在沒有用戶輸入的情況下改變焦點窗口。否則,用戶很容易變的迷惑。SetFocus的一個適當用途是給活動窗口裏的子窗口(更多是控件)設置輸入焦點。在這種情況下,程序讓想接收鍵盤消息的窗口用其子窗口的句柄來調用SetFocus,以響應WM_SETFOCUS消息。
鍵盤消息
除了一些小的例外,Windows CE與桌面版的Windows具有相同的鍵盤消息處理過程,當鍵被按下,Windows給焦點窗口發送一系列消息,通常都是以WM_KEYDOWN消息開始的。如果被按下的鍵代表諸如字母或數字等字符,Windows會在WM_KEYDOWN之後發送一個WM_CHAR消息。(一些按鍵,例如功能鍵和光標鍵等,不代表字符,則不會發送WM_CHAR。對這些按鍵,程序必須翻譯WM_KEYDOWN消息來了解這些按鍵是什麼時候被按下的。)當按鍵被釋放,Windows會發送一個WM_KEYUP消息。如果按鍵按的時間長一些,則自動重複功能就會開啓,多條WM_KEYDOWN消息和WM_CHAR消息會被送出,直到最後鍵被釋放,發出WM_KEYUP消息。當Alt鍵和另一個鍵一起被按下時,上面討論的消息會被WM_SYSKEYDOWN、WM_SYSKEYCHAR和WM_SYSKEYUP消息替代。
對所有這些消息,幾乎都按相同的方式使用參數wParam和lParam。對WM_KEYxx和WM_SYSKEYxx消息,wParam包含虛擬鍵值,用於指出當前被按下的鍵。所有版本的Windows都在鍵盤硬件和應用程序之間提供了一箇中間層,用於把鍵盤返回的掃描碼轉換成虛擬鍵盤值。表3-1列出了VK_xx值及對應的鍵。雖然虛擬鍵表很大,但不是所有表中列的鍵都能用於Windows CE設備。例如,作爲PC鍵盤上很主要的鍵並列在虛擬鍵表中的功能鍵,卻並沒有出現在大部分Windows CE鍵盤上。實際上,PC鍵盤上的許多鍵都從空間受限的Windows CE鍵盤上去除了。圖3-1給出了通常很少用在Windows CE設備上的鍵列表,該表只是告訴你這些鍵在Windows CE鍵盤上可能不存在,但並不是說絕對不存在。
表3-1:虛擬鍵
略
圖3-1:PC鍵盤中很少用於Windows CE鍵盤的鍵
對WM_CHAR和WM_SYSCHAR消息來說,wParam包含鍵對應的Unicode字符。多數情況下,應用程序只是處理WM_CHAR消息,而忽略WM_KEYDOWN和WM_KEYUP消息。WM_CHAR消息作爲第2級抽象,使應用程序不必考慮鍵的釋放或按下狀態,而將精力集中在鍵盤輸入的字符上。
這些鍵盤消息中的lParam值含有關於按下的鍵的進一步的信息。圖3-2給出了lParam參數的格式。
從0到15的底字位,包含鍵的重複次數。有時Windows CE設備上的鍵被按的很快,超過了Windows CE發送消息到焦點應用程序的速度。在這種情況下,重複計數器包含鍵被按下的次數。第29比特位是上下文標誌位。如果鍵被按下的同時,Alt鍵被按下,則設置該位。30位是鍵的先前狀態。如果鍵先前是按下的,則該位被設置,否則該位是0。該標誌可以用來判斷鍵消息是否是自動重複的結果。31位指出轉換狀態。如果鍵從按下轉成釋放,該位被設置。16-28位用來指出鍵的掃描碼。在許多情況下,Windows CE並不支持該域。然而,在一些掃描碼作爲必需品的新的Windows CE平臺上,這個域包含掃描碼。除非您知道在您的特定平臺支持它,否則您不應該設想掃描碼域是可用的。
圖3-2:lParam用於鍵消息時的佈局
一個額外的鍵盤消息,WM_DEADCHAR,有時可能會有用。當按下的鍵代表一個諸如元音變音的死字符,用它和一個字符組合起來創建一個不同的字符的時候,您需要發送該消息。在這種情況下,WM_DEADCHAR消息用來防止光標移動到下一格,直到第2個鍵被按下,這樣您可以完成組合後的字符。
WM_DEADCHAR消息在Windows下總是有的,但在Windows CE下它的作用可能更大一點。隨着運行Windows CE的小消費品設備國際化,程序員應該,也有必要使用在外語系統裏經常需要的WM_DEADCHAR消息。
鍵盤函數
您會發現用於判斷鍵盤狀態的函數對Windows應用程序是很有用的。在鍵盤函數中,有兩個密切相關但又經常混淆的函數:GetKeyState和GetAsyncKeyState。
GetKeyState的原型如下:
short GetKeyState (int nVirtKey);
它返回大小寫字母鍵盤(換擋鍵)、Ctrl、Alt和Shift的按下/釋放狀態,指出這些鍵是否在轉換狀態。如果鍵盤有兩個鍵具有同樣的功能,例如,鍵盤兩邊的那兩個Shift鍵,則該函數可以用來區分是哪個被按下。(大部分鍵盤有左右Shife鍵,一些有左右Ctrl和Alt鍵。)
nVirKey表示要查詢的鍵的虛擬鍵碼。如果返回值的高位被設置,則該鍵被按下。如果返回值的最低有效位被設置,則該鍵在轉換狀態;也就是說,自從系統啓動開始,該鍵已被按下奇數次。返回的狀態是消息從消息隊列裏讀出時的狀態,並不是鍵的實時狀態。需要注意的是,Alt鍵的虛擬鍵是VK_MENU。
注意GetKeyState函數在Windows CE下查詢換擋鍵的狀態是受限制的。在Windows的其它版本里,GetKeyState可以用來判斷鍵盤上的每個鍵的狀態。
要判斷鍵的實時狀態,可以使用short GetAsyncKeyState(int vKey);
同GetKeyState一樣,傳入要查詢的鍵的虛擬鍵碼。GetAsyncKeyState函數返回值同GetKeyState的略微不同。和GetKeyState一樣,當鍵被按下,返回值的高位被設置。然而,如果在上次調用GetAsyncKeyState後,鍵被按下,則最低有效位也被設置。同GetKeyState一樣,GetAsyncKeyState可以區分左右Shift、Ctrl及Alt鍵。另外,通過傳遞虛擬值VK_LBUTTON,GetAsyncKeyState可以判斷觸摸筆是否正在觸摸屏幕。
通過keybd_event函數,應用程序可以模擬擊鍵動作。
void keybd_event(BYTE bVk, BYTE bScan, DWORD dwFlags, DWORD dwExtraInfo);
第一個參數是要模擬的鍵的虛擬鍵碼。bScan碼在Windows CE下應該設置爲NULL。dwFlags參數可以取兩個可能的值:KEYEVENTF_KEYUP,表示該調用是模擬鍵盤釋放事件,KEYEVENTF_SILENT表示模擬的鍵盤按下動作不會產生通常的鍵盤滴答聲。所以,要完全模擬鍵按下操作,keydb_event應該調用2次。一次不帶KEYEVENTF_KEYUP,來模擬鍵按下,之後在用KEYEVENTF_KEYUP調用一次來模擬鍵盤釋放。當模擬換擋鍵時,要指出具體的左右VK碼,比如VK_LSHIF或VK_RCONTROL。
Windows CE特有的函數是
BOOL PostKeybdMessage (HWND hwnd, UINT VKey, KEY_STATE_FLAGS KeyStateFlags, UINT cCharacters, UINT *pShiftStateBuffer, UINT *pCharacterBuffer );
該函數發送一系列的鍵到指定的窗口。hwnd參數是目標窗口,該窗口必須是調用線程所擁有的。VKey參數爲0。KeyStateFlags爲所有即將發送的鍵規定鍵的狀態。cCharacters參數指定要發送的鍵的數量。pShiftStateBuffer參數指向一個數組,包含了每個鍵的shift狀態;pCharacterBuffer指向鍵的VK碼。不像keybd_event,該函數不改變鍵盤的全局狀態。
最後一個鍵盤函數MapVirtualKey把虛擬鍵碼翻譯成字符。Windows CE下的MapVirtualKey並不進行鍵盤掃描碼和虛擬碼之間的轉換,雖然在Windows的其它版本里它會這麼做。函數原型如下:
UINT MapVirtualKey (UINT uCode, UINT uMapType);
Windows CE下,第一個參數是待翻譯的虛擬鍵碼,第二個參數uMapType指出如何翻譯鍵碼。MapVirtualKey依賴於實現支持功能的鍵盤設備驅動程序。許多OEM設備並不實現這種支持函數,所以在這種系統上,MapVirtualKey會失敗。
鍵盤測試
要判斷系統裏是否有鍵盤,可以調用DWORD GetKeyboardStatus (VOID);
如果系統裏鍵盤,則該函數返回KBDI_KEYBOARD_PRESENT標誌。如果鍵盤啓用,則該函數返回KBDI_KEYBOARD_ENABLED標誌。要讓鍵盤失效,可以把bEnable設置爲FALSE來調用BOOL EnableHardwareKeyboard (BOOL bEnable)。對鍵盤摺疊在屏幕後面的系統,您可能需要使鍵盤失效,因爲在這種系統裏,當使用觸摸筆時,用戶可能會意外地按下鍵盤。
KeyTrac 示例程序
下面的示例程序KeyTrac顯示了鍵盤消息的順序。從程序上講,KeyTrac與書中早期的程序差別不大。不同點在於所有鍵盤消息都被捕獲和記錄在數組裏,並在WM_PAINT消息裏被顯示出來。對每個鍵盤消息,消息名稱記錄、wParam、lParam值以及指出轉換鍵狀態的標誌集合都被記錄下來。鍵盤消息之所以被記錄到數組裏是因爲這些消息比重繪操作要產生的快。圖3-3顯示了一些鍵被按下後的KeyTrac窗口。
圖3-3略:Shift-A鍵組合以及小寫a鍵被按下後的KeyTrac窗口
瞭解鍵盤消息順序的最好方式是運行KeyTrac,按一些鍵,之後觀察消息滾動顯示在屏幕上。按一個字符鍵,例如a,會產生三個消息:WM_KEYDOWN,WM_CHAR和WM_KEYUP。按a的同時按下Shift鍵,隨後釋放Shift鍵,會產生Shift鍵按下的消息,隨後是爲a鍵產生的三個消息,最後是爲Shift鍵產生的鍵釋放消息。因爲Shift鍵本身不是字符鍵,所以沒有發送WM_CHAR消息。然而,在a鍵對應的WM_CHAR消息中,現在wParam包含的是0x41,表示一個大寫的A被輸入,而不是小寫的a。清單3-1列出了KeyTrac的源代碼。
清單3-1:KeyTrac程序
KeyTrac.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT { // Structure associates
UINT Code; // messages
// with a function.
LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};
//----------------------------------------------------------------------
// Program-specific defines and structures
//
typedef struct {
UINT wKeyMsg;
INT wParam;
INT lParam;
LPCTSTR pszMsgTxt;
TCHAR szShift[20];
} MYKEYARRAY, *PMYKEYARRAY;
// Structure to associate messages with text name of message
typedef struct {
UINT wMsg;
LPCTSTR pName;
} KEYNAMESTRUCT;
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoKeysMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
KeyTrac.cpp
//======================================================================
// KeyTrac - displays keyboard messages
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include <commctrl.h> // Command bar includes
#include "keytrac.h" // Program-specific stuff
// The include and lib files for the Pocket PC are conditionally
// included so that this example can share the same project file. This
// is necessary since this example must have a menu bar on the Pocket
// PC to have a SIP button.
#if defined(WIN32_PLATFORM_PSPC)
#include <aygshell.h> // Add Pocket PC includes.
#pragma comment( lib, "aygshell" ) // Link Pocket PC lib for menu bar.
#endif
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("KeyTrac");
HINSTANCE hInst; // Program instance handle
// Program-specific global data
MYKEYARRAY ka[16];
int nKeyCnt = 0;
int nFontHeight;
// Array associates key messages with text tags
KEYNAMESTRUCT knArray[] = {{WM_KEYDOWN, TEXT ("WM_KEYDOWN")},
{WM_KEYUP, TEXT ("WM_KEYUP")},
{WM_CHAR, TEXT ("WM_CHAR")},
{WM_SYSCHAR, TEXT ("WM_SYSCHAR")},
{WM_SYSKEYUP, TEXT ("WM_SYSKEYUP")},
{WM_SYSKEYDOWN, TEXT ("WM_SYSKEYDOWN")},
{WM_DEADCHAR, TEXT ("WM_DEADCHAR")},
{WM_SYSDEADCHAR, TEXT ("WM_SYSDEADCHAR")}};
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_CREATE, DoCreateMain,
WM_PAINT, DoPaintMain,
WM_KEYUP, DoKeysMain,
WM_KEYDOWN, DoKeysMain,
WM_CHAR, DoKeysMain,
WM_DEADCHAR, DoKeysMain,
WM_SYSCHAR, DoKeysMain,
WM_SYSDEADCHAR, DoKeysMain,
WM_SYSKEYDOWN, DoKeysMain,
WM_SYSKEYUP, DoKeysMain,
WM_DESTROY, DoDestroyMain,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
MSG msg;
int rc = 0;
HWND hwndMain;
// Initialize this instance.
hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0)
return 0x10;
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow) {
WNDCLASS wc;
HWND hWnd;
#if defined(WIN32_PLATFORM_PSPC)
// If Pocket PC, allow only one instance of the application
hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return 0;
}
#endif
hInst = hInstance; // Save program instance handle
// Register application main window class.
wc.style = 0; // Window style
wc.lpfnWndProc = MainWndProc; // Callback function
wc.cbClsExtra = 0; // Extra class data
wc.cbWndExtra = 0; // Extra window data
wc.hInstance = hInstance; // Owner handle
wc.hIcon = NULL, // Application icon
wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name
if (RegisterClass(&wc) == 0) return 0;
// Create main window.
hWnd = CreateWindowEx (WS_EX_NODRAG, szAppName, TEXT ("KeyTrac"),
WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
// Fail if window not created
if (!IsWindow (hWnd)) return 0;
// Standard show and update calls
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
return nDefRC;
}
//======================================================================
// Message handling procedures for MainWindow
//
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
INT i;
//
// Search message list to see if we need to handle this
// message. If in list, call procedure.
//
for (i = 0; i < dim(MainMessages); i++) {
if (wMsg == MainMessages[i].Code)
return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCreateMain - Process WM_CREATE message for window.
//
LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
HDC hdc;
TEXTMETRIC tm;
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
SHMENUBARINFO mbi; // For Pocket PC, create
memset(&mbi, 0, sizeof(SHMENUBARINFO)); // menu bar so that we
mbi.cbSize = sizeof(SHMENUBARINFO); // have a sip button
mbi.hwndParent = hWnd;
mbi.dwFlags = SHCMBF_EMPTYBAR; // No menu
SHCreateMenuBar(&mbi);
#endif
// Get the height of the default font.
hdc = GetDC (hWnd);
GetTextMetrics (hdc, &tm);
nFontHeight = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC (hWnd, hdc);
return 0;
}
//----------------------------------------------------------------------
// DoPaintMain - Process WM_PAINT message for window.
//
LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PAINTSTRUCT ps;
RECT rect, rectOut;
TCHAR szOut[256];
HDC hdc;
INT i, j;
LPCTSTR pKeyText;
GetClientRect (hWnd, &rect);
// Create a drawing rectangle for the top line of the window.
rectOut = rect;
rectOut.bottom = rectOut.top + nFontHeight;
hdc = BeginPaint (hWnd, &ps);
if (nKeyCnt) {
for (i = 0; i < nKeyCnt; i++) {
// Create string containing wParam, lParam, and shift data.
wsprintf (szOut, TEXT ("wP:%08x lP:%08x shift: %s"),
ka[i].wParam, ka[i].lParam, ka[i].szShift);
// Look up name of key message.
for (j = 0; j < dim (knArray); j++)
if (knArray[j].wMsg == ka[i].wKeyMsg)
break;
// See if we found the message.
if (j < dim (knArray))
pKeyText = knArray[j].pName;
else
pKeyText = TEXT ("Unknown");
// Scroll the window one line.
ScrollDC (hdc, 0, nFontHeight, &rect, &rect, NULL, NULL);
// See if wide or narrow screen.
if (GetSystemMetrics (SM_CXSCREEN) < 480) {
// If Pocket PC, display info on 2 lines
ExtTextOut (hdc, 10, rect.top, ETO_OPAQUE, &rectOut,
szOut, lstrlen (szOut), NULL);
// Scroll the window another line.
ScrollDC(hdc, 0, nFontHeight, &rect, &rect, NULL, NULL);
ExtTextOut (hdc, 5, rect.top, ETO_OPAQUE, &rectOut,
pKeyText, lstrlen (pKeyText), NULL);
} else {
// Wide screen, print all on one line.
ExtTextOut (hdc, 5, rect.top, ETO_OPAQUE, &rectOut,
pKeyText, lstrlen (pKeyText), NULL);
ExtTextOut (hdc, 100, rect.top, 0, NULL,
szOut, lstrlen (szOut), NULL);
}
}
nKeyCnt = 0;
}
EndPaint (hWnd, &ps);
return 0;
}
//----------------------------------------------------------------------
// DoKeysMain - Process all keyboard messages for window.
//
LRESULT DoKeysMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {
if (nKeyCnt >= 16) return 0;
ka[nKeyCnt].wKeyMsg = wMsg;
ka[nKeyCnt].wParam = wParam;
ka[nKeyCnt].lParam = lParam;
// Capture the state of the shift flags.
ka[nKeyCnt].szShift[0] = TEXT ('/0');
if (GetKeyState (VK_LMENU))
lstrcat (ka[nKeyCnt].szShift, TEXT ("lA "));
if (GetKeyState (VK_RMENU))
lstrcat (ka[nKeyCnt].szShift, TEXT ("rA "));
if (GetKeyState (VK_MENU))
lstrcat (ka[nKeyCnt].szShift, TEXT ("A "));
if (GetKeyState (VK_LCONTROL))
lstrcat (ka[nKeyCnt].szShift, TEXT ("lC "));
if (GetKeyState (VK_RCONTROL))
lstrcat (ka[nKeyCnt].szShift, TEXT ("rC "));
if (GetKeyState (VK_CONTROL))
lstrcat (ka[nKeyCnt].szShift, TEXT ("C "));
if (GetKeyState (VK_LSHIFT))
lstrcat (ka[nKeyCnt].szShift, TEXT ("lS "));
if (GetKeyState (VK_RSHIFT))
lstrcat (ka[nKeyCnt].szShift, TEXT ("rS "));
if (GetKeyState (VK_SHIFT))
lstrcat (ka[nKeyCnt].szShift, TEXT ("S "));
nKeyCnt++;
InvalidateRect (hWnd, NULL, FALSE);
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {
PostQuitMessage (0);
return 0;
}
下面是KeyTrac中需要注意的一些特性。在每個鍵盤消息被記錄後,一個InvalidateRect函數被調用,用來強制重繪窗口,併產生一個WM_PAINT消息。正如我在第二章中提到的,一個程序不應該嘗試發送或者提交(send or post)WM_PAINT消息給窗口,因爲在Windows用WM_PAINT消息調用一個窗口之前,它需要執行一些設置工作。
KeyTrac中使用的另一個設備描述表函數是
BOOL ScrollDC (HDC hDC, int dx, int dy, const RECT *lprcScroll, const RECT *lprcClip, HRGN hrgnUpdate,
LPRECT lprcUpdate);
該函數水平或者垂直滾動設備描述表的一個區域,但在Windows CE下,同時不會有兩個方向。接下來的三個矩形參數定義了 即將被滾動的區域,滾動區域裏將別裁減的區域和在滾動結束後將被繪製的區域。一個區域句柄可以傳遞給ScrollDC。由ScrollDC定義的這個區域包含了在滾動後需要繪製的區域。
最後,如果KeyTrac窗口被覆蓋和重新顯示出來,屏幕上顯示的信息將丟失,原因是設備描述表沒有存儲顯示屏的位信息。由應用程序負責存儲用於恢復屏幕客戶區域所需要的任何信息。因爲KeyTrac沒有存儲這些信息,所以當窗口被覆蓋的時候,這些信息就丟失了。