Лабораторна робота №3.
Тема роботи: Робота з мишею.
Ціль роботи: Придбання досвіду створення прикладного програмного забезпечення для Windows, забезпечення взаємодії додатка з маніпулятором миша, c використанням алгоритмічної мови Cі++.
Теоретична частина
У драйвері дисплея містяться кілька визначених курсорів миші, що можуть використовуватися в програмах. Найбільш типовим курсором є похила стрілка, що називається IDC_ARROW (вершина курсору - вістря стрілки). Курсор IDC_WAIT у виді піскового годинника звичайно використовується для індикації того, що програма чимось зайнята.
Курсор, установлюваний за замовчуванням для конкретного вікна, задається при визначенні структури класу вікна:
wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);
Повідомлення миші, зв'язані з робочою областю вікна.
Windows посилає повідомлення клавіатури тому вікну, що має фокус уведення. Повідомлення миші відрізняються: віконна процедура одержує повідомлення миші і коли миша проходить через її вікно, і при щелчку усередині вікна, навіть якщо вікно не активне чи не має фокуса введення.
У Windows для миші визначений набір з 21 повідомлення. Однак 11 з цих повідомлень не відносяться до робочої області, і програми для Windows звичайно ігнорують їх.
Якщо миша переміщається по робочій області вікна, віконна процедура одержує повідомлення WM_MOUSEMOVE.
Якщо кнопка миші натискається чи відпускається усередині робочої області вікна, віконна процедура одержує наступні повідомлення:
-
про натискання - WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN;
-
про відпускання - WM_LBUTTONUP, WM_RBUTTONUP, WM_MBUTTONUP;
-
про подвійного щиглика - WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK.
Для всіх повідомлень, зв'язаних з робочою областю, значення параметра lParam містить положення миші. Молодше слово - це координата Х, а старше - координата Y щодо верхнього лівого кута робочої області вікна.
Координати X і Y можна витягти з параметра lParam за допомогою макросів LOWORD і HIWORD.
Значення параметра wParam показує стан кнопок миші і клавіш <Shift> і <Ctrl>. Можна перевірити параметр wParam за допомогою бітових масок, визначених у заголовних файлах:
MK_LBUTTON - ліва кнопка натиснута.
MK_RBUTTON - права кнопка натиснута.
MK_MBUTTON - середня кнопка натиснута.
MK_SHIFT - клавіша <Shift> натиснута.
MK_CONTROL - клавіша <Ctrl> натиснута.
От як, наприклад, виглядає код, що перевіряє, чи була натиснута ліва кнопка миші при її русі по робочій області вікна, і малює на цьому місці пиксель:
case WM_MOUSEMOVE:
{
// стан кнопок миші
UINT fwKeys = wParam;
// горизонтальна позиція курсору
int xPos = LOWORD(lParam);
// вертикальна позиція курсору
int yPos = HIWORD(lParam);
if(fwKeys & MK_LBUTTON)
{
HDC hDC=GetDC(hWnd);
SetPixel(hDC,xPos,yPos,0l);
ReleaseDC(hWnd,hDC);
}
};
return 0;
При русі миші по робочій області вікна, Windows не виробляє повідомлення WM_MOUSEMOVE для всіх можливих положень миші.
Кількість повідомлень WM_MOUSEMOVE залежить від пристрою миші і швидкості, з яким віконна процедура може обробляти повідомлення про рух миші.
Якщо користувач клацне кнопкою миші в робочій області неактивного вікна, Windows зробить активним вікно, у якому був зроблений щелчок, і потім передасть віконній процедурі повідомлення WM_LBUTTONDOWN.
Якщо додаток одержує повідомлення WM_LBUTTONDOWN, то воно може упевнено вважати, що в даний момент його вікно активне.
Однак, віконна процедура може одержати повідомлення WM_LBUTTONUP, не одержавши спочатку повідомлення WM_LBUTTONDOWN. Це може случитися, якщо кнопка миші натискається в одному вікні, миша переміщається в інше вікно, і кнопка відпускається.
Аналогічно, віконна процедура може одержати повідомлення WM_LBUTTONDOWN без відповідного йому повідомлення WM_LBUTTONUP, якщо кнопка миші відпускається під час перебування миші в іншім вікні.
У цих правилах є виключення:
-
Віконна процедура може захопити мишу (capture the mouse) і продовжувати одержувати повідомлення миші, навіть якщо вона знаходиться поза робочою областю вікна.
-
Якщо системне модальне вікно повідомлень чи системне модальне вікно діалогу знаходиться на екрані, ніяка інша програма не може одержувати повідомлення від миші.
Обробка натискання клавіш <Shift> і <Ctrl> і кнопок миші.
При одержанні повідомлень миші, зв'язаних з робочою областю вікна, через параметр wParam передається значення, що дозволяє визначити, чи були одночасно з цим натиснуті кнопки миші чи клавіші <Shift> і <Ctrl> клавіатури.
Наприклад, якщо обробка повинна залежати від стану клавіш <Shift> і <Ctrl>, той додаток міг би скористатися наступною логікою:
UINT fwKeys = wParam; // стан кнопок миші
if(MK_SHIFT & fwKeys)
{
if(MK_CONTROL & fwKeys)
{ /* натиснуті клавіші <Shift> і <Ctrl> */ }
else
{ /* натиснута клавіша <Shift> */ }
}
else
{
if(MK_CONTROL & fwKeys)
{ /* натиснута клавіша <Ctrl> */ }
else
{ /* клавіші <Shift> і <Ctrl> не натиснуті */ }
}
Функція GetKeyState також може повертати стан кнопок миші чи клавіш <Shift> і <Ctrl>, використовуючи віртуальні коди клавіш VK_LBUTTON, VK_RBUTTON, VK_MBUTTON, VK_SHIFT і VK_CONTROL. При натиснутій кнопці чи клавіші значення функції, що повертається, GetKeyState негативне.
Функція GetKeyState повертає стан кнопки миші і клавіші в зв'язку з оброблюваним у даний момент повідомленням, тобто інформація про стан належним чином синхронізується з повідомленням.
Як приклад, розробимо програму MOUSE, що демонструє кілька простих тестів улучення. Програма поділяє робочу область на 25 прямокутників, одержуючи в такий спосіб масив розміром 5 на 5. Якщо ви клацаєте мишею на одному з прямокутників, то в прямокутнику малюється символ X. При повторному щелчку символ Х знищується. Зовнішній вигляд програми буде наступним:
Рисунок 2.4 Зовнішній вигляд програми Mouse
Створення додатка, що демонструє роботу з мишею:
-
Створіть новий проект і назвіть його MOUSE. Потім додайте до проекту файл типу C++ Source File за назвою mouse.cpp.
-
Тепер цілком скопіюйте уміст файлу sample.cpp з Лабораторної роботи №1 у mouse.cpp.
-
Крім заголовного файлу windows.h, не знадобляться ніякі інші файли.
-
У функцію WinMain варто внести наступні незначні зміни:
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{ static char szAppName[] = "Mouse";
HWND hWnd ;
…
RegisterClass (&wndclass) ;
hWnd = CreateWindow (szAppName, "Checker Mouse Hit-Test Demo",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
…
-
Функція WndProc, крім WM_PAINT і WM_DESTROY буде обробляти повідомлення WM_SIZE і WM_LBUTTONDOWN. Саму функцію модифікуйте так:
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{ …
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (iMsg)
{
case WM_SIZE :
case WM_LBUTTONDOWN :
case WM_PAINT :
case WM_DESTROY :
…
-
У всіх 25 прямокутників буде однакова висота і ширина. Значення ширини і висоти будуть зберігатися в cxBlock і cyBlock, і перераховуватися при зміні розмірів робочої області:
#include <windows.h>
#define DIVISIONS 5
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{ …
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static int cxBlock, cyBlock ;
HDC hdc ;
PAINTSTRUCT ps ;
switch (iMsg)
{
case WM_SIZE :
cxBlock = LOWORD (lParam) / DIVISIONS ;
cyBlock = HIWORD (lParam) / DIVISIONS ;
return 0 ;
case WM_LBUTTONDOWN :
…
-
У логіку обробки повідомлень WM_LBUTTONDOWN для визначення прямокутника, на якому був зроблений щиглик, використовуються координати миші. Цей оброблювач буде встановлювати поточний стан прямокутника в масиві fState, і робити відповідний прямокутник недійсним для вироблення повідомлення WM_PAINT. Якщо ширина чи висота робочої області не поділяється без залишку на п'ять, вузька смуга праворуч чи унизу робочої області не буде зарисована прямокутниками. У відповідь на щелчок миші в цій області, програма MOUSE, викликаючи функцію MessageBeep, повідомить про помилку.
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static BOOL fState[DIVISIONS][DIVISIONS] ;
static int cxBlock, cyBlock ;
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
int x, y ;
switch (iMsg)
{case WM_SIZE :
…
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 ;