Usando a entrada do teclado
Uma janela recebe a entrada do teclado na forma de mensagens de teclas e mensagens de caractere. O loop de mensagem anexado à janela deve incluir código para traduzir mensagens de pressionamento de tecla nas mensagens de caractere correspondentes. Se a janela exibir a entrada do teclado em sua área de cliente, ela deverá criar e exibir um careta para indicar a posição em que o próximo caractere será inserido. As seções a seguir descrevem o código envolvido no recebimento, processamento e exibição de entrada de teclado:
- Processando mensagens de pressionamento de tecla
- Traduzindo mensagens de caractere
- Processando mensagens de caractere
- Usando o Caret
- Exibindo entrada de teclado
Processando mensagens de pressionamento de tecla
O procedimento de janela da janela que tem o foco do teclado recebe mensagens de tecla quando o usuário digita no teclado. As mensagens de pressionamento de tecla são WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN e WM_SYSKEYUP. Um procedimento de janela típico ignora todas as mensagens de pressionamento de tecla, exceto WM_KEYDOWN. O sistema posta a mensagem WM_KEYDOWN quando o usuário pressiona uma tecla.
Quando o procedimento de janela recebe a mensagem WM_KEYDOWN , ele deve examinar o código de chave virtual que acompanha a mensagem para determinar como processar o pressionamento de teclas. O código de chave virtual está no parâmetro wParam da mensagem. Normalmente, um aplicativo processa apenas pressionamentos de tecla gerados por chaves não caracter, incluindo as chaves de função, as teclas de movimentação do cursor e as chaves de finalidade especiais, como INS, DEL, HOME e END.
O exemplo a seguir mostra a estrutura de procedimento de janela que um aplicativo típico usa para receber e processar mensagens de pressionamento de tecla.
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;
}
Traduzindo mensagens de caractere
Qualquer thread que receba a entrada de caractere do usuário deve incluir a função TranslateMessage em seu loop de mensagem. Essa função examina o código de chave virtual de uma mensagem de pressionamento de tecla e, se o código corresponder a um caractere, coloca uma mensagem de caractere na fila de mensagens. A mensagem de caractere é removida e enviada na próxima iteração do loop de mensagem; o parâmetro wParam da mensagem contém o código de caractere.
Em geral, o loop de mensagem de um thread deve usar a função TranslateMessage para traduzir todas as mensagens, não apenas mensagens de chave virtual. Embora TranslateMessage não tenha nenhum efeito sobre outros tipos de mensagens, ele garante que a entrada do teclado seja traduzida corretamente. O exemplo a seguir mostra como incluir a função TranslateMessage em um loop de mensagem de thread típico.
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);
}
}
}
Processando mensagens de caractere
Um procedimento de janela recebe uma mensagem de caractere quando a função TranslateMessage converte um código de chave virtual correspondente a uma chave de caractere. As mensagens de caractere são WM_CHAR, WM_DEADCHAR, WM_SYSCHAR e WM_SYSDEADCHAR. Um procedimento de janela típico ignora todas as mensagens de caractere, exceto WM_CHAR. A função TranslateMessage gera uma mensagem WM_CHAR quando o usuário pressiona qualquer uma das seguintes chaves:
- Qualquer chave de caractere
- BACKSPACE
- ENTER (retorno do carro)
- ESC
- SHIFT+ENTER (linefeed)
- TAB
Quando um procedimento de janela recebe a mensagem WM_CHAR , ele deve examinar o código de caractere que acompanha a mensagem para determinar como processar o caractere. O código de caractere está no parâmetro wParam da mensagem.
O exemplo a seguir mostra a estrutura de procedimento de janela que um aplicativo típico usa para receber e processar mensagens de caracteres.
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;
}
Usando o Caret
Uma janela que recebe entrada de teclado normalmente exibe os caracteres que o usuário digita na área do cliente da janela. Uma janela deve usar um cursor para indicar a posição na área do cliente em que o próximo caractere será exibido. A janela também deve criar e exibir o careta quando receber o foco do teclado e ocultar e destruir o careta quando perder o foco. Uma janela pode executar essas operações no processamento das mensagens WM_SETFOCUS e WM_KILLFOCUS . Para obter mais informações sobre carets, consulte Carets.
Exibindo entrada de teclado
O exemplo nesta seção mostra como um aplicativo pode receber caracteres do teclado, exibi-los na área do cliente de uma janela e atualizar a posição do cursor com cada caractere digitado. Ele também demonstra como mover o cursor em resposta aos pressionamentos de tecla SETA PARA A ESQUERDA, SETA PARA A DIREITA, HOME e END e mostra como realçar o texto selecionado em resposta à combinação de teclas SHIFT+SETA PARA A DIREITA.
Durante o processamento da mensagem WM_CREATE , o procedimento de janela mostrado no exemplo aloca um buffer de 64K para armazenar a entrada do teclado. Ele também recupera as métricas da fonte carregada no momento, salvando a altura e a largura média dos caracteres na fonte. A altura e a largura são usadas no processamento da mensagem WM_SIZE para calcular o comprimento da linha e o número máximo de linhas, com base no tamanho da área do cliente.
O procedimento de janela cria e exibe o careta ao processar a mensagem WM_SETFOCUS . Ele oculta e exclui o careta ao processar a mensagem WM_KILLFOCUS .
Ao processar a mensagem de WM_CHAR , o procedimento da janela exibe caracteres, armazena-os no buffer de entrada e atualiza a posição do cursor. O procedimento de janela também converte caracteres de guia em quatro caracteres de espaço consecutivos. Os caracteres backspace, linefeed e escape geram um bipe, mas não são processados de outra forma.
O procedimento de janela executa os movimentos de esquerda, direita, extremidade e cuidados domésticos ao processar a mensagem WM_KEYDOWN . Durante o processamento da ação da tecla SETA PARA A DIREITA, o procedimento de janela verifica o estado da chave SHIFT e, se ela estiver inativa, seleciona o caractere à direita do cursor à medida que o cursor é movido.
Observe que o código a seguir é escrito para que ele possa ser compilado como Unicode ou como ANSI. Se o código-fonte definir UNICODE, as cadeias de caracteres serão tratadas como caracteres Unicode; caso contrário, eles são tratados como caracteres 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;
}