使用鍵盤輸入
視窗會以按鍵訊息和字元訊息的形式接收鍵盤輸入。 附加至視窗的訊息迴圈必須包含程式碼,才能將擊鍵訊息轉譯成對應的字元訊息。 如果視窗在其工作區中顯示鍵盤輸入,它應該建立並顯示插入號,以指出下一個字元將輸入的位置。 下列各節說明接收、處理及顯示鍵盤輸入所涉及的程式碼:
處理擊鍵訊息
當使用者在鍵盤輸入時,具有鍵盤焦點的視窗程式會收到按鍵訊息。 擊鍵訊息是 WM_KEYDOWN、 WM_KEYUP、 WM_SYSKEYDOWN和 WM_SYSKEYUP。 一般視窗程式會忽略除了 WM_KEYDOWN以外的所有擊鍵訊息。 當使用者按下按鍵時,系統會張貼 WM_KEYDOWN 訊息。
當視窗程式收到 WM_KEYDOWN 訊息時,它應該檢查訊息隨附的虛擬按鍵程式碼,以判斷如何處理擊鍵。 虛擬金鑰程式碼位於訊息的 wParam 參數中。 一般而言,應用程式只會處理非字元鍵所產生的按鍵,包括函式索引鍵、資料指標移動索引鍵,以及 INS、DEL、HOME 和 END 等特殊用途索引鍵。
下列範例顯示一般應用程式用來接收和處理按鍵訊息的視窗程式架構。
case WM_KEYDOWN:
switch (wParam)
{
case VK_LEFT:
// Process the LEFT ARROW key.
break;
case VK_RIGHT:
// Process the RIGHT ARROW key.
break;
case VK_UP:
// Process the UP ARROW key.
break;
case VK_DOWN:
// Process the DOWN ARROW key.
break;
case VK_HOME:
// Process the HOME key.
break;
case VK_END:
// Process the END key.
break;
case VK_INSERT:
// Process the INS key.
break;
case VK_DELETE:
// Process the DEL key.
break;
case VK_F2:
// Process the F2 key.
break;
// Process other non-character keystrokes.
default:
break;
}
翻譯字元訊息
從使用者接收字元輸入的任何執行緒,都必須在其訊息迴圈中包含 TranslateMessage 函式。 此函式會檢查擊鍵訊息的虛擬按鍵程式碼,如果程式碼對應至字元,請將字元訊息放入訊息佇列中。 字元訊息會在訊息迴圈的下一個反復專案上移除並分派;訊息的 wParam 參數包含字元碼。
一般而言,執行緒的訊息迴圈應該使用 TranslateMessage 函式來翻譯每個訊息,而不只是虛擬金鑰訊息。 雖然 TranslateMessage 對其他類型的訊息沒有任何作用,但它保證鍵盤輸入正確翻譯。 下列範例示範如何在一般執行緒訊息迴圈中包含 TranslateMessage 函式。
MSG msg;
BOOL bRet;
while (( bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0)
{
if (bRet == -1);
{
// handle the error and possibly exit
}
else
{
if (TranslateAccelerator(hwndMain, haccl, &msg) == 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
處理字元訊息
當 TranslateMessage 函式轉譯對應至字元索引鍵的虛擬金鑰程式碼時,視窗程式會收到字元訊息。 字元訊息是 WM_CHAR、 WM_DEADCHAR、 WM_SYSCHAR和 WM_SYSDEADCHAR。 一般視窗程式會忽略 除了WM_CHAR以外的所有字元訊息。 當使用者按下下列任一鍵時, TranslateMessage 函式會產生 WM_CHAR 訊息:
- 任何字元索引鍵
- 退格鍵
- ENTER (歸位字元)
- ESC
- SHIFT+ENTER (換行)
- TAB
當視窗程式收到 WM_CHAR 訊息時,它應該檢查訊息隨附的字元碼,以判斷如何處理字元。 字元碼位於訊息的 wParam 參數中。
下列範例顯示一般應用程式用來接收和處理字元訊息的視窗程式架構。
case WM_CHAR:
switch (wParam)
{
case 0x08:
// Process a backspace.
break;
case 0x0A:
// Process a linefeed.
break;
case 0x1B:
// Process an escape.
break;
case 0x09:
// Process a tab.
break;
case 0x0D:
// Process a carriage return.
break;
default:
// Process displayable characters.
break;
}
使用插入號
接收鍵盤輸入的視窗通常會在視窗的工作區中顯示使用者輸入的字元。 視窗應該使用插入號來指出下一個字元會出現在工作區中的位置。 視窗也應該在收到鍵盤焦點時建立並顯示插入號,並在失去焦點時隱藏和終結插入號。 視窗可以在處理 WM_SETFOCUS 和 WM_KILLFOCUS 訊息時執行這些作業。 如需插入號的詳細資訊,請參閱 插入號。
顯示鍵盤輸入
本節中的範例示範應用程式如何從鍵盤接收字元、在視窗的工作區中顯示字元,以及使用每個字元輸入的插入號來更新插入號的位置。 它也會示範如何移動插入號以回應向左鍵、向右鍵、首頁和 END 按鍵,並示範如何反白顯示選取的文字,以回應 SHIFT+向右鍵組合。
在處理 WM_CREATE 訊息期間,範例中顯示的視窗程式會配置 64K 緩衝區來儲存鍵盤輸入。 它也會擷取目前載入字型的計量,以儲存字型中的字元高度和平均寬度。 高度和寬度用於處理 WM_SIZE 訊息,根據工作區的大小計算行長度和行數上限。
視窗程式會在處理 WM_SETFOCUS 訊息時建立並顯示插入號。 它會隱藏並刪除處理 WM_KILLFOCUS 訊息時的插入號。
處理 WM_CHAR 訊息時,視窗程式會顯示字元、將它們儲存在輸入緩衝區中,以及更新插入號位置。 視窗程式也會將定位字元轉換成四個連續空白字元。 退格字元、換行字元和逸出字元會產生嗶聲,但不會另外處理。
視窗程式會在處理 WM_KEYDOWN 訊息時,執行左、右、結束和主插入號移動。 處理向右鍵的動作時,視窗程式會檢查 SHIFT 鍵的狀態,如果已關閉,請在插入號移動時選取插入號右邊的字元。
請注意,下列程式碼是撰寫的,因此可以編譯為 Unicode 或 ANSI。 如果原始程式碼定義 UNICODE,字串會以 Unicode 字元處理;否則,它們會以 ANSI 字元處理。
#define BUFSIZE 65535
#define SHIFTED 0x8000
LONG APIENTRY MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc; // handle to device context
TEXTMETRIC tm; // structure for text metrics
static DWORD dwCharX; // average width of characters
static DWORD dwCharY; // height of characters
static DWORD dwClientX; // width of client area
static DWORD dwClientY; // height of client area
static DWORD dwLineLen; // line length
static DWORD dwLines; // text lines in client area
static int nCaretPosX = 0; // horizontal position of caret
static int nCaretPosY = 0; // vertical position of caret
static int nCharWidth = 0; // width of a character
static int cch = 0; // characters in buffer
static int nCurChar = 0; // index of current character
static PTCHAR pchInputBuf; // input buffer
int i, j; // loop counters
int cCR = 0; // count of carriage returns
int nCRIndex = 0; // index of last carriage return
int nVirtKey; // virtual-key code
TCHAR szBuf[128]; // temporary buffer
TCHAR ch; // current character
PAINTSTRUCT ps; // required by BeginPaint
RECT rc; // output rectangle for DrawText
SIZE sz; // string dimensions
COLORREF crPrevText; // previous text color
COLORREF crPrevBk; // previous background color
size_t * pcch;
HRESULT hResult;
switch (uMsg)
{
case WM_CREATE:
// Get the metrics of the current font.
hdc = GetDC(hwndMain);
GetTextMetrics(hdc, &tm);
ReleaseDC(hwndMain, hdc);
// Save the average character width and height.
dwCharX = tm.tmAveCharWidth;
dwCharY = tm.tmHeight;
// Allocate a buffer to store keyboard input.
pchInputBuf = (LPTSTR) GlobalAlloc(GPTR,
BUFSIZE * sizeof(TCHAR));
return 0;
case WM_SIZE:
// Save the new width and height of the client area.
dwClientX = LOWORD(lParam);
dwClientY = HIWORD(lParam);
// Calculate the maximum width of a line and the
// maximum number of lines in the client area.
dwLineLen = dwClientX - dwCharX;
dwLines = dwClientY / dwCharY;
break;
case WM_SETFOCUS:
// Create, position, and display the caret when the
// window receives the keyboard focus.
CreateCaret(hwndMain, (HBITMAP) 1, 0, dwCharY);
SetCaretPos(nCaretPosX, nCaretPosY * dwCharY);
ShowCaret(hwndMain);
break;
case WM_KILLFOCUS:
// Hide and destroy the caret when the window loses the
// keyboard focus.
HideCaret(hwndMain);
DestroyCaret();
break;
case WM_CHAR:
// check if current location is close enough to the
// end of the buffer that a buffer overflow may
// occur. If so, add null and display contents.
if (cch > BUFSIZE-5)
{
pchInputBuf[cch] = 0x00;
SendMessage(hwndMain, WM_PAINT, 0, 0);
}
switch (wParam)
{
case 0x08: // backspace
case 0x0A: // linefeed
case 0x1B: // escape
MessageBeep((UINT) -1);
return 0;
case 0x09: // tab
// Convert tabs to four consecutive spaces.
for (i = 0; i < 4; i++)
SendMessage(hwndMain, WM_CHAR, 0x20, 0);
return 0;
case 0x0D: // carriage return
// Record the carriage return and position the
// caret at the beginning of the new line.
pchInputBuf[cch++] = 0x0D;
nCaretPosX = 0;
nCaretPosY += 1;
break;
default: // displayable character
ch = (TCHAR) wParam;
HideCaret(hwndMain);
// Retrieve the character's width and output
// the character.
hdc = GetDC(hwndMain);
GetCharWidth32(hdc, (UINT) wParam, (UINT) wParam,
&nCharWidth);
TextOut(hdc, nCaretPosX, nCaretPosY * dwCharY,
&ch, 1);
ReleaseDC(hwndMain, hdc);
// Store the character in the buffer.
pchInputBuf[cch++] = ch;
// Calculate the new horizontal position of the
// caret. If the position exceeds the maximum,
// insert a carriage return and move the caret
// to the beginning of the next line.
nCaretPosX += nCharWidth;
if ((DWORD) nCaretPosX > dwLineLen)
{
nCaretPosX = 0;
pchInputBuf[cch++] = 0x0D;
++nCaretPosY;
}
nCurChar = cch;
ShowCaret(hwndMain);
break;
}
SetCaretPos(nCaretPosX, nCaretPosY * dwCharY);
break;
case WM_KEYDOWN:
switch (wParam)
{
case VK_LEFT: // LEFT ARROW
// The caret can move only to the beginning of
// the current line.
if (nCaretPosX > 0)
{
HideCaret(hwndMain);
// Retrieve the character to the left of
// the caret, calculate the character's
// width, then subtract the width from the
// current horizontal position of the caret
// to obtain the new position.
ch = pchInputBuf[--nCurChar];
hdc = GetDC(hwndMain);
GetCharWidth32(hdc, ch, ch, &nCharWidth);
ReleaseDC(hwndMain, hdc);
nCaretPosX = max(nCaretPosX - nCharWidth,
0);
ShowCaret(hwndMain);
}
break;
case VK_RIGHT: // RIGHT ARROW
// Caret moves to the right or, when a carriage
// return is encountered, to the beginning of
// the next line.
if (nCurChar < cch)
{
HideCaret(hwndMain);
// Retrieve the character to the right of
// the caret. If it's a carriage return,
// position the caret at the beginning of
// the next line.
ch = pchInputBuf[nCurChar];
if (ch == 0x0D)
{
nCaretPosX = 0;
nCaretPosY++;
}
// If the character isn't a carriage
// return, check to see whether the SHIFT
// key is down. If it is, invert the text
// colors and output the character.
else
{
hdc = GetDC(hwndMain);
nVirtKey = GetKeyState(VK_SHIFT);
if (nVirtKey & SHIFTED)
{
crPrevText = SetTextColor(hdc,
RGB(255, 255, 255));
crPrevBk = SetBkColor(hdc,
RGB(0,0,0));
TextOut(hdc, nCaretPosX,
nCaretPosY * dwCharY,
&ch, 1);
SetTextColor(hdc, crPrevText);
SetBkColor(hdc, crPrevBk);
}
// Get the width of the character and
// calculate the new horizontal
// position of the caret.
GetCharWidth32(hdc, ch, ch, &nCharWidth);
ReleaseDC(hwndMain, hdc);
nCaretPosX = nCaretPosX + nCharWidth;
}
nCurChar++;
ShowCaret(hwndMain);
break;
}
break;
case VK_UP: // UP ARROW
case VK_DOWN: // DOWN ARROW
MessageBeep((UINT) -1);
return 0;
case VK_HOME: // HOME
// Set the caret's position to the upper left
// corner of the client area.
nCaretPosX = nCaretPosY = 0;
nCurChar = 0;
break;
case VK_END: // END
// Move the caret to the end of the text.
for (i=0; i < cch; i++)
{
// Count the carriage returns and save the
// index of the last one.
if (pchInputBuf[i] == 0x0D)
{
cCR++;
nCRIndex = i + 1;
}
}
nCaretPosY = cCR;
// Copy all text between the last carriage
// return and the end of the keyboard input
// buffer to a temporary buffer.
for (i = nCRIndex, j = 0; i < cch; i++, j++)
szBuf[j] = pchInputBuf[i];
szBuf[j] = TEXT('\0');
// Retrieve the text extent and use it
// to set the horizontal position of the
// caret.
hdc = GetDC(hwndMain);
hResult = StringCchLength(szBuf, 128, pcch);
if (FAILED(hResult))
{
// TODO: write error handler
}
GetTextExtentPoint32(hdc, szBuf, *pcch,
&sz);
nCaretPosX = sz.cx;
ReleaseDC(hwndMain, hdc);
nCurChar = cch;
break;
default:
break;
}
SetCaretPos(nCaretPosX, nCaretPosY * dwCharY);
break;
case WM_PAINT:
if (cch == 0) // nothing in input buffer
break;
hdc = BeginPaint(hwndMain, &ps);
HideCaret(hwndMain);
// Set the clipping rectangle, and then draw the text
// into it.
SetRect(&rc, 0, 0, dwLineLen, dwClientY);
DrawText(hdc, pchInputBuf, -1, &rc, DT_LEFT);
ShowCaret(hwndMain);
EndPaint(hwndMain, &ps);
break;
// Process other messages.
case WM_DESTROY:
PostQuitMessage(0);
// Free the input buffer.
GlobalFree((HGLOBAL) pchInputBuf);
UnregisterHotKey(hwndMain, 0xAAAA);
break;
default:
return DefWindowProc(hwndMain, uMsg, wParam, lParam);
}
return NULL;
}