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

лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows

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

Ìûøü

231

 

 

 

Количество элементов в векторе в любой момент времени можно определить при помощи метода size.

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

vector<int>::iterator it; // объявление итератора it для класса vector<int> for (it = v1.begin(); it != v1.end(); ++it) {

// *it – значение элемента с адресом it

}

Здесь begin() — метод, возвращающий адрес первого элемента вектора v1, а end() — метод, возвращающий адрес воображаемого элемента, который мог бы следовать за последним элементом вектора v1.

Если нужно удалить все элементы из вектора и сделать его пустым, то приме няется вызов метода clear.

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

Сначала нужно определиться с тем, какие переменные должны быть добавле ны для сохранения информации о траекториях движения мыши. Каждая позиция курсора мыши описывается координатами x и y. В Win32 API есть структура POINT, которая позволяет объединять эти два значения. Поэтому, казалось бы, можно объявить следующий объект:

static vector<POINT> curve;

для накопления точек, описывающих текущую кривую линию. Но, к сожалению, в определении структуры POINT нет конструктора, который позволял бы исполь зовать анонимные экземпляры класса1 POINT(x, y), и поэтому компилятор не про пустит выражения следующего вида:

curve.push_back(POINT(x, y));

Значит, нам нужно определить свою структуру Point, в которой устранен ука занный недостаток:

struct Point {

Point(int _x = 0, int _y = 0) : x(_x), y(_y) {} int x;

int y;

};

Теперь определение объекта для накопления точек, представляющих текущую кривую линию, будет иметь следующий вид:

static vector<Point> curve;

Накопление заканчивается, как только пользователь отпускает кнопку мыши, и оконная процедура получает сообщение WM_LBUTTONUP. Сформированный век тор curve необходимо сохранить в другом векторе curves, который будет хранили щем для накопления объектов curve:

static vector<vector<Point> > curves;

1Напомним, что структура является частным случаем класса, в котором все члены по умолчанию считаются открытыми (public). Анонимный экземпляр класса — это временный объект, создаваемый явным вызовом конструктора.

232

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

 

 

В каком то смысле объект curves является аналогом двумерного массива. Его элементами являются объекты типа vector<Point>. Обратите внимание на пробел между двумя последними угловыми скобками. Если его не поставить, то компи лятор выдаст сообщение об ошибке.

Текст приложения ScribbleAdvanced, являющегося улучшенной версий Scribble, приведен в листинге 4.3.

Листинг 4.3. Проект ScribbleAdvanced

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

// ScribbleAdvanced.cpp #include <windows.h> #include <vector>

using namespace std;

#include "KWnd.h"

struct Point {

Point(int _x = 0, int _y = 0) : x(_x), y(_y) {} int x;

int y;

};

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

LPSTR lpCmdLine, int nCmdShow)

