Toetsenbordinvoer gebruiken
Een venster ontvangt toetsenbordinvoer in de vorm van toetsaanslagberichten en tekenberichten. De berichtenlus die aan het venster is gekoppeld, moet code bevatten om toetsaanslagberichten te vertalen in de bijbehorende tekenberichten. Als het venster toetsenbordinvoer weergeeft in het clientgebied, moet het een cursor maken en weergeven om de positie aan te geven waar het volgende teken wordt ingevoerd. In de volgende secties wordt de code beschreven die betrokken is bij het ontvangen, verwerken en weergeven van toetsenbordinvoer:
- toetsenberichten verwerken
- tekenberichten vertalen
- tekenberichten verwerken
- De Caret gebruiken-
- Toetsenbordinvoer weergeven
Toetsaanslagen verwerken
De vensterprocedure van het venster met de toetsenbordfocus ontvangt toetsenbordberichten wanneer de gebruiker op het toetsenbord typt. De toetsaanslagen zijn WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWNen WM_SYSKEYUP. Een gebruikelijke vensterprocedure negeert alle berichten van toetsaanslagen behalve WM_KEYDOWN. Het systeem plaatst het WM_KEYDOWN bericht wanneer de gebruiker op een toets drukt.
Wanneer de vensterprocedure het WM_KEYDOWN bericht ontvangt, moet deze de code van de virtuele sleutel onderzoeken die het bericht begeleidt om te bepalen hoe de toetsaanslagen moeten worden verwerkt. De code van de virtuele sleutel bevindt zich in de parameter wParam van het bericht. Normaal gesproken verwerkt een toepassing alleen toetsaanslagen die worden gegenereerd door niet-tekentoetsen, waaronder de functietoetsen, de cursorverplaatsingstoetsen en de speciale doeltoetsen, zoals INS, DEL, HOME en END.
In het volgende voorbeeld ziet u het vensterprocedureframework dat een typische toepassing gebruikt voor het ontvangen en verwerken van toetsaanslagen.
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;
default:
// Process other non-character keystrokes.
break;
}
Karakterberichten vertalen
Elke thread die tekeninvoer van de gebruiker ontvangt, moet de functie TranslateMessage opnemen in de berichtenlus. Met deze functie wordt de code van de virtuele sleutel van een toetsaanslagbericht onderzocht en wordt, als de code overeenkomt met een teken, een tekenbericht in de berichtenwachtrij geplaatst. Het tekenbericht wordt verwijderd en verzonden tijdens de volgende iteratie van de berichtlus; de parameter wParam van het bericht bevat de tekencode.
Over het algemeen moet de berichtenlus van een thread de functie TranslateMessage gebruiken om elk bericht, niet alleen virtuele-sleutelberichten, te vertalen. Hoewel TranslateMessage- geen effect heeft op andere typen berichten, zorgt dit ervoor dat toetsenbordinvoer correct wordt vertaald. In het volgende voorbeeld ziet u hoe u de functie TranslateMessage kunt opnemen in een normale threadberichtlus.
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);
}
}
}
Karakterberichten verwerken
Een vensterprocedure ontvangt een tekenbericht wanneer de functie TranslateMessage een code van een virtuele sleutel vertaalt die overeenkomt met een tekensleutel. De tekenberichten zijn WM_CHAR, WM_DEADCHAR, WM_SYSCHARen WM_SYSDEADCHAR. Een typische vensterprocedure negeert alle tekenberichten behalve WM_CHAR. De functie TranslateMessage genereert een WM_CHAR bericht wanneer de gebruiker op een van de volgende toetsen drukt:
- Een willekeurige tekensleutel
- BACKSPACE
- ENTER (regelterugloop)
- ESC
- SHIFT+ENTER (linefeed)
- TABBLAD
Wanneer een vensterprocedure het WM_CHAR bericht ontvangt, moet deze de tekencode onderzoeken die het bericht begeleidt om te bepalen hoe het teken moet worden verwerkt. De tekencode bevindt zich in de parameter wParam van het bericht.
In het volgende voorbeeld ziet u het vensterprocedureframework dat een typische toepassing gebruikt voor het ontvangen en verwerken van tekenberichten.
case WM_CHAR:
switch (wParam)
{
case 0x08: // or '\b'
// Process a backspace.
break;
case 0x09: // or '\t'
// Process a tab.
break;
case 0x0A: // or '\n'
// Process a linefeed.
break;
case 0x0D:
// Process a carriage return.
break;
case 0x1B:
// Process an escape.
break;
default:
// Process displayable characters.
break;
}
De caret gebruiken
In een venster dat toetsenbordinvoer ontvangt, worden doorgaans de tekens weergegeven die de gebruiker typt in het clientgebied van het venster. Een venster moet een caret gebruiken om de positie in het clientgebied aan te geven waar het volgende teken wordt weergegeven. Het venster moet ook de caret maken en weergeven wanneer het de toetsenbordfocus ontvangt, en de caret verbergen en verwijderen wanneer het de focus verliest. Een venster kan deze bewerkingen uitvoeren bij de verwerking van de WM_SETFOCUS en WM_KILLFOCUS berichten. Voor meer informatie over carets, zie Carets.
Toetsenbordinvoer weergeven
In het voorbeeld in deze sectie ziet u hoe een toepassing tekens van het toetsenbord kan ontvangen, deze kan weergeven in het clientgebied van een venster en de positie van de caret kan bijwerken met elk getypt teken. Ook wordt gedemonstreerd hoe u de caret verplaatst als reactie op de toetsaanslagen PIJL-LINKS, PIJL-RECHTS, HOME en END en laat zien hoe u geselecteerde tekst markeert in reactie op de toetsencombinatie SHIFT+PIJL-RECHTS.
Tijdens het verwerken van het WM_CREATE bericht wijst de vensterprocedure in het voorbeeld een 64K-buffer toe voor het opslaan van toetsenbordinvoer. Ook worden de metrische gegevens van het momenteel geladen lettertype opgehaald, waardoor de hoogte en de gemiddelde breedte van tekens in het lettertype worden opgeslagen. De hoogte en breedte worden gebruikt bij het verwerken van het WM_SIZE bericht om de lijnlengte en het maximum aantal regels te berekenen, op basis van de grootte van het clientgebied.
De vensterprocedure maakt en geeft de caret weer bij het verwerken van het WM_SETFOCUS bericht. Het verbergen en verwijderen van de caret gebeurt bij het verwerken van het WM_KILLFOCUS bericht.
Bij het verwerken van het WM_CHAR bericht geeft de vensterprocedure tekens weer, slaat ze op in de invoerbuffer, en werkt de caretpositie bij. Met de vensterprocedure worden tabtekens ook geconverteerd naar vier opeenvolgende spatietekens. Backspace-, linefeed- en escapetekens genereren een pieptoon, maar worden anders niet verwerkt.
De vensterprocedure voert de bewegingen links, rechts, einde en home uit bij het verwerken van het WM_KEYDOWN bericht. Tijdens het verwerken van de actie van de pijl-rechts controleert de vensterprocedure de status van de Shift-toets en, als deze ingedrukt is, wordt het teken rechts van de cursor geselecteerd terwijl de cursor beweegt.
Houd er rekening mee dat de volgende code is geschreven, zodat deze kan worden gecompileerd als Unicode of als ANSI. Als de broncode UNICODE definieert, worden tekenreeksen verwerkt als Unicode-tekens; anders worden ze verwerkt als ANSI-tekens.
#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;
}