Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows

.pdf
Скачиваний:
0
Добавлен:
11.02.2026
Размер:
13.15 Mб
Скачать

Клавиатура

221

 

 

 

Добавление символа symb в конец строки осуществляется с помощью опера ции += :

text += symb;

Метод insert позволяет вставлять символ symb в произвольную позицию index:

text.insert(index, symb);

Для удаления n символов, начиная с позиции index, вызывается метод erase:

text.erase(index, n);

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

nLines = text.size() / nCharPerLine;

И наконец, содержимое строки text можно преобразовать к типу обычной C строки при помощи метода c_str. А это пригодится при вызове функции TextOut, поскольку она принимает в качестве четвертого параметра адрес C строки.

Обратите внимание на обработку сообщений WM_KEYDOWN, WM_CHAR, WM_SETFOCUS, WM_KILLFOCUS, а также на использование редактором Typer системного фиксиро ванного шрифта для вывода текста. Создание текстового редактора для пропор ционального шрифта было бы более трудной задачей.

Листинг 4.1. Проект Typer1

//////////////////////////////////////////////////////////////////////

// Typer.cpp #include <windows.h> #include <string> using namespace std;

#include «KWnd.h»

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

MSG msg;

KWnd mainWnd(«Typer», hInstance, nCmdShow, WndProc);

while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);

}

return msg.wParam;

}

//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

HDC hDC;

 

PAINTSTRUCT ps;

продолжение

 

1Не забудьте добавить к проекту файлы KWnd.h и KWnd.cpp, код которых приведен в листинге 1.2 (гла ва 1).

222

Глава 4. Средства ввода

 

 

Листинг 4.1 (продолжение)

TEXTMETRIC tm;

static string text; // буфер для хранения введенного текста string symb;

static int cxChar, cyChar, cxClient, cyClient; static int nCharPerLine, nClientLines;

static int xCaret = 0, yCaret = 0; int curIndex;

int nLines; // число «полных» строк текста

int nTailChar; // число символов в последней строке int x, y, i;

switch (uMsg) {

case WM_CREATE:

hDC = GetDC(hWnd);

SelectObject(hDC, GetStockObject(SYSTEM_FIXED_FONT)); GetTextMetrics(hDC, &tm);

cxChar = tm.tmAveCharWidth; cyChar = tm.tmHeight; ReleaseDC(hWnd, hDC); break;

case WM_SIZE:

//получить размеры окна в пикселах cxClient = LOWORD(lParam);

cyClient = HIWORD(lParam);

//вычислить размеры окна в символах (по 'x') и строках (по 'y') nCharPerLine = max(1, cxClient / cxChar);

nClientLines = max(1, cyClient / cyChar);

if (hWnd == GetFocus())

SetCaretPos(xCaret * cxChar, yCaret * cyChar); break;

case WM_SETFOCUS:

// создать и показать каретку CreateCaret(hWnd, NULL, 0, cyChar); SetCaretPos(xCaret * cxChar, yCaret * cyChar); ShowCaret(hWnd);

break;

case WM_KILLFOCUS:

// спрятать и уничтожить каретку HideCaret(hWnd);

DestroyCaret();

break;

case WM_KEYDOWN:

nLines = text.size() / nCharPerLine; nTailChar = text.size() % nCharPerLine;

switch (wParam) {

case VK_HOME: xCaret = 0; break;

Клавиатура

223

 

 

 

 

case VK_END:

if (yCaret == nLines)

xCaret = nTailChar; else

xCaret = nCharPerLine - 1; break;

case VK_PRIOR: yCaret = 0; break;

case VK_NEXT: yCaret = nLines; xCaret = nTailChar; break;

case VK_LEFT: xCaret = max(xCaret - 1, 0);

 

break;

case VK_RIGHT: xCaret = min(xCaret + 1, nCharPerLine - 1);

 

if ((yCaret == nLines) && (xCaret > nTailChar))

 

xCaret = nTailChar;

 

break;

case VK_UP:

yCaret = max(yCaret - 1, 0);

 

break;

case VK_DOWN:

yCaret = min(yCaret + 1, nLines);

 

if ((yCaret == nLines) && (xCaret > nTailChar))

 

xCaret = nTailChar;

 

break;

case VK_DELETE: curIndex = yCaret * nCharPerLine + xCaret; if (curIndex < text.size()) {

text.erase(curIndex, 1); InvalidateRect(hWnd, NULL, TRUE);

}

break;

}

SetCaretPos(xCaret * cxChar, yCaret * cyChar); break;

case WM_CHAR:

switch (wParam) {

case '\b': // символ 'backspace' if (xCaret > 0) {

xCaret--;

SendMessage(hWnd, WM_KEYDOWN, VK_DELETE, 1);

}

break;

case '\t': // символ 'tab'

do { SendMessage(hWnd, WM_CHAR, ' ', 1L); } while (xCaret % 8 != 0);

break;

case '\r': case '\n': // возврат каретки или перевод строки for (i = 0; i < nCharPerLine - xCaret; ++i)

text += ' '; xCaret = 0;

if (++yCaret == nClientLines) {

MessageBox(hWnd, "Нет места в окне", "Error", MB_OK); yCaret--;

}

продолжение

224

Глава 4. Средства ввода

 

 

Листинг 4.1 (продолжение)

 

break;

 

default: // любой другой символ

 

curIndex = yCaret * nCharPerLine + xCaret;

 

if (curIndex == text.size())

 

text += (char)wParam;

 

else {

 

symb = (char)wParam;

 

text.insert(curIndex, symb);

 

}

 

InvalidateRect(hWnd, NULL, TRUE);

 

if (++xCaret == nCharPerLine) {

 

xCaret = 0;

if (++yCaret == nClientLines) {

MessageBox(hWnd, "Нет места в окне", "Error", MB_OK); yCaret--;

}

}

break;

}

