Использование ввода с клавиатуры

Окно получает ввод с клавиатуры в виде сообщений нажатия клавиш и символьных сообщений. Цикл сообщений, присоединенный к окну, должен включать код для преобразования сообщений о нажатии клавиш в соответствующие сообщения символов. Если окно отображает ввод с клавиатуры в своей клиентской области, оно должно создать и отобразить курсор, чтобы указать позицию, в которой будет введен следующий символ. В следующих разделах описывается код, участвующий в получении, обработке и отображении ввода с помощью клавиатуры.

Обработка сообщений нажатия клавиш

Процедура окна с фокусом клавиатуры получает сообщения о нажатии клавиши, когда пользователь вводит клавиатуру. К сообщениям о нажатии клавиш относятся 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; 

		default: 
			// Process other non-character keystrokes. 
			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 сообщение, когда пользователь нажимает любую из следующих клавиш:

  • Любая символьная клавиша
  • BACKSPACE
  • ВВОД (возврат каретки)
  • ESC
  • SHIFT+ВВОД (linefeed)
  • TAB

Когда процедура окна получает сообщение WM_CHAR , она должна изучить код символа, сопровождающий сообщение, чтобы определить, как обрабатывать символ. Код символа находится в параметре wParam сообщения.

В следующем примере показана платформа процедур окон, которая используется типичным приложением для получения и обработки символьных сообщений.

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;
	}

Использование курсора

Окно, которое получает ввод с клавиатуры, обычно отображает символы, введенные пользователем в клиентской области окна. Окно должно использовать курсор, чтобы указать позицию в клиентской области, где будет отображаться следующий символ. Окно также должно создавать и отображать курсор при получении фокуса клавиатуры, а также скрывать и уничтожать курсор при потере фокуса. Окно может выполнять эти операции при обработке WM_SETFOCUS и WM_KILLFOCUS сообщений. Дополнительные сведения о курсорах см. в разделе Курсоры.

Отображение ввода с клавиатуры

В примере в этом разделе показано, как приложение может получать символы с клавиатуры, отображать их в клиентской области окна и обновлять положение курсора с помощью каждого введенного символа. В нем также показано, как переместить курсор в ответ на нажатия клавиш СТРЕЛКА ВЛЕВО, СТРЕЛКА ВПРАВО, HOME и END, а также показано, как выделить выделенный текст в ответ на сочетание клавиш SHIFT+СТРЕЛКА ВПРАВО.

Во время обработки сообщения WM_CREATE процедура окна, показанная в примере, выделяет буфер размером 64 КБ для хранения ввода с клавиатуры. Он также извлекает метрики текущего загруженного шрифта, сохраняя высоту и среднюю ширину символов в шрифте. Высота и ширина используются при обработке сообщения WM_SIZE для вычисления длины строки и максимального количества строк в зависимости от размера клиентской области.

Процедура окна создает и отображает курсор при обработке сообщения WM_SETFOCUS . Он скрывает и удаляет курсор при обработке сообщения WM_KILLFOCUS .

При обработке сообщения WM_CHAR процедура окна отображает символы, сохраняет их во входном буфере и обновляет положение курсора. Процедура окна также преобразует символы табуляции в четыре последовательных пробела. Символы backspace, linefeed и escape создают звуковой сигнал, но не обрабатываются в противном случае.

Процедура окна выполняет левое, правое, конечное и домашнее перемещение курсора при обработке сообщения WM_KEYDOWN . При обработке действия клавиши СТРЕЛКА ВПРАВО процедура окна проверяет состояние клавиши SHIFT и, если она не работает, выбирает символ справа от курсора при перемещении курсора.

Обратите внимание, что следующий код написан таким образом, чтобы его можно было скомпилировать как Юникод или ANSI. Если исходный код определяет ЮНИКОД, строки обрабатываются как символы Юникода; в противном случае они обрабатываются как символы 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; 
}