95 в двух томах Том I



Pdf просмотр
страница21/41
Дата27.11.2016
Размер4.32 Mb.
Просмотров7898
Скачиваний0
ТипРеферат
1   ...   17   18   19   20   21   22   23   24   ...   41
перемещает курсор к верхнему левому углу прямоугольника; клавиша опускает его в нижний правый прямоугольник. Клавиши и и включают и выключают вывод символа перечеркивания Х.
CHECKER2.MAK
#------------------------
# CHECKER2.MAK make file
#------------------------ checker2.exe : checker2.obj
$(LINKER) $(GUIFLAGS) -OUT:checker2.exe checker2.obj $(GUILIBS) checker2.obj : checker2.c
$(CC) $(CFLAGS) checker2.c
CHECKER2.C
/*-------------------------------------------------
CHECKER2.C -- Mouse Hit-Test Demo Program No. 2
(c) Charles Petzold, 1996
-------------------------------------------------*/
#include
#define DIVISIONS 5
#define MoveTo(hdc, x, y) MoveToEx(hdc, x, y, NULL)
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{ static char szAppName[] = "Checker2";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass); hwnd = CreateWindow(szAppName, "Checker2 Mouse Hit-Test Demo",
WS_OVERLAPPEDWINDOW,

217
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd); while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{ static BOOL fState[DIVISIONS][DIVISIONS]; static int cxBlock, cyBlock;
HDC hdc;
PAINTSTRUCT ps;
POINT point;
RECT rect; int x, y; switch(iMsg)
{ case WM_SIZE : cxBlock = LOWORD(lParam) / DIVISIONS; cyBlock = HIWORD(lParam) / DIVISIONS; return 0; case WM_SETFOCUS :
ShowCursor(TRUE); return 0; case WM_KILLFOCUS :
ShowCursor(FALSE); return 0; case WM_KEYDOWN :
GetCursorPos(&point);
ScreenToClient(hwnd, &point); x = max(0, min(DIVISIONS - 1, point.x / cxBlock)); y = max(0, min(DIVISIONS - 1, point.y / cyBlock)); switch(wParam)
{ case VK_UP : y--; break; case VK_DOWN : y++; break; case VK_LEFT : x--; break; case VK_RIGHT : x++; break;

218 case VK_HOME : x = y = 0; break; case VK_END : x = y = DIVISIONS - 1; break; case VK_RETURN : case VK_SPACE :
SendMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON,
MAKELONG(x * cxBlock, y * cyBlock)); break;
} x =(x + DIVISIONS) % DIVISIONS; y =(y + DIVISIONS) % DIVISIONS; point.x = x * cxBlock + cxBlock / 2; point.y = y * cyBlock + cyBlock / 2;
ClientToScreen(hwnd, &point);
SetCursorPos(point.x, point.y); return 0; case WM_LBUTTONDOWN : x = LOWORD(lParam) / cxBlock; y = HIWORD(lParam) / cyBlock; if(x < DIVISIONS && y < DIVISIONS)
{ fState[x][y] ^= 1; rect.left = x * cxBlock; rect.top = y * cyBlock; rect.right =(x + 1) * cxBlock; rect.bottom =(y + 1) * cyBlock;
InvalidateRect(hwnd, &rect, FALSE);
} else
MessageBeep(0); return 0; case WM_PAINT : hdc = BeginPaint(hwnd, &ps); for(x = 0; x < DIVISIONS; x++) for(y = 0; y < DIVISIONS; y++)
{
Rectangle(hdc, x * cxBlock, y * cyBlock,
(x + 1) * cxBlock,(y + 1) * cyBlock); if(fState [x][y])
{
MoveTo(hdc, x * cxBlock, y * cyBlock);
LineTo(hdc,(x+1) * cxBlock,(y+1) * cyBlock);
MoveTo(hdc, x * cxBlock,(y+1) * cyBlock);
LineTo(hdc,(x+1) * cxBlock, y * cyBlock);
}
}
EndPaint(hwnd, &ps); return 0; case WM_DESTROY :

219
PostQuitMessage(0); return 0;
} return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Рис. 6.6 Программа CHECKER2
Логика обработки сообщения WM_KEYDOWN в программе CHECKER2 следующая: определяется положение курсора (GetCursorPos), координаты экрана преобразуются в координаты рабочей области (ScreenToClient), а затем координаты делятся на ширину и высоту прямоугольного блока. Полученные значения х и у показывают положение прямоугольника в массиве 5 на 5. Курсор мыши при нажатии клавиши не всегда находится в рабочей области, поэтому х и у должны быть обработаны макросами min и max таким образом, чтобы гарантировать их попадание в диапазон от 0 до 4.
Для клавиш со стрелками программа CHECKER2 увеличивает или уменьшает на 1 соответственно значение х или
у. Если нажатой клавишей является клавиша (VK_RETURN) или клавиша (VK_SPACE), то программа CHECKER2 использует функцию SendMessage для посылки себе же синхронного сообщения
WM_LBUTTONDOWN. Такая технология напоминает метод, использованный в программе SYSMETS в главе 5, где для полосы прокрутки окна в программу добавлен интерфейс клавиатуры. Логика обработки сообщения
WM_KEYDOWN заканчивается расчетом координат рабочей области, которые являются координатами центра прямоугольника, преобразованием их в координаты экрана (ClientToScreen) и установкой положения курсора
(SetCursorPos).
Использование дочерних окон для тестирования попадания
В некоторых программах, например в программе Windows PAINT, рабочая область окна делится на более мелкие логические области. Программа PAINT, окно которой представлено на рис. 6.7, имеет слева область меню со значками, а внизу область меню выбора цветов.
Рис. 6.7 Программа Windows PAINT
Программа PAINT при тестировании попадания в эти две области, перед определением выбранного пользователем пункта меню, должна учитывать положение меню внутри рабочей области.
А можно иначе. В действительности, в программе PAINT рисование меню и тестирование попадания упрощается, благодаря использованию "дочерних окон" (child windows). Дочерние окна делят рабочую область на несколько более мелких участков. Каждое дочернее окно имеет собственный описатель окна, оконную процедуру и рабочую область. Каждая оконная процедура получает сообщения мыши, которые относятся только к его дочернему окну.
Параметр lParam в сообщении мыши содержит координаты, заданные относительно верхнего левого угла рабочей области дочернего, а не родительского окна.
Дочерние окна, используемые таким образом, помогают сделать ваши программы более структурированными и модульными. Если дочерние окна используют разные классы окна, то каждое дочернее окно может иметь собственную оконную процедуру. Для разных классов окна могут также определяться и разный цветовой фон и разные, задаваемые по умолчанию, курсоры. В главе 8, мы рассмотрим "дочерние окна управления" (child window

220 controls) — предопределенные дочерние окна, имеющие форму полос прокрутки, кнопок и окон редактирования. А сейчас давайте рассмотрим, как можно использовать дочерние окна в программе CHECKER.
Дочерние окна в программе CHECKER
На рис. 6.8 представлена программа CHECKER3. В этой версии программы для обработки щелчков мыши создаются 25 дочерних окон. Здесь отсутствует интерфейс клавиатуры, но он может быть легко добавлен.
В программе CHECKER3 имеется две оконные процедуры, которые называются WndProc и ChildWndProc.
WndProc — это по-прежнему оконная процедура главного (или родительского) окна. ChildWndProc — это оконная процедура 25 дочерних окон. Обе оконные процедуры должны быть определены как функции обратного вызова
(CALLBACK).
Поскольку оконная процедура задается в структуре класса окна, которую вы регистрируете в Windows с помощью функции RegisterClassEx, то для двух оконных процедур в программе CHECKER3.С требуется два класса окна.
Первый класс окна — это класс главного окна, и называется он "Checker3". Второму классу окна назначено имя "Checker3_Child".
Большинство полей структурной переменной wndclass просто повторно используются при регистрации класса "Checker3_Child" в WinMain. Поле lpszClassName устанавливается в "Checker3_Child" (имя класса). Поле
lpfnWndProc устанавливается в ChildWndProc — оконную процедуру этого класса, а поля hIcon и hIconSm устанавливаются в NULL, поскольку с дочерними окнами значки не используются. Для класса окна "Checker3_Child" поле cbWndExtra структурной переменной wndclass устанавливается равной 2 байтам, или точнее, sizeof(WORD). Это поле требует от Windows зарезервировать 2 байта дополнительного пространства в структуре, которую Windows строит для каждого окна, создаваемого на основе данного класса окна. Вы можете использовать это пространство для хранения информации, которая может быть индивидуальной для каждого окна.
CHECKER3.MAK
#------------------------
# CHECKER3.MAK make file
#------------------------ checker3.exe : checker3.obj
$(LINKER) $(GUIFLAGS) -OUT:checker3.exe checker3.obj $(GUILIBS) checker3.obj : checker3.c
$(CC) $(CFLAGS) checker3.c
CHECKER3.C
/*-------------------------------------------------
CHECKER3.C -- Mouse Hit-Test Demo Program No. 3
(c) Charles Petzold, 1996
-------------------------------------------------*/
#include
#define DIVISIONS 5
#define MoveTo(hdc, x, y) MoveToEx(hdc, x, y, NULL)
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM); char szChildClass[] = "Checker3_Child"; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{ static char szAppName[] = "Checker3";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc;

221 wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass); wndclass.lpfnWndProc = ChildWndProc; wndclass.cbWndExtra = sizeof(WORD); wndclass.hIcon = NULL; wndclass.lpszClassName = szChildClass; wndclass.hIconSm = NULL;
RegisterClassEx(&wndclass); hwnd = CreateWindow(szAppName, "Checker3 Mouse Hit-Test Demo",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd); while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{ static HWND hwndChild[DIVISIONS][DIVISIONS]; int cxBlock, cyBlock, x, y; switch(iMsg)
{ case WM_CREATE : for(x = 0; x < DIVISIONS; x++) for(y = 0; y < DIVISIONS; y++)
{ hwndChild[x][y] = CreateWindow(szChildClass, NULL,
WS_CHILDWINDOW | WS_VISIBLE,
0, 0, 0, 0, hwnd,(HMENU)(y << 8 | x),
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
NULL);
} return 0; case WM_SIZE : cxBlock = LOWORD(lParam) / DIVISIONS; cyBlock = HIWORD(lParam) / DIVISIONS; for(x = 0; x < DIVISIONS; x++) for(y = 0; y < DIVISIONS; y++)
MoveWindow(hwndChild[x][y],

222 x * cxBlock, y * cyBlock, cxBlock, cyBlock, TRUE); return 0; case WM_LBUTTONDOWN :
MessageBeep(0); return 0; case WM_DESTROY :
PostQuitMessage(0); return 0;
} return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect; switch(iMsg)
{ case WM_CREATE :
SetWindowWord(hwnd, 0, 0); // on/off flag return 0; case WM_LBUTTONDOWN :
SetWindowWord(hwnd, 0, 1 ^ GetWindowWord(hwnd, 0));
InvalidateRect(hwnd, NULL, FALSE); return 0; case WM_PAINT : hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
Rectangle(hdc, 0, 0, rect.right, rect.bottom); if(GetWindowWord(hwnd, 0))
{
MoveTo(hdc, 0, 0);
LineTo(hdc, rect.right, rect.bottom);
MoveTo(hdc, 0, rect.bottom);
LineTo(hdc, rect.right, 0);
}
EndPaint(hwnd, &ps); return 0;
} return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Рис. 6.8 Программа CHECKER3
Вызов функции CreateWindow в WinMain создает на основе класса "Checker3" главное окно. Это нормально.
Однако, когда WndProc получает сообщение WM_CREATE, она, чтобы создать 25 дочерних окон на основе класса "Checker3_Child", 25 раз вызывает функцию CreateWindow. В следующей таблице дано сравнение параметров вызова функции CreateWindow в WinMain, создающей главное окно и вызова функции CreateWindow в WndProc, создающей 25 дочерних окон.
Параметр
Главное окно
Дочернее окно класс окна "Checker3"
"Checker3_Child" заголовок окна "Checker3...
"
NULL стиль окна WS_OVERLAPPEDWINDOW
WS_CHILDWINDOW| WS_VISIBLE

223
Параметр
Главное окно
Дочернее окно положение по горизонтали
CW_USEDEFAULT 0 положение по вертикали
CW_USEDEFAULT 0 ширина CW_USEDEFAULT
0 высота CW_USEDEFAULT
0 описатель родительского окна
NULL
hwnd описатель меню/ идентификатор дочернего окна
NULL
(HMENU)(y << 8 | x) описатель экземпляра
hInstance (HINSTANCE)
GetWindowLong (hwnd,
GWL_HINSTANCE) дополнительные параметры
NULL NULL
Обычно для дочерних окон требуются параметры положения, ширины и высоты, но в программе CHECKER3 положение и размер дочерних окон устанавливаются позже в WndProc. Описатель родительского окна для главного окна равен NULL, поскольку оно родительское. Описатель родительского окна необходим при вызове функции CreateWindow для создания дочернего окна.
В главном окне отсутствует меню, поэтому соответствующий параметр равен NULL. Для дочерних окон этот же параметр называется "идентификатором дочернего окна" (child ID). Это число уникально для каждого дочернего окна. Идентификатор дочернего окна приобретает важное значение при управлении дочерними окнами, поскольку сообщения для родительского окна, как мы увидим в главе 8, идентифицируются этим идентификатором дочернего окна. В программе CHECKER3 идентификаторы дочерних окон установлены так, чтобы они соответствовали положению, которое каждое дочернее окно занимает в массиве 5 на 5 внутри главного окна.
Описателем экземпляра в обоих классах является hInstance. Когда создается дочернее окно, значение hInstance извлекается из структуры, которую Windows поддерживает для окна, с помощью функции GetWindowLong.
(Вместо функции GetWindowLong можно было бы сохранить значение hInstance в глобальной переменной и непосредственно его использовать.)
Каждое дочернее окно имеет свой описатель окна, который хранится в массиве hwndChild. Когда WndProc получает сообщение WM_SIZE, она вызывает функцию MoveWindow для каждого из 25 дочерних окон. Параметры задают верхний левый угол дочернего окна относительно начала координат рабочей области родительского окна, ширину и высоту дочернего окна, а также необходимость перерисовки дочернего окна.
Теперь давайте рассмотрим ChildWndProc. Эта оконная процедура обрабатывает сообщения для всех 25 дочерних окон. Параметр hwnd для ChildWndProc является описателем дочернего окна, получающего сообщение. Когда
ChildWndProc обрабатывает сообщение WM_CREATE (что происходит 25 раз, поскольку имеется 25 дочерних окон), она использует функцию SetWindowWord для хранения 0 в дополнительном пространстве, зарезервированном внутри структуры окна. (Вспомните, что мы зарезервировали это пространство с помощью поля cbWndExtra при определении структуры класса окна.) ChildWndProc использует это значение для хранения текущего состояния прямоугольника (зачеркнут он символом Х или нет). Когда в дочернем окне происходит щелчок мыши, логика обработки сообщения WM_LBUTTONDOWN просто изменяет значение этого слова (0 на 1 или 1 на 0) и делает недействительной всю рабочую область дочернего окна. Эта область и является тем самым прямоугольником, в котором произошел щелчок. Обработка сообщения WM_PAINT вполне обычна, поскольку размер рисуемого прямоугольника равен размеру рабочей области окна.
Поскольку файл с текстом исходной программы на С и исполняемый файл .EXE программы CHECKER3 больше, чем аналогичные файлы для программы CHECKER1, то не будем утверждать, что CHECKER3 "проще", чем
CHECKER1. Но обратите внимание, что нам больше не нужно делать никакого тестирования попадания для мыши! Если какое-нибудь дочернее окно в программе CHECKER3 получает сообщение WM_LBUTTONDOWN, то значит в этом окне и было нажатие, и это все, что ему нужно знать.
Если вы хотите добавить к программе CHECKER3 интерфейс клавиатуры, запомните, что главное окно по- прежнему получает сообщения клавиатуры, поскольку оно имеет фокус ввода. Более подробно дочерние окна мы рассмотрим в главе 8.
Захват мыши
Оконная процедура обычно получает сообщения мыши только тогда, когда курсор мыши находится в рабочей или в нерабочей области окна. Но программе может понадобиться получать сообщения мыши и тогда, когда курсор мыши находится вне окна. Если это так, то программа может произвести "захват" (capture) мыши.

224
Рисование прямоугольника
Для того, чтобы понять, для чего может понадобиться захват мыши, давайте рассмотрим программу BLOKOUT1, представленную на рис. 6.9.
BLOKOUT1.MAK
#------------------------
# BLOKOUT1.MAK make file
#------------------------ blokout1.exe : blokout1.obj
$(LINKER) $(GUIFLAGS) -OUT:blokout1.exe blokout1.obj $(GUILIBS) blokout1.obj : blokout1.c
$(CC) $(CFLAGS) blokout1.c
BLOKOUT1.C
/*-----------------------------------------
BLOKOUT1.C -- Mouse Button Demo Program
(c) Charles Petzold, 1996
-----------------------------------------*/
#include
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{ static char szAppName[] = "BlokOut1";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass); hwnd = CreateWindow(szAppName, "Mouse Button Demo",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd); while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam;
}