SetCaretPos(xCaret * cxChar, yCaret * cyChar); break;

case WM_PAINT:

hDC = BeginPaint(hWnd, &ps);

SelectObject(hDC, GetStockObject(SYSTEM_FIXED_FONT));

if (text.size()) {

nLines = text.size() / nCharPerLine; nTailChar = text.size() % nCharPerLine; for (y = 0; y < nLines; ++y)

TextOut(hDC, 0, y*cyChar, text.c_str() + y*nCharPerLine, nCharPerLine);

TextOut(hDC, 0, y*cyChar, text.c_str() + y*nCharPerLine, nTailChar);

}

EndPaint(hWnd, &ps); break;

case WM_DESTROY: PostQuitMessage(0); break;

default:

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

return 0;

}

//////////////////////////////////////////////////////////////////////

Откомпилируйте проект и посмотрите, как будет работать текстовый редак тор Typer. Поэкспериментируйте с изменением параметров каретки. Для этого най дите в блоке обработки сообщения WM_SETFOCUS следующую инструкцию:

Ìûøü

225

 

 

 

CreateCaret(hWnd, NULL, 0, cyChar);

Поменяйте ее сначала на инструкцию

CreateCaret(hWnd, 1, 0, cyChar);

а затем на инструкцию

CreateCaret(hWnd, 1, 10, cyChar);

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

Ìûøü

Мышь — это устройство ввода информации с одной или более кнопками. Win32 API поддерживает однокнопочную, двухкнопочную или трехкнопочную мышь, а также мышь с колесиком. Наличие мыши можно определить при помощи функ ции GetSystemMetrics:

fMouse = GetSystemMetrics (SM_MOUSEPRESENT);

Если мышь установлена, то значение fMouse будет равно TRUE. Количество кно пок у мыши определяется следующим вызовом:

cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS);

Если мышь не установлена, то функция вернет нулевое значение.

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