{

MSG msg;

KWnd mainWnd("ScribbleAdvanced", 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; // контекст устройства для рисования мышью HDC hdc; // контекст устройства для восстановления

// рисунка при обработке WM_PAINT PAINTSTRUCT ps;

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

static vector<Point> curve;

static vector<vector<Point> > curves; vector<Point>::iterator it;

int i, j;

switch (uMsg)

{

case WM_CREATE:

hDC = GetDC(hWnd);

Ìûøü

233

 

 

 

break;

case WM_LBUTTONDOWN: bTracking = TRUE;

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

MoveToEx(hDC, x, y, NULL); curve.push_back(Point(x, y)); break;

case WM_LBUTTONUP: if (bTracking) {

bTracking = FALSE; curves.push_back(curve); curve.clear();

}

break;

case WM_MOUSEMOVE: if (bTracking) {

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

x = LOWORD(lParam);

y = HIWORD(lParam); LineTo(hDC, x, y); curve.push_back(Point(x, y));

}

break;

case WM_PAINT:

hdc = BeginPaint(hWnd, &ps);

for (i = 0; i < curves.size(); ++i) { it = curves[i].begin(); MoveToEx(hdc, it->x, it->y, NULL);

for (it + 1; it != curves[i].end(); ++it) LineTo(hdc, it->x, it->y);

}

EndPaint(hWnd, &ps); break;

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

default:

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

}

return 0;

}

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

Кроме тех дополнений, о которых упоминалось ранее, в этой программе по явился также блок обработки сообщения WM_PAINT. Заметьте, что рисование здесь производится с дескриптором контекста дисплея hdc, который отличается от дес криптора hDC, используемого при обработке сообщений от мыши.

234

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

 

 

Обратите внимание на то, что доступ к элементам вектора curves осуществля ется по индексу, а доступ к элементам его элементов (то есть к объектам типа vector<Point>) — с использованием итератора it. Здесь у вас может возникнуть воп рос: «Как же так, ранее говорилось, что значением элемента с адресом it является выражение *it?» Да, все правильно, но учтите, что наш элемент является структу рой типа Point, а в коде нужен доступ к ее полям x и y. Поэтому вызов функции LineTo мог бы выглядеть так:

LineTo(hdc, (*it).x, (*it).y);

Этот вызов тоже работал бы правильно. Но язык C++ содержит более удоб ную синтаксическую конструкцию для такого случая, позволяя вместо (*it).x за писать it->x. Поэтому вызов LineTo имеет следующий вид:

LineTo(hdc, it->x, it->y);

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

Эластичные прямоугольники

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

Приложение ElasticRect, приведенное в листинге 4.4, демонстрирует реализа цию подобной операции. Кроме того, в нем показана обработка двойного щелчка мыши, по которому программа рисует квадрат фиксированного размера.

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

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

Проще всего стирание текущего изображения фигуры можно организовать, если в контексте устройства установлен режим рисования R2_XORPEN или R2_NOTXORPEN. В этом случае повторный вывод фигуры приводит к восстановлению того состоя ния холста, которое было перед первым выводом этой фигуры. Чем же различают ся эти два режима или эти две растровые операции: R2_XORPEN и R2_NOTXORPEN?

Если фигура рисуется черным пером на белом холсте, то используйте бинар ную растровую операцию R2_NOTXORPEN. Если белым пером на черном холсте, то — R2_XORPEN. Почему? Для ответа на этот вопрос рассмотрим реализацию этих опе раций в монохромной системе с кодировкой 1 бит/пиксел («0» — черный цвет, «1» — белый цвет), показанную в табл. 4.4 и 4.5.

Ìûøü

 

 

235

 

 

 

 

Таблица 4.4. Операция R2_XORPEN

 

 

 

 

 

 

Холст

Ïåðî

Результат операции R2_XORPEN

Примечание

 

 

 

 

 

0

0

0

 

0

1

1

Öâåò ïåðà

1

0

1

Цвет холста

1

1

0

 

 

 

 

 

Таблица 4.5. Операция R2_NOTXORPEN

 

 

 

 

 

 

Холст

Ïåðî

Результат операции R2_NOTXORPEN

Примечание

 

 

 

 

 

0

0

1

 

0

1

0

Цвет холста

1

0

0

Öâåò ïåðà

1

1

1

 

 

 

 

 

 

Когда фигура рисуется первый раз, то наибольший интерес представляют си туации, в которых цвет пера и холста различаются, что соответствует второй

итретьей строкам в таблицах. Естественно, мы хотели бы в результате этого рисо вания получить цвет пера, а не холста. В графе «Примечание» показано, что такой результат достигается при рисовании белым пером на черном холсте для растро вой операции R2_XORPEN и при рисовании черным пером на белом холсте для рас тровой операции R2_NOTXORPEN.

Теперь вернемся к представлению цвета в формате RGB. Так как для белого

ичерного цветов значения одного бита, приведенные в табл. 4.4 и 4.5, просто ти ражируются по всем разрядам, то выявленные рекомендации остаются в силе.

Все сказанное выше справедливо для рисования фигур сплошным черным или белым пером (стиль PS_SOLID). Если же эластичный прямоугольник рисуется пре рывистым пером (например, имеющим стиль PS_DOT), то ваша задача как про граммиста упрощается — вы может использовать любую растровую операцию:

иR2_XORPEN, и R2_NOTXORPEN. Также любая из этих операций может быть приме нена в случае рисования сплошным цветным пером.

Но хватит теории, пора предъявить код (листинг 4.4).

Листинг 4.4. Проект ElasticRect

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

// ElasticRect.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("Elastic Rectangle", hInstance, nCmdShow, WndProc);

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

продолжение

236

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

 

 

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

DispatchMessage(&msg);

}

return msg.wParam;

}

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