225 void DrawBoxOutline(HWND hwnd, POINT ptBeg, POINT ptEnd)
{
HDC hdc; hdc = GetDC(hwnd);
SetROP2(hdc, R2_NOT);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);
ReleaseDC(hwnd, hdc);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{ static BOOL fBlocking, fValidBox; static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd;
HDC hdc;
PAINTSTRUCT ps; switch(iMsg)
{ case WM_LBUTTONDOWN : ptBeg.x = ptEnd.x = LOWORD(lParam); ptBeg.y = ptEnd.y = HIWORD(lParam);
DrawBoxOutline(hwnd, ptBeg, ptEnd);
SetCursor(LoadCursor(NULL, IDC_CROSS)); fBlocking = TRUE; return 0; case WM_MOUSEMOVE : if(fBlocking)
{
SetCursor(LoadCursor(NULL, IDC_CROSS));
DrawBoxOutline(hwnd, ptBeg, ptEnd); ptEnd.x = LOWORD(lParam); ptEnd.y = HIWORD(lParam);
DrawBoxOutline(hwnd, ptBeg, ptEnd);
} return 0; case WM_LBUTTONUP : if(fBlocking)
{
DrawBoxOutline(hwnd, ptBeg, ptEnd); ptBoxBeg = ptBeg; ptBoxEnd.x = LOWORD(lParam); ptBoxEnd.y = HIWORD(lParam);
SetCursor(LoadCursor(NULL, IDC_ARROW)); fBlocking = FALSE; fValidBox = TRUE;
InvalidateRect(hwnd, NULL, TRUE);
} return 0;

226 case WM_CHAR : if(fBlocking & wParam == '\x1B') // ie, Escape
{
DrawBoxOutline(hwnd, ptBeg, ptEnd);
SetCursor(LoadCursor(NULL, IDC_ARROW)); fBlocking = FALSE;
} return 0; case WM_PAINT : hdc = BeginPaint(hwnd, &ps); if(fValidBox)
{
SelectObject(hdc, GetStockObject(BLACK_BRUSH));
Rectangle(hdc, ptBoxBeg.x, ptBoxBeg.y, ptBoxEnd.x, ptBoxEnd.y);
} if(fBlocking)
{
SetROP2(hdc, R2_NOT);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);
}
EndPaint(hwnd, &ps); return 0; case WM_DESTROY :
PostQuitMessage(0); return 0;
} return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Рис. 6.9 Программа BLOKOUT1
Эта программа показывает немногое из того, что могло бы быть реализовано в программе рисования в Windows.
Вы начинаете рисовать, нажимая левую кнопку мыши, чтобы указать один из углов прямоугольника, и удерживая кнопку нажатой, вы двигаете мышь. Программа рисует контур прямоугольника, со вторым противоположным углом в текущей позиции курсора. После отпускания кнопки мыши, программа закрашивает прямоугольник. На рис. 6.10 показаны два прямоугольника, один из которых уже нарисован, а второй находится в процессе рисования.
При нажатии левой кнопки мыши, программа BLOKOUT1 сохраняет координаты мыши и первый раз вызывает функцию DrawBoxOutline. Функция рисует прямоугольник с использованием растровой операции R2_NOT, которая меняет цвет рабочей области на противоположный. При обработке последующих сообщений WM_MOUSEMOVE программа снова рисует такой же прямоугольник, полностью стирая предыдущий. Затем она использует новые координаты мыши для рисования нового прямоугольника. Наконец, когда программа BLOKOUT1 получает сообщение WM_LBUTTONUP, координаты мыши сохраняются, и окно делается недействительным, генерируя сообщение WM_PAINT для вывода на экран полученного прямоугольника.