Трехкнопочная мышь не отличалась особой популярностью у пользователей Windows, пока Microsoft не выпустила модель IntelliMouse. Хотя эту мышь труд но считать интеллектуальной в прямом смысле этого слова, ее новый конструк тивный элемент в виде небольшого колесика, расположенного между левой и пра вой кнопками, позволяет использовать множество дополнительных функций. При нажатии на колесико оно функционирует как средняя кнопка. Но кроме этого колесико можно прокручивать, и программы, поддерживающие эту функцию, будут реагировать прокруткой или масштабированием документа. Это намного удобнее, чем работать мышью на полосе прокрутки, так как колесико выполняет свои функции даже тогда, когда курсор мыши находится за пределами окна с до кументом.

Наличие мыши с колесиком можно определить при помощи вызова все той же функции GetSystemMetrics:

fMouseWheel = GetSystemMetrics(SM_MOUSEWHEELPRESENT);

Функция возвращает значение TRUE, если у мыши есть колесо.

Хотя мышь является важнейшим средством ввода для пользователя, разработ чики программ для Windows часто дублируют, если это возможно, «мышиный» интерфейс соответствующим клавиатурным интерфейсом. При разработке новых программ весьма желательно придерживаться этой концепции, ориентированной на обеспечение максимума удобств для конечного пользователя.

226

Глава 4. Средства ввода

 

 

Терминология, связанная с мышью

Когда пользователь перемещает мышь, Windows передвигает по экрану неболь шую растровую картинку, которая выступает в качестве курсора мыши (mouse cursor).

У курсора мыши есть горячая точка (hot spot) — пиксел, соответствующий положению курсора на экране. Когда говорят о позиции курсора мыши, то имеют в виду именно позицию горячей точки. Например, горячая точка стандартного курсора в виде стрелки (IDC_ARROW) находится на кончике стрелки. Другой кур сор, в виде перекрестья (IDC_CROSS), имеет горячую точку в центре крестообраз ного шаблона.

Следует помнить, что курсор, используемый по умолчанию, для конкретного окна задается при определении структуры класса окна. Например, следующим оператором:

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

Действия пользователя с мышью принято описывать следующими терминами:

Щелчок — нажатие и отпускание кнопки мыши.

Двойной щелчок — быстрое двойное нажатие и отпускание кнопки мыши.

Перетаскивание — перемещение мыши при нажатой кнопке.

Кнопки трехкнопочной мыши называются левой, средней и правой кнопками.

Всвязанных с мышью идентификаторах, которые определены в заголовочных файлах Windows, используются аббревиатуры LBUTTON, MBUTTON и RBUTTON. На двухкнопочной мыши имеются только левая и правая кнопки. Единственная кноп ка однокнопочной мыши является левой.

Сообщения мыши

Изучая в предыдущем разделе ввод с клавиатуры, мы видели, что Windows посы лает сообщения клавиатуры только тому окну, которое имеет фокус ввода. С мы шью Windows ведет себя несколько иначе. Оконная процедура получает сообще ния мыши, и когда мышь проходит через окно, и при щелчке внутри окна, даже если окно не активно или не имеет фокуса ввода.

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

Кнопка

Нажатие

Отпускание

Нажатие (второй щелчок)

 

 

 

 

Левая

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

Средняя

WM_MBUTTONDOWN

WM_MBUTTONUP

WM_MBUTTONDBLCLK

Правая

WM_RBUTTONDOWN

WM_RBUTTONUP

WM_RBUTTONDBLCLK

 

 

 

 

Для всех этих сообщений значение параметра lParam содержит положение мыши. При этом в младшем слове находится значение координаты x, а в старшем слове — значение координаты y. Отсчет координат ведется от левого верхнего угла клиентской области окна. Эти значения можно извлечь из lParam при помощи мак росов LOWORD и HIWORD.

Ìûøü

227

 

 

 

Значение параметра wParam показывает состояние кнопок мыши и клавиш Shift и Ctrl. Вы можете проверить параметр wParam при помощи соответствующих бито вых масок:

Константа

Описание

 

 

MK_LBUTTON

Левая клавиша нажата

