Menggunakan Input Keyboard
Jendela menerima input keyboard dalam bentuk pesan penekanan tombol dan pesan karakter. Perulangan pesan yang dilampirkan ke jendela harus menyertakan kode untuk menerjemahkan pesan penekanan tombol ke dalam pesan karakter yang sesuai. Jika jendela menampilkan input keyboard di area kliennya, jendela harus membuat dan menampilkan tanda sisipan untuk menunjukkan posisi di mana karakter berikutnya akan dimasukkan. Bagian berikut menjelaskan kode yang terlibat dalam menerima, memproses, dan menampilkan input keyboard:
- Memproses Pesan Penekanan Tombol
- Menerjemahkan Pesan Karakter
- Memproses Pesan Karakter
- Menggunakan Caret
- Menampilkan Input Keyboard
Memproses Pesan Penekanan Tombol
Prosedur jendela jendela yang memiliki fokus keyboard menerima pesan penekanan tombol saat pengguna mengetik di keyboard. Pesan penekanan tombol WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, dan WM_SYSKEYUP. Prosedur jendela umum mengabaikan semua pesan penekanan tombol kecuali WM_KEYDOWN. Sistem memposting pesan WM_KEYDOWN saat pengguna menekan tombol.
Ketika prosedur jendela menerima pesan WM_KEYDOWN , itu harus memeriksa kode kunci virtual yang menyertai pesan untuk menentukan cara memproses penekanan tombol. Kode kunci virtual ada di parameter wParam pesan. Biasanya, aplikasi hanya memproses penekanan tombol yang dihasilkan oleh kunci noncharacter, termasuk kunci fungsi, kunci gerakan kursor, dan kunci tujuan khusus seperti INS, DEL, HOME, dan END.
Contoh berikut menunjukkan kerangka kerja prosedur jendela yang digunakan aplikasi umum untuk menerima dan memproses pesan penekanan kunci.
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;
}
Menerjemahkan Pesan Karakter
Setiap utas yang menerima input karakter dari pengguna harus menyertakan fungsi TranslateMessage dalam perulangan pesannya. Fungsi ini memeriksa kode kunci virtual dari pesan penekanan tombol dan, jika kode sesuai dengan karakter, menempatkan pesan karakter ke dalam antrean pesan. Pesan karakter dihapus dan dikirim pada iterasi berikutnya dari perulangan pesan; parameter wParam pesan berisi kode karakter.
Secara umum, perulangan pesan utas harus menggunakan fungsi TranslateMessage untuk menerjemahkan setiap pesan, bukan hanya pesan kunci virtual. Meskipun TranslateMessage tidak berpengaruh pada jenis pesan lain, ini menjamin bahwa input keyboard diterjemahkan dengan benar. Contoh berikut menunjukkan cara menyertakan fungsi TranslateMessage dalam perulangan pesan utas umum.
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);
}
}
}
Memproses Pesan Karakter
Prosedur jendela menerima pesan karakter saat fungsi TranslateMessage menerjemahkan kode kunci virtual yang sesuai dengan kunci karakter. Pesan karakter WM_CHAR, WM_DEADCHAR, WM_SYSCHAR, dan WM_SYSDEADCHAR. Prosedur jendela umum mengabaikan semua pesan karakter kecuali WM_CHAR. Fungsi TranslateMessage menghasilkan pesan WM_CHAR saat pengguna menekan salah satu tombol berikut:
- Kunci karakter apa pun
- BACKSPACE
- ENTER (pengangkutan kembali)
- ESC
- SHIFT+ENTER (linefeed)
- TAB
Ketika prosedur jendela menerima pesan WM_CHAR , prosedur harus memeriksa kode karakter yang menyertai pesan untuk menentukan cara memproses karakter. Kode karakter ada di parameter wParam pesan.
Contoh berikut menunjukkan kerangka kerja prosedur jendela yang digunakan aplikasi umum untuk menerima dan memproses pesan karakter.
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;
}
Menggunakan Caret
Jendela yang menerima input keyboard biasanya menampilkan karakter jenis pengguna di area klien jendela. Jendela harus menggunakan tanda sisipan untuk menunjukkan posisi di area klien tempat karakter berikutnya akan muncul. Jendela juga harus membuat dan menampilkan tanda sisipan saat menerima fokus keyboard, dan menyembunyikan dan menghancurkan tanda sisipan saat kehilangan fokus. Jendela dapat melakukan operasi ini dalam pemrosesan pesan WM_SETFOCUS dan WM_KILLFOCUS . Untuk informasi selengkapnya tentang caret, lihat Carets.
Menampilkan Input Keyboard
Contoh di bagian ini menunjukkan bagaimana aplikasi dapat menerima karakter dari keyboard, menampilkannya di area klien jendela, dan memperbarui posisi tanda sisipan dengan setiap karakter yang di ketik. Ini juga menunjukkan cara memindahkan tanda sisipan sebagai respons terhadap tombol PANAH KIRI, PANAH KANAN, BERANDA, dan TOMBOL AKHIR, dan memperlihatkan cara menyoroti teks yang dipilih sebagai respons terhadap kombinasi tombol SHIFT+RIGHT ARROW.
Selama pemrosesan pesan WM_CREATE , prosedur jendela yang ditunjukkan dalam contoh mengalokasikan buffer 64K untuk menyimpan input keyboard. Ini juga mengambil metrik font yang saat ini dimuat, menyimpan tinggi dan lebar rata-rata karakter dalam font. Tinggi dan lebar digunakan dalam memproses pesan WM_SIZE untuk menghitung panjang baris dan jumlah baris maksimum, berdasarkan ukuran area klien.
Prosedur jendela membuat dan menampilkan tanda sisipan saat memproses pesan WM_SETFOCUS . Ini menyembunyikan dan menghapus tanda sisipan saat memproses pesan WM_KILLFOCUS .
Saat memproses pesan WM_CHAR , prosedur jendela menampilkan karakter, menyimpannya di buffer input, dan memperbarui posisi tanda sisipan. Prosedur jendela juga mengonversi karakter tab menjadi empat karakter spasi berturut-turut. Karakter backspace, linefeed, dan escape menghasilkan bip, tetapi tidak diproses.
Prosedur jendela melakukan gerakan caret kiri, kanan, akhir, dan rumah saat memproses pesan WM_KEYDOWN . Saat memproses tindakan tombol PANAH KANAN, prosedur jendela memeriksa status tombol SHIFT dan, jika tidak berfungsi, memilih karakter di sebelah kanan tanda sisipan saat tanda sisipan dipindahkan.
Perhatikan bahwa kode berikut ditulis sehingga dapat dikompilasi baik sebagai Unicode atau sebagai ANSI. Jika kode sumber mendefinisikan UNICODE, string ditangani sebagai karakter Unicode; jika tidak, mereka ditangani sebagai karakter 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;
}