227
Рис. 6.10 Вид окна программы BLOKOUT1
В чем же проблема?
Попытайтесь сделать следующее: нажмите левую кнопку мыши внутри рабочей области окна программы
BLOKOUT1, а затем переместите курсор за пределы окна. Программа перестает получать сообщения
WM_MOUSEMOVE. Теперь отпустите кнопку. Программа не получит сообщение WM_LBUTTONUP, поскольку курсор находится вне рабочей области. Верните курсор внутрь рабочей области окна программы BLOKOUT1.
Оконная процедура по-прежнему считает, что кнопка остается нажатой.
Это нехорошо. Программа не знает что происходит.
Решение проблемы — захват
Программа BLOKOUT1 показала свои некоторые общие возможности, но она имеет и очевидные недостатки. Для решения проблем такого типа был введен механизм захвата мыши. Если пользователь двигает мышь, то выход мыши за границы окна не должен создавать трудностей. Программа должна по-прежнему контролировать мышь.
Захватить мышь проще, чем поймать ее в мышеловку. Вам достаточно только вызвать функцию:
SetCapture(hwnd);
После вызова этой функции, Windows посылает все сообщения мыши в оконную процедуру того окна, описателем которого является hwnd. Сообщения мыши всегда остаются сообщениями рабочей области, даже если мышь оказывается в нерабочей области окна. Параметр lParam по-прежнему показывает положение мыши в координатах рабочей области. Эти координаты, однако, могут стать отрицательными, если мышь окажется левее или выше рабочей области.
Пока мышь захвачена, системные функции клавиатуры тоже не действуют. Когда вы захотите освободить мышь, вызовите функцию:
ReleaseCapture();
Эта функция возвращает обработку мыши в нормальный режим.
При работе под Windows 95 захват мыши несколько более ограничен, чем это было в предыдущих версиях
Windows. Точнее, если мышь была захвачена, а кнопка мыши в данный момент не нажата и курсор мыши перемещается в другое окно, то вместо захватившего мышь окна, сообщения мыши получит окно, на котором находится курсор, а не окно, захватившее мышь. Это сделано для предотвращения в системе ситуации, когда одна из программ захватит и не освободит мышь.
Другими словами, захватывайте мышь только в том случае, если кнопка нажимается в вашей рабочей области.
Освобождайте мышь, когда кнопка отпускается.
Программа BLOKOUT2
На рис. 6.11 представлена программа BLOKOUT2, иллюстрирующая зах-ват мыши.
BLOKOUT2.MAK