MK_MBUTTON

Средняя клавиша нажата

MK_RBUTTON

Правая клавиша нажата

MK_SHIFT

Клавиша Shift нажата

MK_CONTROL

Клавиша Ctrl нажата

 

 

Если пользователь щелкнет левой кнопкой мыши в клиентской области неак тивного окна, то Windows сделает это окно активным, а затем передаст оконной процедуре сообщение WM_LBUTTONDOWN.

Обработка двойного щелчка

Два последовательных щелчка воспринимаются системой как двойной щелчок, если они происходят в течение достаточно короткого промежутка времени. Пользо ватель может самостоятельно задать этот интервал при помощи системного при ложения Мышь (Mouse), которое можно найти в группе Панель управления.

Следует особо подчеркнуть, что окно будет получать сообщения о двойном щелчке (DBLCLK) только в том случае, если стиль соответствующего класса окна содержит флаг CS_DBLCLKS. Поэтому перед регистрацией класса окна нужно при своить полю style структуры WNDCLASS значение, включающее этот флаг. Впро чем, если используется объект класса KWnd, то следует модифицировать стиль класса окна вызовом следующих инструкций:

style = GetClassLong(hWnd, GCL_STYLE); SetClassLong(hWnd, GCL_STYLE, style | CS_DBLCLKS);

Если класс окна определен без флага CS_DBLCLKS и пользователь делает двойной щелчок левой кнопкой мыши, то оконная процедура последовательно получает со общения WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDOWN и WM_LBUTTONUP.

Если класс окна определен с флагом CS_DBLCLKS, то после двойного щелчка окон ная процедура получит сообщения WM_LBUTTONDOWN, WM_LBUTTONUP, WM_ LBUTTONDBLCLK и WM_LBUTTONUP. Легко заметить, что в этом случае второе сообщение

WM_LBUTTONDOWN заменяется сообщением WM_LBUTTONDBLCLK.

Пример обработки сообщения WM_LBUTTONDBLCLK приведен в листинге 4.4.

Обработка сообщений от колеса мыши

При нажатии на колесо мыши Windows генерирует такие же сообщения, какие вырабатываются при нажатии средней кнопки трехкнопочной мыши. Прокрутка же колесика вызывает сообщение WM_MOUSEWHEEL.

Если вам нужно обрабатывать это сообщение в коде программы, то в начале файла добавьте следующую директиву:

#include <zmouse.h>

В заголовочном файле zmouse.h определены все необходимые константы.

228

Глава 4. Средства ввода

 

 

Младшая часть параметра wParam сообщения WM_MOUSEWHEEL интерпретиру ется точно так же, как для других сообщений мыши, то есть показывает состояние кнопок мыши и клавиш Shift и Ctrl.

Старшая часть параметра wParam содержит значение, отображающее дистан цию, пройденную колесом. Оно рассчитывается как количество шагов колеса при прокрутке, умноженное на коэффициент WHEEL_DELTA. В заголовочном файле zmouse.h этот коэффициент определен со значением 120.

Обработка сообщения WM_MOUSEWHEEL довольно проста, и ее пример можно найти в листинге 4.5.

Теперь рассмотрим примеры программного кода.

Рисуем мышью

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

Попробуем реализовать эту операцию в программе Scribble (листинг 4.2).

Листинг 4.2. Проект Scribble

//////////////////////////////////////////////////////////////////////

// Scribble.cpp #include <windows.h> #include "KWnd.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

//////////////////////////////////////////////////////////////////////

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

MSG msg;

KWnd mainWnd("Scribble", hInstance, nCmdShow, WndProc);

while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);

}

return msg.wParam;

}

//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HDC hDC;

static int x, y; // позиция курсора мыши static BOOL bTracking = FALSE;

switch (uMsg)