{

UINT style; static HDC hDC;

static int x1, y1, x2, y2; static BOOL bTracking = FALSE; static HBRUSH hOldBrush; static HPEN hDotPen, hOldPen;

switch (uMsg)

{

case WM_CREATE:

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

hDotPen = CreatePen(PS_DOT, 1, RGB(0,0,0)); hDC = GetDC(hWnd);

hOldBrush = (HBRUSH)SelectObject(hDC, GetStockObject(HOLLOW_BRUSH)); break;

case WM_LBUTTONDOWN: bTracking = TRUE;

SetROP2(hDC, R2_NOTXORPEN); x1 = x2 = LOWORD(lParam); y1 = y2 = HIWORD(lParam);

hOldPen = (HPEN)SelectObject(hDC, hDotPen); Rectangle(hDC, x1, y1, x2, y2);

break;

case WM_LBUTTONUP: if (bTracking) {

bTracking = FALSE; SetROP2(hDC, R2_COPYPEN);

// нарисовать окончательный прямоугольник x2 = LOWORD(lParam);

y2 = HIWORD(lParam); SelectObject(hDC, hOldPen); Rectangle(hDC, x1, y1, x2, y2);

}

break;

case WM_MOUSEMOVE: if (bTracking) {

//стереть предшествующий прямоугольник Rectangle(hDC, x1, y1, x2, y2);

//нарисовать новый прямоугольник

x2 = LOWORD(lParam);

y2 = HIWORD(lParam); Rectangle(hDC, x1, y1, x2, y2);

}

Ìûøü

237

 

 

 

break;

case WM_LBUTTONDBLCLK: bTracking = FALSE; x1 = LOWORD(lParam); y1 = HIWORD(lParam);

Rectangle(hDC, x1, y1, x1 + 100, y1 + 100); break;

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

break;

default:

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

}

return 0;

}

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

Особое внимание следует обратить на код обработки сообщений WM_CREATE,

WM_LBUTTONDOWN и WM_LBUTTONUP.

В блоке обработки сообщения WM_CREATE модифицируется стиль класса окна при помощи добавления флага CS_DBLCLKS. Без этой модификации не будет обраба тываться двойной щелчок мыши. Затем создается прерывистое перо hDotPen со сти лем PS_DOT и инициализируется контекст дисплея hDC, с которым будет идти вся дальнейшая работа. В контекст устройства выбирается пустая кисть (HOLLOW_BRUSH), чтобы рисуемые прямоугольники были прозрачными.

При обработке сообщения WM_LBUTTONDOWN программа устанавливает растро вую операцию R2_NOTXORPEN и выбирает в контекст устройства прерывистое перо hDotPen. Дальнейшее рисование в блоке обработки сообщения WM_MOUSEMOVE бу дет идти этим пером с установленным режимом рисования.

При обработке сообщения WM_LBUTTONUP программа возвращает растровую операцию по умолчанию R2_COPYPEN и рисует окончательный вид прямоугольни ка пером по умолчанию (hOldPen).

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

Улучшенное приложение для просмотра текстовых файлов

Во второй главе при изучении полос прокрутки и вывода текста было создано приложение TextViewer (см. листинг 2.2), предназначенное для чтения и вывода на экран текстового файла. В том варианте приложения сообщения мыши не учиты вались, но при работе с полосами прокрутки Windows берет на себя первичную обработку этих сообщений. В результате этой обработки Windows направляет окну

238

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

 

 

сообщения с префиксом SB_. Именно эти сообщения и обрабатывала оконная про цедура приложения.

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

Есть два пути решения второй задачи. Обрабатывая сообщение WM_KEYDOWN, можно было бы просто продублировать полностью код, отвечающий за обработку сообщений WM_VSCROLL и WM_HSCROLL. Но это некрасиво и непрактично с точки зрения дальнейшего сопровождения программы. Второй подход заключается в том, что при появлении аппаратного сообщение клавиатуры, которое дублирует действие мыши, следует вызывать тот код, который уже был написан для обра ботки сообщений, начинающихся префиксом SB_. Фактически, это означает, что оконная процедура должна послать сама себе сообщение SB_.

Win32 API имеет несколько функций для отправки сообщений. В данной си туации лучше использовать функцию SendMessage, имеющую следующий про тотип:

LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

Параметры функции имеют тот же смысл, что и параметры, передаваемые в оконную процедуру. Когда вызывается функция SendMessage, Windows вызыва ет оконную процедуру с дескриптором окна hWnd, передавая ей эти четыре пара метра. После того как оконная процедура заканчивает обработку сообщения, Windows передает управление следующей за вызовом SendMessage инструкции.