228
#------------------------
# BLOKOUT2.MAK make file
#------------------------ blokout2.exe : blokout2.obj
$(LINKER) $(GUIFLAGS) -OUT:blokout2.exe blokout2.obj $(GUILIBS) blokout2.obj : blokout2.c
$(CC) $(CFLAGS) blokout2.c
BLOKOUT2.C
/*---------------------------------------------------
BLOKOUT2.C -- Mouse Button & Capture Demo Program
(c) Charles Petzold, 1996
---------------------------------------------------*/
#include
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{ static char szAppName[] = "BlokOut2";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass); hwnd = CreateWindow(szAppName, "Mouse Button & Capture Demo",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd); while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam;
} void DrawBoxOutline(HWND hwnd, POINT ptBeg, POINT ptEnd)
{
HDC hdc; hdc = GetDC(hwnd);

229
SetROP2(hdc, R2_NOT);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);
ReleaseDC(hwnd, hdc);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{ static BOOL fBlocking, fValidBox; static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd;
HDC hdc;
PAINTSTRUCT ps; switch(iMsg)
{ case WM_LBUTTONDOWN : ptBeg.x = ptEnd.x = LOWORD(lParam); ptBeg.y = ptEnd.y = HIWORD(lParam);
DrawBoxOutline(hwnd, ptBeg, ptEnd);
SetCapture(hwnd);
SetCursor(LoadCursor(NULL, IDC_CROSS)); fBlocking = TRUE; return 0; case WM_MOUSEMOVE : if(fBlocking)
{
SetCursor(LoadCursor(NULL, IDC_CROSS));
DrawBoxOutline(hwnd, ptBeg, ptEnd); ptEnd.x = LOWORD(lParam); ptEnd.y = HIWORD(lParam);
DrawBoxOutline(hwnd, ptBeg, ptEnd);
} return 0; case WM_LBUTTONUP : if(fBlocking)
{
DrawBoxOutline(hwnd, ptBeg, ptEnd); ptBoxBeg = ptBeg; ptBoxEnd.x = LOWORD(lParam); ptBoxEnd.y = HIWORD(lParam);
ReleaseCapture();
SetCursor(LoadCursor(NULL, IDC_ARROW)); fBlocking = FALSE; fValidBox = TRUE;
InvalidateRect(hwnd, NULL, TRUE);
} return 0; case WM_CHAR : if(fBlocking & wParam == '\x1B') // i.e., Escape
{

230
DrawBoxOutline(hwnd, ptBeg, ptEnd);
ReleaseCapture();
SetCursor(LoadCursor(NULL, IDC_ARROW)); fBlocking = FALSE;
} return 0; case WM_PAINT : hdc = BeginPaint(hwnd, &ps); if(fValidBox)
{
SelectObject(hdc, GetStockObject(BLACK_BRUSH));
Rectangle(hdc, ptBoxBeg.x, ptBoxBeg.y, ptBoxEnd.x, ptBoxEnd.y);
} if(fBlocking)
{
SetROP2(hdc, R2_NOT);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);
}
EndPaint(hwnd, &ps); return 0; case WM_DESTROY :
PostQuitMessage(0); return 0;
} return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Рис. 6.11 Программа BLOKOUT2
Программа BLOKOUT2 — это та же самая программа BLOKOUT1, за исключением трех новых строчек кода: вызова функции SetCapture при обработке сообщения WM_LBUTTONDOWN и вызовов функции ReleaseCapture при обработке сообщений WM_LBUTTONUP и WM_CHAR. (Обработка сообщения WM_CHAR позволяет отказаться от захвата мыши при нажатии пользователем клавиши .)
Проверьте работу программы теперь: измените размер окна так, чтобы оно стало меньше экрана целиком, начните рисовать прямоугольник внутри рабочей области, а затем уведите курсор за границы рабочей области влево или вниз, и наконец, отпустите кнопку мыши. Программа получит координаты целого прямоугольника. Чтобы его увидеть, увеличьте окно.
Вам следует всегда пользоваться захватом мыши, когда нужно отслеживать сообщения WM_MOUSEMOVE после того, как кнопка мыши была нажата в вашей рабочей области, и до момента отпускания кнопки. Ваша программа станет проще, и ожидания пользователя будут удовлетворены.
1   ...   17   18   19   20   21   22   23   24   ...   41


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал