Приложение KBMSG
Наше следующее приложение с названием KBMSG поможет вам "почувствовать" клавиатурные сообщения. Это приложение создает одно главное окно, функция которого обрабатывает все сообщения от клавиатуры. Для каждого сообщения в окно выводится символическое имя кода сообщения, параметры сообщения wParam и lParam, а также название виртуальной клавиши, определенное в драйвере клавиатуры.
В верхней части главного окна приложения расположены две строки заголовка отдельных полей параметров сообщения. В остальной части окна организован построчный вывод самих параметров сообщения со сверткой (рис. 5.9).
Рис. 5.9. Главное окно приложения KBMSG
В первом столбце выводится символическое имя полученного от клавиатуры сообщения, во втором - символ ANSI, соответствующий параметру wParam полученного сообщения, затем следуют значения wParam и lParam в шестнадцатеричном представлении, и в последнем столбце выводится имя виртуальной клавиши, полученное по коду клавиши от драйвера клавиатуры.
Помимо способов обработки сообщений клавиатуры приложение KBMSG демонстрирует использование свертки экрана и загрузку в контекст отображения системного шрифта с фиксированной шириной букв.
Функция WinMain приложения KBMSG определена в файле kbmsg.cpp (листинг 5.7).
Листинг 5.7. Файл kbmsg\kbmsg.cpp
// ---------------------------------------- // Просмотр сообщений от клавиатуры // ----------------------------------------
#define STRICT #include <windows.h> #include <mem.h>
BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
char const szClassName[] = "KBMSGAppClass"; char const szWindowTitle[] = "KBMSG Application";
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
if(!InitApp(hInstance)) return FALSE;
hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
while(GetMessage(&msg, 0, 0, 0)) { // Вызываем функцию, создающую // символьные сообщения TranslateMessage(&msg);
DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
memset(&wc, 0, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;
aWndClass = RegisterClass(&wc); return (aWndClass != 0); }
Обратите внимание на цикл обработки сообщений, созданный в функции WinMain:
while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
Перед обычной функцией DispatchMessage вызывается функция TranslateMessage, которая на основе сообщений WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP создает сообщения WM_CHAR, WM_SYSCHAR, WM_DEADCHAR и WM_SYSDEADCHAR.
При регистрации класса окна указывается стиль класса, который требует перерисовки окна при изменении его вертикального или горизонтального размера:
wc.style = CS_HREDRAW | CS_VREDRAW;
Других особенностей (по сравнению с нашими предыдущими приложениями) функция WinMain не имеет.
Функция окна, обрабатывающая все сообщения, поступающие в главное окно приложения KBMSG, приведена в листинге 5.8.
Листинг 5.8. Файл kbmsg\wndproc.cpp
// ===================================== // Функция WndProc // =====================================
#define STRICT #include <windows.h> #include <stdio.h> #include <string.h>
void PrintMsg(HWND, WPARAM, LPARAM, char *);
static int cxChar, cyChar; RECT rect;
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования static TEXTMETRIC tm; // структура для записи метрик // шрифта
switch (msg) { case WM_CREATE: { // Получаем контекст отображения, // необходимый для определения метрик шрифта hdc = GetDC(hwnd);
// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm);
// Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth;
// Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading;
// Освобождаем контекст ReleaseDC(hwnd, hdc);
// Задаем верхнюю границу несвертываемой // области окна. Эта область будет использована // для двух строк заголовка rect.top = 3 * cyChar;
return 0; }
case WM_SIZE: { // Сохраняем координаты нижнего правого // угла окна rect.right = LOWORD(lParam); rect.bottom = HIWORD(lParam);
// Перерисовываем все окно InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd);
return 0; }
case WM_PAINT: { // Две строки заголовка сообщений char szHead1[] = "Message Char wParam lParam KeyName"; char szHead2[] = "------- ---- ------ ------ -------";
hdc = BeginPaint(hwnd, &ps);
// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Выводим строки заголовка TextOut(hdc, cxChar, cyChar/2, szHead1, sizeof(szHead1) - 1); TextOut(hdc, cxChar, cyChar/2 + cyChar, szHead2, sizeof(szHead2) - 1);
EndPaint(hwnd, &ps); return 0; }
case WM_KEYDOWN: { PrintMsg(hwnd, wParam, lParam, "WM_KEYDOWN"); break; }
case WM_KEYUP: { PrintMsg(hwnd, wParam, lParam, "WM_KEYUP"); break; }
case WM_SYSKEYDOWN: { PrintMsg(hwnd, wParam, lParam, "WM_SYSKEYDOWN"); break; }
case WM_SYSKEYUP: { PrintMsg(hwnd, wParam, lParam, "WM_SYSKEYUP"); break; }
case WM_CHAR: { PrintMsg(hwnd, wParam, lParam, "WM_CHAR"); break; }
case WM_SYSCHAR: { PrintMsg(hwnd, wParam, lParam, "WM_SYSCHAR"); break; }
case WM_DEADCHAR: { PrintMsg(hwnd, wParam, lParam, "WM_DEADCHAR"); break; }
case WM_SYSDEADCHAR: { PrintMsg(hwnd, wParam, lParam, "WM_SYSDEADCHAR"); break; }
case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }
// ========================================== // Функция для вывода параметров сообщений // от клавиатуры в окно // ==========================================
void PrintMsg(HWND hwnd, WPARAM wParam, LPARAM lParam, char *szMsg) { HDC hdc; char szBuf[256]; char szKeyName[20]; int nBufSize; int rc;
// Сворачиваем часть окна, не занятую заголовком ScrollWindow(hwnd, 0, -cyChar, &rect, &rect);
hdc = GetDC(hwnd);
// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Получаем имя клавиши, определенное в // драйвере клавиатуры rc = GetKeyNameText(lParam, szKeyName, 20); if(!rc) MessageBeep(0);
// Подготавливаем строку, описывающую сообщение nBufSize = wsprintf(szBuf, "%-14s %c %02x %08lX %-20s", (LPSTR)szMsg, (BYTE)wParam, (BYTE)wParam, lParam, (LPSTR)szKeyName);
// Выводим строку в нижнюю часть окна TextOut(hdc, cxChar, rect.bottom - cyChar, szBuf, nBufSize);
ReleaseDC(hwnd, hdc);
// Удаляем всю внутреннюю часть окна из // списка областей, требующих обновления ValidateRect(hwnd, NULL); }
При создании главного окна приложения (функцией CreateWindow) функция окна получает сообщение WM_CREATE. Наш обработчик этого сообщения создает контекст отображения и выбирает в него системный шрифт с фиксированной шириной букв, для чего вызывает функции GetStockObject и SelectObject:
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
Эти функции будут описаны позже. Сейчас мы только отметим, что после выполнения приведенной выше строки системный шрифт с переменной шириной букв будет заменен на системный шрифт с фиксированной шириной букв. С таким шрифтом проще работать, так как можно использовать известные вам по MS-DOS методы вывода таблиц.
После выбора шрифта обработчик сообщения WM_CREATE определяет метрики шрифта. В переменные cxChar и cyChar записывается соответственно ширина и высота букв.
Далее контекст отображения освобождается.
В поле top переменной rect типа RECT записывается координата верхней границы сворачиваемой области главного окна приложения:
rect.top = 3 * cyChar;
Выше этой границы, в полосе, ширина которой равна высоте символа, умноженной на 3, будет расположен заголовок таблицы. Этот заголовок состоит из строки названий полей таблицы и строки подчеркивания.
Область окна, расположенная ниже, будет использована для отображения параметров сообщений. По мере того как вы будете нажимать на клавиши, в этом окне будут появляться строки параметров сообщений. При появлении каждой новой строки вся область окна до границы заголовка будет смещена вверх. Новая строка будет добавлена в нижней части главного окна приложения. Словом, нижняя часть окна будет действовать как хорошо знакомая вам консоль MS-DOS.
При отображении окна функцией ShowWindow функция окна получает среди прочих сообщение WM_SIZE. Обработчик этого сообщения определяет координаты правого нижнего угла окна, сохраняя их в переменной rect:
rect.right = LOWORD(lParam); rect.bottom = HIWORD(lParam);
Поле bottom, содержащее y-координату нижней границы окна, будет использовано для вывода параметров сообщения в нижней части окна функцией TextOut.
После определения координат обработчик сообщения WM_SIZE объявляет все окно как требующее перерисовки и генерирует сообщение WM_PAINT, вызывая функцию UpdateWindow:
InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd);
Это приводит к тому, что при изменении размеров окна содержимое окна стирается и в него выводятся только две строки заголовка.
Обработчик сообщения WM_PAINT выбирает в контекст отображения системный шрифт с фиксированной шириной букв и выводит две строки заголовка. Этим его функции и ограничиваются.
Далее в исходном тексте функции окна расположены несколько обработчиков для клавиатурных сообщений. Все они вызывают функцию PrintMsg, отображающую параметры сообщения, вслед за чем передают сообщение функции DefWindowProc:
case WM_KEYDOWN: { PrintMsg(hwnd, wParam, lParam, "WM_KEYDOWN"); break; }
Таким образом, сообщения от клавиатуры не изымаются приложением, оно только "подсматривает" за ними.
Отображение параметров сообщения выполняется функцией PrintMsg, определенной в нашем приложении.
Кроме идентификатора окна, нужного для свертки окна и получения контекста отображения, функции PrintMsg передаются параметры сообщения и текстовая строка символического имени сообщения.
Функция PrintMsg начинает свою работу со свертки окна для освобождения в его нижней части места для вывода параметров очередного сообщения:
ScrollWindow(hwnd, 0, -cyChar, &rect, &rect);
Для свертки окна используется функция ScrollWindow, входящая в состав программного интерфейса Windows:
void WINAPI ScrollWindow(HWND hwnd, int dx, int dy, const RECT FAR* lprcScroll, const RECT FAR* lprcClip);
Первый параметр (hwnd) этой функции определяет идентификатор сворачиваемого окна.
Второй (dx) и третий (dy) парамеры определяют величину сдвига при свертке соответственно по горизонтали и вертикали. Для свертки влево и вверх параметры должны иметь отрицательное значение, для свертки вправо и вниз - положительное. В нашем случае окно сворачивается только в верхнем направлении, поэтому второй параметр равен нулю, а третий задает сдвиг на высоту буквы.
Четвертый параметр (lprcScroll) определяет прямоугольный участок внутренней области окна, подлежащей свертке. В нашем случае это нижняя часть окна (верхняя часть используется для вывода двух строк заголовка).
Пятый параметр (lprcClip) задает ограничивающую область, внутри которой выполняется сдвиг.
В нашем случае эта область совпадает с заданной четвертым параметром.
В качестве четвертого и пятого параметра можно задать значение NULL, в этом случае будет свернута вся внутренняя область окна.
Далее функция PrintMsg выбирает системный шрифт с фиксированной шириной букв и получает имя клавиши, соответствующее параметру lParam. Для получения имени клавиши, определенном в драйвере клавиатуры, вызывается уже знакомая вам функция GetKeyNameText:
rc = GetKeyNameText(lParam, szKeyName, 20); if(!rc) MessageBeep(0);
В нашем приложении эта функция копирует 20 символов имени клавиши в буфер szKeyName. В случае ошибки выдается звуковой сигнал, для чего вызывается функция MessageBeep.
Вслед за этим в буфере szBuf подготавливается строка, которая будет выведена в нижней части экрана. В эту строку включается символическое имя полученного сообщения, код ANSI, соответствующий параметру wParam, параметры wParam и lParam в шестнадцатеричном представлении, а также имя клавиши, определенное в драйвере клавиатуры.
Для вывода строки вызывается функция TextOut:
TextOut(hdc, cxChar, rect.bottom - cyChar, szBuf, nBufSize);
Строка выводится начиная с позиции (cxChar, rect.bottom - cyChar), то есть в нижней части главного окна приложения.
Перед завершением работы функция PrintMsg вызывает функцию ValidateRect:
ValidateRect(hwnd, NULL);
Эта функция описана в файле windows.h:
void WINAPI ValidateRect(HWND hwnd, const RECT FAR* lprc);
Для окна, идентификатор которого задан первым параметром, функция удаляет область, заданную вторым параметром, из области, требующей обновления.
Для чего здесь вызывается эта функция?
Дело в том, что после вызова функции ScrollWindow область свертки помечается как требующая обновления, поэтому функция окна получит сообщение WM_PAINT и только что выведенная строка будет стерта.
В нашем случае вывод в окно выполняется не во время обработки сообщения WM_PAINT, поэтому для предотвращения стирания мы должны объявить окно не требующим перерисовки. Если в качестве второго параметра функции ValidateRect указать значение NULL, вся внутренняя область окна будет помечена как не требующая перерисовки и сообщение WM_PAINT будет удалено из очереди приложения.
Файл определения модуля для приложения KBMSG приведен в листинге 5.9.
Листинг 5.9. Файл kbmsg\kbmsg.def
; ============================= ; Файл определения модуля ; ============================= NAME KBMSG DESCRIPTION 'Приложение KBMSG, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Программный интерфейс Windows версии 3.1 содержит еще одну функцию, предназначенную для свертки окна, - функцию ScrollWindowEx:
int WINAPI ScrollWindowEx(HWND hwnd, int dx, int dy, const RECT FAR* lprcScroll, const RECT FAR* lprcClip, HRGN hrgnUpdate, RECT FAR* lprcUpdate, UINT fuScroll);
Эта функция аналогична функции ScrollWindow, но имеет три дополнительных параметра - hrgnUpdate, lprcUpdate, и fuScroll.
Параметр hrgnUpdate определяет область, которая будет обновлена в результате свертки. Этот параметр можно указывать как NULL.
Параметр lprcUpdate является указателем на структуру типа RECT, в которую после вызова функции ScrollWindowEx будут записаны координаты границ области, обновленной в результате свертки. Этот параметр также можно указывать как NULL.
Параметр fuScroll определяет флаги, которые используются для управления режимом свертки:
Символическое имя | Описание |
SW_SCROLLCHILDREN | Выполнить свертку всех дочерних окон, пересекающих прямоугольную область, заданную параметром lprcScroll. Все эти дочерние окна получат сообщение WM_MOVE |
SW_INVALIDATE | После выполнения свертки область, указанная параметром hrgnUpdate, отмечается как требующая обновления |
SW_ERASE | Если указан флаг SW_INVALIDATE, обновленная область стирается посылкой сообщения WM_ERASEBKGND |