{

case WM_CREATE:

hDC = GetDC(hWnd);

Ìûøü

229

 

 

 

break;

case WM_LBUTTONDOWN: bTracking = TRUE;

// начальная позиция x = LOWORD(lParam); y = HIWORD(lParam);

MoveToEx(hDC, x, y, NULL); break;

case WM_LBUTTONUP: bTracking = FALSE; break;

case WM_MOUSEMOVE: if (bTracking) {

// новая позиция

x = LOWORD(lParam); y = HIWORD(lParam); LineTo(hDC, x, y);

}

break;

case WM_DESTROY: ReleaseDC(hWnd, hDC); PostQuitMessage(0); break;

default:

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

return 0;

}

//////////////////////////////////////////////////////////////////////

Процесс рисования очередной линии начинается с нажатия левой кнопки мыши. Программа, обрабатывая сообщение WM_LBUTTONDOWN, устанавливает флаг слежения за мышью bTracking в значение TRUE и задает начальную позицию пера при помощи функции MoveToEx. После этого при обработке сообщения WM_MOUSEMOVE программа просто проводит линию в текущую позицию курсора мыши. Когда пользователь отпускает кнопку мыши, флаг bTracking сбрасывает ся в FALSE и рисование прекращается.

Обратите особое внимание на то, с каким контекстом устройства работают ри сующие функции MoveToEx и LineTo. Дескриптор контекста дисплея hDC объявлен со спецификатором static. Это раз. Программа получает контекст устройства при помощи функции GetDC в блоке обработки сообщения WM_CREATE, а освобождает его с помощью ReleaseDC в блоке обработки сообщения WM_DESTROY. Это два. Все это позволяет обновлять клиентскую область непосредственно в ответ на пользо вательский ввод информации с помощью мыши, не прибегая к механизмам, гене рирующим сообщение WM_PAINT. Иными словами, обеспечивается минимальная задержка между вводом пользователя и реакцией приложения. Статический класс памяти для переменной hDC здесь необходим потому, что требуется сохранять значение атрибута «текущая позиция пера», используемого и модифицируемого функцией LineTo, между двумя последовательными вызовами функции WndProc.

230

Глава 4. Средства ввода

 

 

На рис. 4.2 показан результат испытаний «графического редактора» Scribble.

Рис. 4.2. Рисование мышью в редакторе Scribble

Но что это?.. Как только пользователь пытается изменять размеры окна, ухва тившись мышью за его рамку, изображение тотчас бесследно исчезает! Ах, да… Мы же забыли про обработку сообщения WM_PAINT, которое генерируется при из менении размеров окна!

Давайте устраним эту оплошность. Но что же будет рисовать приложение в блоке обработки сообщения WM_PAINT? Ведь информация о траекториях движе ния мыши нигде не сохранена! Значит, надо где то сохранять массивы координат позиций мыши для каждой рисуемой линии. Сложность проблемы состоит в том, что количество линий заранее неизвестно, так же как и количество точек в каж дой линии может быть произвольным. Таким образом, использование статичес ких массивов будет крайне неудобным.

На выручку в таких ситуациях приходит замечательный класс vector из стан дартной библиотеки шаблонов STL. Объект класса vector — это, фактически, ди намический массив с переменным размером, обслуживаемый набором удобных для программиста методов. Аналогично классу string класс vector обеспечивает автоматическое управление выделением и освобождением памяти.

Чтобы использовать в программе объекты класса vector, необходимо в начало файла с исходным кодом добавить следующие директивы:

#include <vector> using namespace std;

Класс vector является шаблонным классом, поэтому при объявлении его объекта необходимо указать в угловых скобках имя конкретного типа, как показано ниже:

vector<int> v1; // v1 - вектор элементов типа int

Мы, конечно, не будем рассматривать здесь все методы класса vector, а остано вимся кратко только на тех, которые нужны для нашей программы.

После объявления вектора v1, приведенного выше, этот вектор пуст, то есть не содержит ни одного элемента. Добавление нового элемента в конец вектора осу ществляется при помощи метода push_back:

v1.push_back(value);

Эта инструкция добавляет новый элемент типа int в конец вектора v1 и при сваивает ему значение переменной value.