КОДЫ РАСТРОВЫХ ОПЕРАЦИЙ
Выше уже было сказано, что одиннадцатый аргумент функции SiretchBltO - это код растровой операции. Другими словами, это код, который определяет, как при операции будут взаимодействовать биты, определяющие заливку и изображение совместимого контекста с изобра-
жением на действительном контексте. Комбинируются биты на основе логических операций над ними. По укоренившейся в книгах по программированию для Windows традиции, эти операции записываются в обратной польской нотации (не путать с венгерской, автор польской нотации не имеет к Microsoft ни малейшего отношения).
По той же традиции, биты, определяющие bitmap совместимого контекста, обозначают буквой S (source - источник, исходный), биты заливки - буквой Р (pattern - образец), а биты, на которых будет прорисовываться изображение - буквой D (destination - назначение, место назначения). Операции обозначаются следующим образом: а - побитовое И (AND), n -побитовое НЕТ (NO), о - побитовое ИЛИ (OR), x - побитовое исключающее ИЛИ (XOR).
Несколько слов о польской нотации. В ней операции записываются слева направо. Знак операции следует за операндами. Появление знака операции означает, что нужно произвести следующие действия: взять два последних операнда; произвести с ними требующуюся операцию; записать результат на место последних двух операндов. Фактически польская нотация описывает действия таким образом, словно операнды и операции находятся в стеке, для чего, собственно, эта польская нотация и была изобретена.
Обозначив знак операции как Ор, в польской нотации действия с битами можно записать таким образом: PSOp
Это говорит о необходимости взять пиксель патерны и прорисовываемого bitmap'a и произвести над ними операцию. Если в операции участвуют три операнда, то получим: DPSOplOp2
Что мы должны сделать в этом случае? Правильно, сначала произвести действие, определяемое Opl, с битами патерны и прорисовываемым bitmap'oM, после этого произвести Ор2 с полученным результатом и битами действительного контекста. Ничего сложного здесь нет.
Каждый код растровой операции представляется 32-битным целым. Старшее слово кода представляет собой индекс битовой операции, младшее - код операции. Как определяется индекс операции?
Давайте представим, что нам необходимо определить индекс растровой операции, определяемой в польской нотации записью DPSxx. Попутно можно определить и индекс операции PSx. Запишем друг под другом ОПРЕДЕЛЕННЫЕ значения Р, S
и D, а под ними - результаты побитовых операций PSx и DPSxx:
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
i |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
x 0 |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
'Sxx 1 |
0 |
0 |
1 |
0 |
1 |
1 |
0 |
Т а б л и ц а 9. Краткое описание колон растровых операций
] ^именование |
Инлскс операции |
Польская запись |
Эффект |
IU.ACKNF.SS |
0x00 |
{) |
Заполнение действительного |
|
|
|
контекста черным цветом |
\OTSRCERASE |
Oxl 1 |
DSoii |
|
NOTSRCCOPY |
0x33 |
Sn |
Прорисовываемый bitmap отобража- |
|
|
|
ется в негативном виде |
OSTINVHRT |
0x55 |
Dn |
Изображение действительного контекста |
|
|
|
проявляется негативным |
I'ATINVERT |
Ox5A |
DPx |
|
4RCTNVFRT |
0x66 |
DSx |
|
SRCAND |
Ox8X |
DSa |
|
MERGEPAINT |
OxBB |
DSno |
|
MI-RGFiCOPY |
OxCO |
PSa |
|
SRCCOPY |
OxCC |
S |
Копирование прорисовываемого |
|
|
|
bitmap'a на действительный кон- |
|
|
|
текст |
SRCPAINT |
OxEE |
DSo |
|
I'ATCOPY |
OxFO |
P |
Копирование патерны на действи- |
|
|
|
тельный контекст |
''\TPAINT |
OxFB |
DPSnoo |
|
WHITENESS |
OxFF |
1 |
Заполнение действительного |
|
|
|
кон текста белым цветом |
Фугом записываются не произвольные, а строго определенные значения.
Mil значения позволяют перебрать все возможные комбинации патерны,
«сходного и целевого bitmap'oB. Теперь, когда все стало ясно, вы можете i 'опробовать попрактиковаться в определении индексов любых операций. ! !есмотря на то, что существуют 256 индексов растровых операций, на
рактике используются только некоторые из них. В файле wingdi.h для чапболее часто используемых растровых операций определены иденти-'•'пкаторы, которые приведены в табл. 9.
На основании данных табл. 9 я затрудняюсь объяснить, как изменяется изображение при использовании разных растровых операций. Рекомендую читателю запустить приведенную выше программу несколько раз, и каждый раз в функции StretchBltQ указывать новую растровую операцию. Seeing is believing!
Теперь и одиннадцатый аргумент PatBlt() стал ясным и понятным - я просто копирую bitmap в окно. Только и всего. Кстати, понимание логики работы с растровыми операциями можег позволить избежать трудоемких преобразований bitmap'oe перед копированием.
ПОЛОСЫ ПРОКРУТКИ
Мне кажется, что у читателя может возникнуть вопрос: что делать в тех случаях, когда нам не нужно масштабировать bitmap, но нужно иметь возможность просматривать все части изображения? Ответ заключен в названии данного раздела - использование полос прокрутки.
Давайте, уважаемый читатель, чуть-чуть изменим предыдущую программу для того, чтобы продемонстрировать использование полос прокрутки, и посмотрим, как она будет работать. Текст программы с внесенными изменениями:
^include <windows.li*
long WINAPI DCDcmoWndProc ( HWND, UfNT, UINT, LONG );
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstaiicc, LPSTR IpszCmdParam, int nCmdShow )
HWND hWnd ;
WNDCLASS WndClass ;
MSG Msg;
char szClassName[] = "DCDemo";
/* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style = CSJ-IREDRAW [ CSJVREDRAW;
WndClass.lpfnWndProc - DCDcmoWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hlnstance = hlnstance ;
WndClass.hlcon - Loadlcon (NULL,IDI_APPLICATION);
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
WndClass.hbrBackground = (HBRUSH) GctStockObjcct (WHITEJ3RUSH);
WndClass.IpszMenuName = "MyMenu";
WndClass. IpszClassName — szClassNamc;
il'( !RegisterClass(&WndClass) )
McssageBox(NULL,"Cannot register class","Error",MB_OK); return 0;
hWnd = CreateWindow(szClassNamc, "Program No 1", WS_OVERLAPPEDWINDOW | WS_VSCROLL| WS_HSCROLL, CWJJSEDEFAULT, CWJJSEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hlnstance.NULL);
il'(!hWnd)
MessageBox(NULL,"Cannot create window","Error",MB_OK); return 0;
/* Show our window */ ShowWindow(hWnd,nCmdShow); UpdatcWindow(hWnd);
/* Beginning of messages cycle */
whilc(GctMcssage(&Msg, NULL, 0, 0))
TranslatcMessage(&Msg); DispatchMessage(&Msg);
return Msg.wParam;
LRESULT CALLBACK DCDcmoWndProc (HWND hWnd. UINT Message,
UINT wParam, LONG IParam )
HOC hDC, hCompatibleDC;
PAINTSTRUCT PamtStruct;
static HANDLE hBitmap;
HANDLE hOldBitmap;
RECTRect;
BITMAP Bitmap;
static int nliorizDifference = 0, nVertDilTerence ~ 0;
static int nHorizPosition = 0, nVertPosition - 0;
switch(Messagc) i
case WM CREATL:
hBitmap =• LoadlmagcfNULL, "MSDOGS.BMP" IMAGE_BITMAP,
о, о,
LR_LOADFROMFILE); return 0;
case WM_PAINT:
hDC ^ BeginPaint(hWnd, &PaintStruct); GetObject(hBitmap, sizeof(BITMAP), &Bitmap); hCompatibleDC = CreateCompalibleDC(hDC); hOldBitmap = SelectObject(hCompatibleDC, liBitmap); GctClientRcct(hWnd,&Rcct); BitBltdiDC, 0, 0, Rect.right, Rect.bottom,
hCompatibleDC, nHorizPosition, nVertPosilion, SRCC'OPY); if( (nHorizDii'terence = (Bitmap.bmWidth - Rect.right)) > 0)
SetScrollRange(hWnd, SBJTORZ, 0, nHonzDifference, TRUE); else
SctScrollRange(liWnd, SBJIORZ, 0. 0, TRUE); if( (nVertDifference = (Bitmap.bmHeight - Rect.bottom)) > 0)
SetScrol]Range(h\Vnd, SBJVERT, 0. nVertDilTcrence, TRUE); else
SetScrollRangc(hWnd, SB_VERT, 0. 0, TRUE); SelectObject(hCompatibleDC, hOldBitmap); DeleteDC(hCompatibleDC); EndPaint(hWnd,&PaintStmct); return 0;
case WM_VSCROLL: svvitcli(LOWORD(wParam))
\
case SB LINEDOWN;
if(nVcrtPosition < nVertDiiTerencc) nVerlPosition—;
break; case SB_LINEUP:
if(nVcrtPosi(ion > 0) nVerlPosition—;
break; ease SB_THUMBTRACK:
nVcrtPosition - HIWORD(wParam);
break; I
SetScrollPosfhWnd, SB VERT, nVcrtPosition, TRUE); InvalidatcRect(hWnd. NULL, TRUE); return 0;
case \VM_HSCROLL: switch(LOWORD(wParam))
I
'caseSB_LINEDO\VN: ii'fnHorizPosition < nHorizDilTerence)
ullori/.Position—'-; break:
case SB LINEUP: if(nHorizPosition > 0)
nHori/.Position--;
break;
case SBjniUMBTRACK: nHori/Posilion -- HIWORD(wParam); break;
SelScrollPos(h\Vmi, SBJIORZ, nHori/Position, TRUE); InvalidateRect(h\Vnd, NULL, TRUE); return 0;
case WM_DF,STROY: DeicteObjea(liBitmap); PostQu!lMessage(0); relnrti 0;
return DefWindowProc(h\Vnd,Messagc,wParam, Il'aram);
Пил. окна, создаваемого программой, показан на рис. 4.
Ноли в окне не отображаются горизонтальная и вертикальная полосы прокрутки, необходимо уменьшить размеры окна по горизонтали и нертикали.
BProgiamNol
i\ i. Пример |VK:.'ibi пилос прокрутки
Появились полосы прокрутки? Давайте разберемся, благодаря чему это произошло.
Во-первых, если сравнить вызовы функций CreateWindow() в этой и предыдущей программах, то можно увидеть, что у окна появились два новых стиля - WS__HSCROLL и WSJVSCROLL. Эти стили определяют наличие у окна горизонтальной и вертикальной полос прокрутки соответственно. Первый шаг сделан. Этот шаг можно было бы сделать и по-другому, определив полосы прокрутки как дочерние окна, но о дочерних окнах мы будем говорить позже. Разница между полосами прокружи, являющимися частью окна, и полосами прокрутки - дочерними окнами состоит в том, что дочерние окна имеют встроенный клавиатурный интерфейс, позволяющий воздействовать на полосу прокрутки с помощью клавиатуры. Встроенным полосам прокрутки, к сожалению, досталось только управление с помощью курсора мыши.
Теперь необходимо определить диапазон прокрутки, который определяет число шагов между крайними позициями бегунка (слайдера). По умолчанию для полос прокрутки, являющихся частью окна, этот диапазон определен от 0 до 100. Для того чтобы изменить диапазон прокрутки, необходимо вызвать функцию SetScrollRange(), которая в файле winuser.h определена следующим образом:
WINUSERAPI BOOL WINAPI SetScrollRange(HWND hWnd, int nBar,
int nMinPos, int nMaxPos, BOOL bRedniw);
Первый аргумент функции - хэндл окна, которому принадлежат полосы прокрутки. Второй аргумент определяет, для какой полосы прокрутки (вертикальной или горизонтальной) устанавливается диапазон. В данном случае этот аргумент может принимать значение SB_VERT или SB_HORZ, что определяет работу с вертикальной или горизонтальной полосой прокрутки. Третий и четвертый аргументы непосредственно указывают нижнюю и верхнюю границу диапазона прокрутки. Пятый аргумент представляет собой флаг, определяющий, нужно ли перерисовывать полосу прокрутки после определения диапазона. TRUE - полоса прокрутки перерисовывается, FALSE - перерисовка не нужна. Заметьте, что если диапазон прокрутки определен от 0 до 0, то полоса прокрутки становится невидимой. Это свойство используется и в приведенной выше программе. В том случае, когда размеры окна превышают размеры отображаемого bitmap'a, у полос прокрутки устанавливается диапазон от О до 0, следовательно, полоса прокрутки скрывается.
В данном случае с помощью функции SetScrollRangeQ диапазон прокрутки определен как разность между размером bitmap'a и размером окна по вертикали и по горизонтали, т. с. шаг полосы прокрутки соответствует одному пикселю.
Воздействовать на полосы прокрутки можно по-разному: во-первых, можно щелкнуть клавишей мыши на стрелах, расположенных по краям полосы; во-вторых, можно щелкнуть на полосе выше или ниже слайдера. Наконец, можно перетащить слайдер на другое место. Все эти воздействия приводят к тому, что оконная функция окна, которому принадлежат полосы прокрутки, получает сообщение WM VSCROLL (если действия производились вертикальной полосой) или WM_HSCROLL (реакция на воздействие на горизонтальную полосу).
Характер воздействия оконная функция может определить по параметрам сообщения. Младшее слово wParam, которое и определяет харак-i ср воздействия на полосу прокрутки, может принимать значения, приведенные в табл. 10. В таблице показано, что прокрутка при нажатии клавиши мыши в некоторых случаях производится на одну строку и одну страницу. В данном случае необходимо осознать, что понятия «строка» и «страница» ничего общего с текстовой строкой и страницей не имеют. Этими понятиями я заменит! условные единицы, на которые прокручивается изображение в окне. К примеру, в приведенной программе строке соответствует один пиксель, а понятие страницы вовсе не определено (что есть страница для картинки?).
После ознакомления с таблицей становится ясно, какие •xl.OWORD(wParam)» должна обрабатывать прикладная программа.
Старшее слово wParam используется только в тех случаях, когда LOWORD(wParam) равен SBJTHUMBPOSITION или SB THUMBTRACK. В этих случаях оно хранит позицию слайдера. В остальных случаях это значение не используется.
В тех случаях, когда полосы прокрутки реализованы как дочерние окна, IParam содержит хэндл окна полосы прокрутки. Если полоса реализована как часть окна, этот параметр не используется.
После того, как мы зафиксировали факт произведенного с полосой прокрутки действия и характер действия, программа должна правильно отреагировать на него и при необходимости изменить позицию слайдера в соответствии с произведенным воздействием. Делается это с помощью обращения к функции SetScrollPosQ, которая следующим образом описана в файле winuser.h:
WINUSERAPI int WINAPI SetScrolIPos(HWND hWnd, int nBar. int nPos,
BOOL bRcdraw);
Таблица 10. Идентификаторы характером воздействия на полосы прокрутки
Окончание табл. 10
Парамеф |
Значение |
Описание |
|
||
SBJLINF.UP |
0 |
Используется только с WM_VSCROLL; |
|||
|
|
щелчок мытью на стрелке вверх; приво- |
|
||
SB_LUNELEFT |
0 |
дит к прокрутке на одну «строку» вверх Используется только с WM HSCROLL, |
|
||
|
|
щелчок мышью на стрелке влево; приво- |
|
||
SBJLINEDOWN |
1 |
дит к прокрутке на одну «колонку» влево Используется только с \VM VSCROLL, |
|
||
|
|
щелчок мытью на стрелке ьни i; приводит к |
|
||
SBJJNERIGHT |
1 |
прокрутке на одну «строку» вниз Используется только с WM HSCROLL., |
|
||
|
|
щелчок мышью на стрелке вправо; приводит к |
|
||
SB_PAGFUP |
2 |
прокрутке на олну «колонку» вправо Используется •! ,ль„о с WMJVSCROLL, |
|
||
|
|
щелчок мышью на полосе прокрутки выше |
|
||
|
|
слайдера; приводит к прокрутке на одну |
|
||
SB PAGELEFT |
2 |
«страницу» вверх Используется ю.Т1,кос\\'М HSCROLL, |
|
||
|
|
щелчок мышью на полосе прокрутки левее |
|
||
|
|
слайдера; приводит к прокрутке на одну |
|
||
SB PAGEDOWN |
3 |
«страницу» влево Используется только с WM VSCROLL, |
|
||
|
|
щелчок мышью на полосе прокрутки ниже |
|
||
|
|
слайдера; приводит к прокрутке на одну |
|
||
SB_PAGERIGHT |
j |
«страницу» вниз Используется только с WM HSCROLL, |
|
||
|
|
щелчок мышью на полоее прокрутки правее |
|
||
|
|
слайдера. прпво.ттп к прокрутке на одну |
|
||
SB_THUMBPOSITION |
4 |
«страницу» вправо Перетаскивание слайдера закопчено, пользова- |
|
||
SB_THUMBTRACK |
5 |
тель отжал клавишу мыши Слайдер перетаскивается с помощью мыши, |
|
||
|
|
приводит к перемещению содержимого |
|
||
SB TOP |
6 |
чкрана Используется только с вертикальными полоса- |
|
||
SBJ.EFT |
6 |
ми прокрутки, реализованными как дочерние окна, пользователь нажал клавишу «Ноте» Используется только с горизонтальными |
|
||
|
|
полосами прокрутки, реализованными как |
|
||
|
|
дочерние окна, пользователь нажал клавишу «Ноте» |
|
||
SB BOTTOM |
7 |
Используется только с вертикальными полоса- |
|
||
|
|
ми прокрутки, реализованными как дочерние окна, пользователь нажал клавишу «bnd» |
|
||
I lapasieip |
Значение |
Опиеание |
|||
I SB RIGHT SBJ-NDSCROLL |
7 8 |
Используется только с горизонтальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавишу «End» Пользователь отпустил клавишу мыши после удержания се нажатой на стрелке или на полосе прокрутки |
|||
I lepiibrii аргумент - это хэндл окна, содержащего полосу прокрутки (в u)M случае, если полоса прокрутки реализована как часть окна), второй аргумент может принимать значение SBJVERT или SB HORZ (об этих значениях говорилось выше), третий аргумент определяет, в какую позицию должен быть установлен слайдер. И наконец, четвертый аргумент определяет, нужно ли перерисовывать полосу прокрутки после установки слайдера. Если последний аргумент равен TRUE, то полоса прокрутки будет перерисована.
Для того чтобы в соответствии с новой позицией слайдера изменилось изображение в рабочей области, окну необходимо послать сообщение \VM_PAINT, которое заставит окно перерисоваться. В программе, приведенной выше, сообщение WM_PAINT окну посылается с помощью вызова функции InvalidateRectQ. Из этого следует, что код обработки сообщения WM
PAINT
в оконной функции должен разрабатываться с учетом того, что содержимое окна может прокручиваться (скроллироваться).
II в заключение мне бы хотелось слегка посыпать голову пеплом. Лю-•fioii
хоть немного понимающий в программировании человек ужаснется, когда увидит, чго я загружаю изображение из файла при каждой перерисовке окна (в программе, использующей функцию StretchBltQ). Это резко замедляет работу программы и занимает слишком много ресурсов. Но в шпном случае целью было не написание программы, работающей оптимальным образом., а простая демонстрация того, что должна сделать программа для того, чтобы вывести на экран изображение.
'Кстати, когда я начинал писать этот раздел, я думал, что уложусь в две-три страницы. Зато теперь надеюсь, что у читателя не осталось никаких вопросов относительно манипулирования изображениями. Естественно, что манипулированием изображениями графические возможности Windows отнюдь не исчерпываются. По я достиг своей цели.
Теперь мне не придется постоянно просить читателя подождать, когда мы потом что-нибудь изучим. И теперь я готов к тому, чтобы продолжить изложение нашей «азбуки».
КОНТЕКСТ УСТРОЙСТВА И WM PAINT
Я уже говорил, что в Windows окно само отвечает за перерисовку себя. Для того чтобы окно осуществило перерисовку, оно должно получить сообщение WM_PAINT. Каким образом осуществляется перерисовка?
Обычно используют один из трех методов:
рабочая область может быть восстановлена, если ее содержимое формируется с помощью каких-либо вычислений;
последовательность событий, формирующих рабочую область, может быть сохранена, а затем "проиграна" сколь угодно раз (имеются в виду метафайлы, но их рассмотрение выходит за рамки этой книги);
можно создать виртуальное окно и направлять весь вывод в виртуальное окно, а при получении основным окном сообщения WM_PAINT копировать содержимое виртуального окна в основное.
Думаю, что читатель догадался, что в качестве виртуального окна используется контекст в памяти. Как его копировать, мы уже знаем. Но как рисовать на нем?
РИСОВАНИЕ ГРАФИЧЕСКИХ ПРИМИТИВОВ
Наверное, в подавляющем большинстве случаев читателю для работы с графикой будет достаточно того, о чем он только что прочитал. Теперь он сможет создавать изображение в графическом редакторе и копировать его в окно.
Но что делать читателю в том случае, когда необходимо это изображение создать в ходе самое программы? Как ему быть, если, к примеру, необходимо отобразить спектр радиосигнала, принятого его приемником? Или нарисовать график функции? Без краткого введения в основы рисования не обойтись. Если читателю не нужно в ближайшее время создавать изображение в программе, он может смело пропустить этот раздел и перейти к разделу «Взаимодействие программы с пользователем», а к этому разделу вернуться только в случае надобности.
Итак, Windows - операционная система, которая предоставляет пользователю графический интерфейс. Наша задача - научиться создавать в программе изображение, которое в дальнейшем мы будем использовать.
ТО, БЕЗ ЧЕГО РИСОВАНИЕ НЕВОЗМОЖНО
А невозможно рисование, во-первых, без инструментов.
Инструментами рисования в Windows являются перо (реп) и кисть (brash). Перо является инструментом для прорисовки линий, цвет и способ заполнения замкнутых графических объектов, таких, как круги, прямоугольники, эллипсы и так называемые регионы, определяются текущей кистью. Во-вторых, рисование невозможно без определения той точки, от которой мы начинаем прорисовку того или иного графического объекта. Обычно эта точка называется текущей графической позицией.
Установка текущей позиции
Для установки текущей позиции используется функция MoveToExQ. В файле заголовков wingdi.h эта функция описывается следующим образом:
WTNGDIAPI BOOL WINAPI MoveToEx(HDC, irit, int, LPPOINT);
Первый аргумент - это контекст устройства, на котором мы будем рисовать, второй и третий - координаты точки, в которую мы устанавливаем текущую графическую позицию. Последний аргумент - указатель на структуру типа POINT, в которую функция запишет координаты старой текущей позиции. Структура типа POINT описана в файле windef.h и ее описание выглядит следующим образом:
typedef struct tagPOINT
LONG x; LONG y; } POINT, *PP()INT, NEAR *NPPOINT, FAR *LPPOINT;
Если при вызове функции указатель на структуру типа POINT равен NULL, то координаты старой текущей позиции не возвращаются.
Прорисовка одного пикселя
Прорисовать один пиксель в определенной позиции мы можем с помощью вызова функции SetPixelQ, описанной в wingdi.h:
WINGDIAPF COLORREF WINAPI SetPixcl(HDC. int. int, COLORREF);
Первые три аргумента очевидны - контекст устройства вывода и координаты прорисовываемого пикселя. Но что такое COLORREF?
Здесь следует пояснить, что каждый пиксель на экране состоит из тех микроточек - красной, зеленой и синей. Каждая из этих микроточек может светиться с интенсивностью от 0 (микроточка не светится) до 255 (максимальная яркость). Например, если светится только красная составляющая, то получаются цвета от темно-бордового (почти черного) до ярко красного. Комбинируя микроточки и их интенсивность, мы можем определить почти 17 миллионов цветов (будут ли они все поддерживаться на компьютере читателя, определяется видеоподсистемой компьютера, но это уже другой разговор). Обычно в таких случаях говорят об RGB значениях цвета (red, green, blue - красный, зеленый, голубой).
Вернемся к COLORREF. Опять обратимся к заголовочному файлу, но на сей раз не к wingdi.h, а к windef.h:
typedcf DWORD COLORREF;
Понятно, что COLORREF - это двойное слово. Оно кодируется следующим образом:
OxOObbggrr
т. е. младший байт определяет интенсивность красного, второй - зеленого, третий - синего цвета. Старший байт должен быть нулевым. Для того чтобы облегчить жизнь пользователю, Microsoft в wingdi.h вставила макрос RGB:
«define RGBfr. g, b) ( (COLORREF) (((BYTF.)(r) | ((WORD) ((BYTEKg)) <-'X ))
(((d/aokd) (ija.ij-:) (p)) «\c)))
С первого взгляда в этом не разобраться. Поэтому приведу пример определения цвета с RGB - значениями 0, 100, 200
RGB(0, 100,200);
Стало понятнее, не правда ли?
При нормальном завершении функция возвращает предыдущее значение цвета пикселя. Если возвращаемое значение равно -1, то по говорит либо о возникновении ошибки, либо о том, что координаты пикселя вышли за пределы рабочей области окна.
Теперь мы готовы прорисовывать пиксели везде, где только можно. А если мы сумеем прорисовывать один пиксель, то сможем прорисовать и много. Но для того, чтобы рисовать, скажем, прямую, необходимо знать и реализовать в программе алгоритм прорисовки линий. То же можно сказать и о кругах, эллипсах и т. д. Думаю, что перспектива самостоятельной разработки этих алгоритмов читателю вовсе не улыбается. Но нет ничего страшного, команда разработчиков Win32 и здесь сняла проблему с программистов. Перейдем к достаточно обширной теме под названием
РИСОВАНИЕ ГРАФИЧЕСКИХ ПРИМИТИВОВ
Создание пера для рисования линий
Рисование графических примитивов производится с помощью перьев. В Windows'95 есть три предопределенных пера - черное (BLACK_PEN), белое (W1IITE_PEN) и прозрачное (NULL_PEN). При создании окна по умолчанию ему присваивается черное перо. Хэндл каждого из них может быть получен с помощью функции GetStockObjectQ. Естественно, что программиста ие может удовлетворить столь малое число перьев, поэтому для прорисовки линий можно воспользоваться пером, созданным в программе посредством вызова функции CreatePenQ. Как всегда, обращаемся к файлам заголовков, в данном случае - к файлу wingdi.h:
WINGDIAPI HPEN WINAPI CreatePen(int, int. COLORREF);
Первый аргумент определяет стиль кисти. В wingdi.h эти стили описаны достаточно образно. Для того чтобы сохранить стиль этого описания (не путать со стилем кисти) я включил его третьим столбцом в табл. 11.
Не правда ли, «seeing is believing»?
Второй аргумент функции CreatePen() - толщина пера в логических единицах. Если этот аргумент равен 0, то толщина пера делается равной одному пикселю.
Третий аргумент - цвет чернил. Теперь для того, чтобы мы могли использовать наше перо, необходимо сделать его текущим в контексте устройства. Делается это уже давно знакомой нам функцией SclectObjectO. После того, как мы отработаем с пером, необходимо удалить его, вызвав функцию DeleteObject().
Мы создали перо. А теперь нам необходимо научиться рисовать примитивы.
Таблица И. Возможные стили кисти
Стиль пера |
Значение |
Описание |
Эффект |
PS SOLID |
0 |
|
Сплошная линия |
PS DASH |
1 |
|
Пунктирная линия |
|
|
|
|
PS DOT |
2 |
|
Линия из точек |
PS_DASHDOT |
3 |
|
Штрих-пунктирная линия (тирс- |
|
|
|
точка) |
PS DASHDOTDOT |
4 |
|
Штрих-пунктирная линия (тире - |
|
|
|
точка - точка) |
PS NULL |
5 |
|
Прозрачное перо |
PSJNSIDEFRAME |
6 |
|
При рисовании замкнутой фигуры |
|
|
|
граница фигуры будет определяться |
|
|
|
по внешнему краю, а не по середине |
|
|
|
линии (если толщина пера более 1 |
|
|
|
пикселя) |
Рисование линии
Нарисовать линию можно с помощью функции LineTo(). Она описана в файле wingdi.h:
WINGDIAPI BOOL WINAPI LincTo(HDC, int, int);
Первый аргумент - контекст устройства. Второй и третий аргументы - координаты точки, ДО КОТОРОЙ ОТ ТЕКУЩЕЙ ПОЗИЦИИ будет проведена линия. При успешном завершении функция возвращает TRUE.
Но здесь же возникает вопрос: где будет находиться текущая позиция после успешного выполнения функции? А будет она находиться там, где закончилась линия. Это сделано для того, чтобы легко можно было рисовать ломаные линии. В таком случае не нужно многократно вызывать функцию MoveToExQ для установления новой текущей позиции.
Рисование прямоугольника
Прямоугольник можно нарисовать, обратившись к функции RectangleQ. Её описание содержится в файле wingdi.h:
WINGDIAPI BOOL WINAPI Rectangle(HDC, int, int, int, int);
Аргумент первый понятен без объяснений - хэндл контекста устройства. Остальные аргументы - координаты верхнего левого и нижнего
правого углов прямоугольника. TRUE возвращается при нормальном завершении операции. Прямоугольник автоматически заполняется цветом и способом, определяемым текущей кистью.
Рисование эллипса
Для рисования эллипса необходимо вызвать функцию EllipseQ, которая в wingdi.h описывается следующим образом:
WFNGDIAPI BOOL WINAPI Ellipse(HDC, int, int, int, int);
Первый аргумент - это, как всегда, контекст устройства. Для того чтобы понять, как определяется эллипс, предлагаю читателю обратиться к рис. 5.
Как видно из рисунка, эллипс ограничен прямоугольником. Именно через координаты этого прямоугольника и определяется прорисовываемый эллипс. Второй и третий аргументы - координаты левого верхнего угла прямоугольника (на рисунке обозначены как UpX, UpY), четвертый и пятый аргументы - координаты нижнего правого угла (на рисунке обозначены как LowX, LowY).
Окружность является частным случаем эллипса. И в данном случае, если мы определим прямоугольник, у которого ширина равна высоте, т. е. квадрат, вместо эллипса получим окружность.
Как эллипс, так и окружность после прорисовки заполняются цветом и атрибутами текущей кисти.
UpX, UpY
EndX, EndY
StartX, StartY
~~LowX,LowY
Рис. 5. Определение аргумента функции FJlipscQ
Узнав, как рисуется эллипс, мы можем узнать, как рисуется прямоугольник с закругленными углами.
Рисование прямоугольника с закругленными краями
Прямоугольник с закругленными краями рисуется с помощью функции RoundRectQ. Из файла wingdi.h добываем ее описание WINGDIAPI BOOL WINAPI RoundRectfHDC, int, int, int, int. int. int);
Первые пять аргументов полностью идентичны аргументам функции Rect(). Последние два аргумента содержат ширину и высоту эллипса, определяющего дуги. После прорисовки прямоугольник закрашивается текущей кистью. В случае успешного завершения функция возвращает TRUE.
Рисование дуги и сектора эллипса
Возьмем из файла wingdi.h описание функции Агс(), которая используется для рисования дуги:
WINGDIAPI BOOL WINAPI Arc(HDC, int, int, int, int, inl, int. int, int);
Первые пять аргументов полностью аналогичны аргументам функции EllipseQ. Непосредственно дуга определяется ещё двумя точками. Первая - начало дуги - находится на пересечении эллипса, частью которого является дуга, и прямой, проходящей через центр прямоугольника и точку начала дуги. На рис. 5 начало дуги обозначено StartX, StartY. Вторая - конец дуги - определяется аналогично. Конец дуги обозначен EndX, EndY. Таким образом, для прорисовки дуги необходимо сначала определить точки StartX, StartY и EndX, EndY, после чего прорисовывать дугу. Дуга прорисовывается против часовой стрелки.
У функции Pie(), которая применяется для рисования сектора эллипса, набор аргументов и их назначение абсолютно идентичны функции Агс().
Несколько слов о заполнении объектов
Как читатель уже знает, заполнение замкнутых графических объектов происходит с помощью текущей кисти. Программист может использовать предопределенную кисть, а может создать свою собственную, после чего сделать ее текущей с помощью функции SelectObject().
Простейшим видом кисти является так называемая сплошная кисть, которая создается с помощью функции CreateSolidBrush():
WINGDIAPI HBRUSH WINAPI CreatcSolidBrush(COI.ORRP.F):
Единственный аргумент этой функции - цвет кисти (может, лучше сказать не кисти, а краски?).
Штриховая кисть создается с помощью функции CreateHatchBrush():
WINUDIAPI HBRUSH WINAPI СгсаюНаюШшЩли. COLORREF);
Первый аргумент этой функции - стиль штриховки. Возможные стили приведены в табл. 12.
Второй аргумент указывает цвет штриховки.
И наконец, с помощью функции CreatePattemBrush() мы можем создать кисть, которая при заполнении будет использовать bitmap. В wingdi.h она описана следующим образом:
WtNGDlAPf HHRUSH WINAPI CrcalcPattcrnBrush(HHITMAP);
Уже по типу аргумента видно, что единственным аргументом этой функции является хэндл bitmap'a.
Эти три функции при успешном завершении возвращают хэндл созданной кисти. В том случае, если произошла какая-то ошибка, возвращаемое значение равно NULL.
Давайте закрепим те знания, которые мы получили, рассмотрев небольшую демонстрационную программу.
К большому моему сожалению, до изучения меню я не могу написать программу, в которой действия пользователя определялись бы его выбором. Мне сейчас придется написать программу, которая только демонстрирует вынод на экран различных графических примитивов.
Т а б л и ц а 12. Стили штриховки
Спин, штриховки |
Значение |
Описание |
Эффект |
HS HORIZONTAL HS VERTICAL HS_FDIAGONAL |
0 1 2 |
liill \v\\\ |
Горизонтальная штриховка Вертикальная штриховка Наклонная слева направо |
HSJBDIAGONAL |
3 |
Hill |
штриховка Наклонная справа налево |
HS_CROSS HS DIAGCROSS |
4 5 |
+ —— }-+ xxxxx |
штриховка Штриховка крестиком Штриховка косым крестиком |
Ниже приведен текст программы, которая использует основные функции для вывода на экран 10 000 пикселей, поверх них несколько линий разных стилей, после которых, в свою очередь, прорисовывает прямоугольники и эллипсы:
^include <windows.h>
LRESULT CALLBACK GraphDemoWndProc ( HWND, UINT, UINT, LONG );
hit WINAPI WinMain ( HINSTANCE hlnstancc, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )
HWND hWnd ;
WNDCLASS WndClass ;
MSG Msg;
char szClassName[] = "GraphDcmo";
/* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style - CSJHREDRAW | CS_VREDRAW;
WndClass.IpfnWndProc = GraphDemoWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hlnstancc = hlnstance ;
WndClass.hlcon = Loadlcon (NULL.IDIJVPPLTCATION);
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
WndClass.hbrBackground - (HBRUSH) GetStockObject (WHITE_BRUSH);
WndClass.lpszMenuName = NULL;
WndClass.IpszClassName — szClassName;
if ( !RegisterClass(&WndClass))
I (
MessageBox(NULL."Cannot register class","Error",MB_OK); return 0;
hWnd = CreateWindow(szClassName, "Graph Demo",
WS_OVERLAPPEDWINDOW, CW USEDEFAULT. CWJJSEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hlnstance.NULL);
if(!hWnd) >
MessageBoxfNULL,"Cannot create window","Error",MB_OK); return 0;
/* Show our window */
ShowWmdow(hWnd,nCmdSho\v);
UpdateWindovv(hWnd);
/* Beginning of messages cycle */
whilc(GetMcssage(&Msg, NULL, 0, 0)) i
TranslateMessage(&Msg); DispatchMessage(&Msg); i return Msg.vvParam;
LRESULT CALLBACK GraphDemoWndProc (HWND hWnd, UtNT Message,
UINT wParam, LONG IParam )
HDC hDC, hCompatibleDC;
PAINTSTRUCT PaintStruct;
RFXT Reel;
HBITMAP hCompatibleBitmap, hOldBitmap;
HPENhOldPen;
static HPENPens[5];
HBRUSH hOklBnish;
static HBRUSH Brushcs[6];
int i;
swilch(Message)
case WM_PAINT: randomizcf); for(i = 0; i <=• 4; j-r~) {
Pcnsfil = (CreatePen(i, 1, RGB(random(255),random(255), random(255)))); Bnishesfil - (CreateHa(cliBrush(i, RCiB(random(255), random(255),
random(255))));
GetClientRect(h\Vnd. &Recl);
hDC - BeginPaint(hWnd, &PamtSiruct);
hCompatiblcDC - CreatcCompatibleDC(hDC);
GctClientRect(liWnd, &Rect);
hCompatibleBilmap = CreateCompaliblc,Bitmap(liDC, Rcct.right, Reel.bottom);
hOldBitmap - SelectObject(hCompatibleDC, hCompatibleBitmap);
PatBlt(hCompatibleDC, 0. 0, Red.right, Reel.bottom, PATCOPY); // Drawing of pixels
for(i = 0; i <- 9999; i---) SetPixel(hCompatibleDC,random(Rect.right), random(Rect.bottom),
RGB(random(255), random(255), random(255))); // Drawing of lines
for(i - 0; i <=- 9; i+-<-)
hOldPen = SelectObject(hCompatibleDC, Pcns[random(4)]); MoveToEx(hCompatibleDC.random(Rect.right), random(Rcct.bottom),
NULL);
LineTo(hCompatibleDC. nmdom(Rcct.right), random(Rect.bottom)); ScleclObjcct(hCompatibleDC, hOldPen); } // Drawing of rectangles
for(i - 0; i <=- 5; i++) i
hOldBrush - SelcctObject(hConipatibleDC, Bmshes[random(4)l); Rcctanglc(hCompaliblcDC, random(Rect.right).
random( Reel, bottom), random(Rcct.right), random(Rcet.bottom)); Ellipse(hCompatibicDC,random(Rcct.right),
random(Rect.bottom). random(Rect.right), random(Rect. bottom)); SelectObject(hCompatiblcDC, hOldBrush);
I BitBit(hDC, PaintStruct.rcPaint.left, PamtStruct.rcPaint.lop,
PaintStruct.rcPaint.right,
PaintStruct.rcPaint. bottom,
hCompatibleDC,
PaintStruct.rcPaint. left,
PaintStruct.rcPaint.top,
SRCCOPY); for(i = 0; i<=4; i++)
!
DeleteObject(Pens[i]); DeleteObjcct(Brushcs[il);
} SelectObject(hCompatibleDC, hOldBitmap);
DeleteObjcct(hCompatibleBitmap); DelelcDC(hCompatibleDC); EndPaint(h\Vnd, &PaintStruct); return 0;
case WM_DESTROY: PoslQuitMcssagc(O); return 0;
} return DefWindo\vProc(hWnd,Messagc,wParam, IParam);
На рис. 6 показан вид окна, создаваемого программой. Следует учесть, что положение линий, прямоугольников и эллипсов - случайное. При перерисовке их положение, размер и стиль штриховки изменяется, по-
этому при повторном запуске программы в окне может быть другое изображение.
Думаю, что после всего того, что мы обсудили в этом разделе, при разборе программы не встретится трудностей. Предоставляю вам возможность разобрать эту программу самостоятельно. У читателя может возникнуть вопрос, для чего все эти сложности с созданием контекста в памяти, копированием его на действительный контекст и прочее. Цель единственная - показать технику работы с виртуальным окном. Весь вывод осуществляется в виртуальное окно (контекст в памяти), после чего одна из «могучих Bit», BitBltQ выполняет копирование содержимого виртуального окна на действительное окно. Как я уже говорил, обычно вывод в действительное окно (т. е. копирование контекста в памяти на действительный контекст) происходит при обработке WM_PAINT.
В Giaph Demo
Рис. 6. Прорисовка геометрических объектов
ВЗАИМОДЕЙСТВИЕ ПРОГРАММЫ С ПОЛЬЗОВАТЕЛЕМ
НЕМНОГО О РЕСУРСАХ (ПРЕДИСЛОВИЕ К РАЗГОВОРУ)
ЧТО ТАКОЕ РЕСУРСЫ?
Выше упоминалось, что составной частью проекта, работа которого планируется в Windows, является файл определения ресурсов. Возникает вопрос: что же такое ресурсы, когда и в каких целях они используются?
У Windows, как уже говорилось, есть некоторые предопределенные данные (вспомним предопределенные курсоры, иконки и кисти). Точно так же, почти в каждой программе для Windows есть некоторые данные, которые определяются еще до начала работы программы, особым образом добавляются в выполняемый файл и используются при работе программы. Яркими примерами таких данных являются иконки и курсоры мыши. Кроме них, к числу ресурсов относятся: используемые в программе изображения; строки символов;
меню;
ускорители клавиатуры;
диалоговые окна;
шрифты;
ресурсы, определяемые пользователем.
Следует отметить, что выполняемым файлом может быть файл программы .ехе, файл динамической библиотеки .dll и другие бинарные файлы. Для удобства буду их называть bin-файлами.
Помимо того, что ресурсы определяются до начала работы программы и добавляются в bin-файл, у них есть еще одна характерная черта. При загрузке bin-файла в память, РЕСУРСЫ В ПАМЯТЬ НЕ ЗАГРУЖАЮТСЯ. Только в случае, если тот или иной ресурс требуется для работы программы, программа сама загружает ресурс в память.
Возможность использования того или иного атрибута в качестве ресурса не означает, что программист не может создавать эти атрибуты в программе. Яркий пример тому можно найти в работе старого доброго Program Manager'a. При перетаскивании иконки с места на место курсор меняет свою форму и принимает форму, подобную перетаскиваемой иконке. Естественно, что в этом случае курсоры определяются программой. Помимо .этого, вспомним drag-and-drop в Explorer'e и изменение формы курсора при этом.
Еще одним примером являются динамические меню, т. е. меню, которые изменяют свои вид и предоставляемые возможности в зависимости от обстоятельств. Пример динамического меню будет приведен при изучении меню.
РЕСУРСЫ СТАНДАРТНЫЕ И НЕСТАНДАРТНЫЕ
Все ресурсы, заранее определенные в Win32 API, называются стандартными. Для работы с ними существуют специальные функции. Но именно эта стандартность и ограничивает возможности программиста. Стандарт, он и есть стандарт.
Для того чтобы можно было преодолеть эти ограничения, был создан особый тип ресурсов - определяемые пользователем ресурсы. Используя именно этот тип, мы можем предоставить в распоряжение программы практически любые данные. Но, как известно, бесплатным бывает только сыр в мышеловке. В данном случае платой за универсальность является усложнение программы, так как забота о манипулировании данными из ресурсов лежит уже не на системе, а на программе, использующей эти ресурсы. Программа может только получить указатель на данные ресурсов, загруженные в память средствами Windows. Дальнейшая работа с ними ложится ИСКЛЮЧИТЕЛЬНО на плечи программы!
ПОДКЛЮЧЕНИЕ РЕСУРСОВ К ИСПОЛНЯЕМОМУ ФАЙЛУ
Ресурсы создаются отдельно от файлов программы и добавляются в bin-файл при линковании программы. Подавляющее большинство ресурсов содержится в файлах ресурсов, имеющих расширение .RC. Имя файла ресурсов обычно совпадает с именем bin-файла программы. Так, если имя программы MYPROG.EXE, то имя файла ресурсов - MYPROG.RC.
Некоторые типы ресурсов (меню, например) можно описать на специальном языке и воспользоваться при этом обычным текстовым редактором, поддерживающим текст в формате ASCII. Другие ресурсы (иконки, курсоры, изображения) тоже описываются в текстовом виде, но частью их описания является последовательность шестнадцатиричных цифр, описывающих изображения. Можно, конечно, попробовать написать эту последовательность и в текстовом редакторе, но, наверное, в этом случае сложность создания ресурса приблизится к сложности написания программы, а возможно, и превысит ее. Обычно для создания ресурсов пользуются специальными средствами - редакторами ресурсов. Они позволяют создавать ресурсы, визуально контролировать правильность их создания, после чего сохранять их в файлах ресурсов.
Я часто использую «смешанный» способ редактирования ресурсов. Например, при визуальном редактировании диалоговых окон достаточно трудно точно установить элементы диалогового окна именно так, как хочется. Устанавливаю все элементы ПРИБЛИЗИТЕЛЬНО на те места, где они должны находиться, после чего сохраняю ресурсы в виде файла с расширением RC. Затем редактирую RC-файл как обычный текстовый файл, точно указывая при этом все размеры и позиции.
При создании RC-файлов программист может столкнуться с одной тонкостью. Некоторые ресурсы, такие, как иконки, курсоры, диалоговые окна, изображения (bitmap) могут быть сохранены в отдельных файлах с расширениями .ico, .cur, .dig, .bmp соответственно. В этом случае в RC-файлах делаются ссылки на упомянутые файлы.
Файл ресурсов создан - теперь его нужно откомпилировать. Компилируется он специальным компилятором ресурсов. Обычно имя компилятора ресурсов заканчивается на RC.EXE. В частности, в Borland 5.0 он называется BRC.EXE.
После компиляции файла ресурсов компилятором ресурсов создается новый файл, имеющий расширение .RES. Именно этот RES-файл используется линкером для добавления ресурсов в bin-файл. Следует отметить, что при необходимости RES-файлы могут создаваться и редакторами ресурсов. В каком формате создавать ресурсы и как присоединять их к исполняемому файлу, зависит от потребностей и привычек создающего ресурсы программиста.
Итак, в очередной раз постараемся подвести итог сказанному. Ресурсы создаются и включаются в bin-файл посредством выполнения следующих шагов (некоторые шаги могут быть опущены в зависимости от обстоятельств) (табл. 13).
Те программисты, которые при работе пользуются интегрированной средой, получают в некотором смысле преимущество. Во-первых, все эти шаги можно осуществить без выхода из интегрированной среды. Во-вторых, компиляция RC-файла и линкование полученного RES-файла можно выполнить автоматически.
После выполнения этих шагов в нашем bin-файле содержатся все необходимые данные нам данные, которые можно использовать (добавлять меню к окну, загружать курсоры, иконки, работать с диалоговыми окнами). Но все это - только описание порядка работы. В следующих разделах мы попробуем создать некоторые ресурсы. Не буду описывать работу с редакторами ресурсов. Во-первых, работа с ними достаточно проста, а во-вторых, описана в технических руководствах. Постараюсь описать синтаксис языка, который используется для создания сценариев (скриптов) ресурсов, после чего продемонстрировать, как работу с ресурсами можно
заменить и/ или дополнить вызовами функций Win32 API. Рассмотрение мы построим следующим образом. Для каждого типа ресурсов сначала рассмотрим способ создания этого ресурса, подключения его к окну, а затем рассмотрим функции Win32, которые предназначены для работы с ресурсами.
В этом разделе мы будем рассматривать только те ресурсы, которые обеспечивают непосредственный диалог пользователя с программой. К их числу, прежде всего, относятся меню. Во-первых, именно с меню начинается знакомство с программой. Во-вторых, оценить функциональные возможности программы, можно просто взглянув на меню. То есть именно меню в большинстве случаев является визитной карточкой программы.
Кроме меню, наиболее часто для взаимодействия с пользователем используются диалоговые окна. Они, как правило, применяются для ввода данных и информирования программы о принятых решениях. При их рассмотрении, нам придется изучить элементы управления (controls) и общие элементы управления (common controls), научиться взаимодействовать с ними, устанавливать и считывать их состояние. Обращаю внимание читателя на следующее. Понимание работы меню и диалоговых окон очень важно. Зная принципы их работы, становится возможным написание программ для Windows, несущих какую-то полезную нагрузку.
Таблица 13. Последовательность сомания файла ресурсов
Действие
Используемое средство
Создание RC-файла (при необходимости включающего ссылки па файлы с расширением .ico, .cur, .bmp, .dig, .innu и т. д.) Редактирование RC-файла в текстовом виде Компиляция RC-файла - получение RES-файла
Добавление ресурсов, содержащихся в RIZS-файле, в bin-файл
Редактор ресурсов (при необходимости может быть использован текстовый редактор и графические редакторы) Текстовый редактор Компилятор ресурсов
Линкер