В листинге 4.5 приводится текст усовершенствованного приложения для про смотра текстовых файлов1.

Листинг 4.5. Проект TextViewerAdv

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

// TextViewerAdv.cpp

#include

<windows.h>

 

#include <zmouse.h>

// для обработки колесика мыши

#include

"KWnd.h"

 

#include

"KDocument.h"

 

#define FILE_NAME "D:\\Program files\\Microsoft Visual Studio\\VC98\\MFC\\SRC\\README.TXT"

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

KDocument doc; //==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

MSG msg;

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

Ìûøü

239

 

 

 

if (!doc.Open(FILE_NAME)) return 0;

KWnd mainWnd("Text Viewer Advanced", hInstance, nCmdShow, WndProc, NULL, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, CS_HREDRAW | CS_VREDRAW, WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL);

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; TEXTMETRIC tm;

int cxClient=0, cyClient=0; static int xInc, yInc; short status;

switch (uMsg)

{

case WM_CREATE:

hDC = GetDC(hWnd); GetTextMetrics(hDC, &tm); doc.Initialize(&tm); ReleaseDC(hWnd, hDC); break;

case WM_SIZE:

hDC = GetDC(hWnd); cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); if (cxClient > 0)

doc.ScrollSettings(hWnd, cxClient, cyClient); ReleaseDC(hWnd, hDC);

break;

case WM_VSCROLL: switch(LOWORD(wParam)) { case SB_LINEUP:

yInc = -1; break; case SB_LINEDOWN:

yInc = 1; break; case SB_PAGEUP:

yInc = -(int)doc.vsi.nPage; break; case SB_PAGEDOWN:

yInc = (int)doc.vsi.nPage; break; case SB_THUMBTRACK:

yInc = HIWORD(wParam) - doc.vsi.nPos; break; case SB_TOP:

yInc = -doc.vsi.nPos; break;

case SB_BOTTOM:

продолжение

240

 

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

 

 

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

 

yInc = doc.vsi.nMax - doc.vsi.nPos;

break;

default:

 

 

yInc = 0;

 

 

}

 

 

doc.UpdateVscroll(hWnd, yInc);

 

break;

 

 

case WM_HSCROLL:

 

 

switch(LOWORD(wParam)) {

 

case SB_LINELEFT:

 

xInc = -1;

break;

 

case SB_LINERIGHT:

 

xInc = 1;

break;

 

case SB_PAGELEFT:

 

xInc = -0.8 * (int)doc.hsi.nPage;

break;

case SB_PAGERIGHT:

xInc = 0.8 * (int)doc.hsi.nPage; break; case SB_THUMBTRACK:

xInc = HIWORD(wParam) - doc.hsi.nPos; break; case SB_TOP:

xInc = -doc.hsi.nPos; break; case SB_BOTTOM:

xInc = doc.hsi.nMax - doc.hsi.nPos; break; default:

xInc = 0;

}

doc.UpdateHscroll(hWnd, xInc); break;

case WM_MOUSEWHEEL:

if (LOWORD(wParam) & MK_SHIFT) {

xInc = -3 * (short)HIWORD(wParam) / WHEEL_DELTA; doc.UpdateHscroll(hWnd, xInc);

}

else {

yInc = -3 * (short)HIWORD(wParam) / WHEEL_DELTA; doc.UpdateVscroll(hWnd, yInc);

}

break;

case WM_KEYDOWN: switch (wParam) {

case VK_UP: SendMessage(hWnd, WM_VSCROLL, SB_LINEUP, 0); break;

case VK_DOWN: SendMessage(hWnd, WM_VSCROLL, SB_LINEDOWN, 0); break;

case VK_LEFT: SendMessage(hWnd, WM_HSCROLL, SB_LINELEFT, 0); break;

case VK_RIGHT: SendMessage(hWnd, WM_HSCROLL, SB_LINERIGHT, 0); break;

case VK_PRIOR: SendMessage(hWnd, WM_VSCROLL, SB_PAGEUP, 0); break;

case VK_NEXT: SendMessage(hWnd, WM_VSCROLL, SB_PAGEDOWN, 0); break;

case VK_HOME:

status = GetKeyState(VK_CONTROL);