ListBox Example
_%«! • families1
Berdiev Ivanov Jackson
Johnson
Mamedov Novozhenov
[Message-about КЬаЪ1Ь|Ш1Ш№Ш;Ь:-
Рис. IO. Диалоговое окно, содержащее окно списка
Следующим шагом в изучении органов управления, применяющихся в диалоговых окнах, является изучение достаточно интересного элемента, который называется
ОКНО РЕДАКТИРОВАНИЯ
Окно редактирования - это один из наиболее сложных (с точки зрения реализации, а не использования) и наиболее интересных элементов управления. Фактически этот элемент представляет собой небольшой текстовый редактор, который позволяет вводить текст, редактировать его, копировать в буфер, вставлять из буфера и т. д. Окна редактирования могу быть однострочными и многострочными. Однострочные окна редактирования обычно используются для ввода небольших элементов текста. Например, в Program Manager'e для запуска программы пользователю необходимо ввести имя и командную строку этой программы. Примером редактора, большую часть функциональности которого обеспечивается за счет многострочного окна редактирования, является Notepad, который поставляется со всеми версиями Windows.
Окно редактирования можно создать как в файле ресурсов, так и как отдельное дочернее окно, указав при этом предопределенный класс «edit». В этом разделе мы разберем оба случая использования окна редактирования .
Перед тем как начать изучение, давайте вспомним, что все поведение элемента управления зависит от того, какие стили мы укажем при его создании. Все стили окна упомянуты в winuser.h. Все они начинаются с букв ES_. Эти стили приведены в табл. 31.
Таблица 31. Стили окна редактирования
Стиль |
Значение |
Описание |
esj.eft |
OxOOOOL |
Текст в окне редактирования выравнивается по |
|
|
левому краю |
F.SJTENTFR |
0x000 IL |
Текст в окис редактирования выравнивается по |
|
|
правому краю |
HS RIGHT |
0x00021. |
Текст в окне редактирования выравнивается по центру |
ES MULTILINE |
Ox()004L |
Создается многострочнос окно редактирования |
es uppercase |
0x00081. |
Вводимый текст преобразуется в прописные буквы |
F.S LOWERCASF. |
0x00 101. |
Вводимы:'] текст преобразуется в строчные буквы |
FS PASSWORD |
0x0020!. |
Все вводимые символы отображаются в виде звез- |
|
|
дочек |
130
Окончание табл. 31
(л и .'1 ь |
Значение |
Описание |
ES_AUTOVSCROLL |
Ox0040L |
При необходимости текст в многострочном окне |
ES_AUTOHSCROLL |
OxOOSOL |
редактирования скроллируется по вертикали При необходимости текст в окне редактирования |
ES_NOHIDFSF.L |
0x0 100 1. |
скроллируется по горизонтали При потере окном редактирования фокуса ввода |
|
|
выделение с текста не снимается |
ES_OEMCONVERT |
0x04001. |
Вводимые символы из одного набора преобразуются |
ES_READONEY |
OxOSOOL |
в символы из друг ого наоора Текст в окне редактирования можно только просмат- |
ESJWANTRETURN |
Ox 1 OOOL |
ривать, но не редактировать При нажатии клавиши Enter в многострочном окне |
ESJNUMBER |
0x20001, |
система вставляет в текст символ возврата каретки Разрешается осуществлять ввод только цифр |
#incl''dc <windo\vs.h>
#definc ID Edit 101
HINSTANCE Must;
ERESUET CALLBACK EditDemoWndProc ( HWND, UINT, U1NT, EONG );
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance,
LPSTR IpszCmdParam, int nCmdShow )
i i
HWND hWnd ;
WNDCLASS WndClass ;
MSG Msi-
char szClassName[] - "EditDemo";
hlnst — hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.slyle - CSJIREDRAW | CS VREDRAW;
WndClass.lpfnWndProc = EditDcmoWndProc;
WndClass.cbClsExtra - 0;
WndClass.cbWndExtru ---- 0;
WndClass.hlnstance = hlnstance ;
WndClass.hlcon ----- Loacllcon (NULEJDI APPLICATION),
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.librBackground - (HBRUSH) GctStockObject (WHITE_BRliSH); WndClass.lpszMcnuNamc - NULL; WndClass.lpszClassNamc ~ sxClassName;
if ( !RegisterClass(&WndClass) )
f
MessageBox(NULL,"Cannot register class", "Error", MB_OK); return 0;
} liWud = C'reateWmdow(szClassName, "EditDemo",
WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT.
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL,
hlnstance.NULL); if(!hWnc!)
t MessageBo,x(NULL,"Cannot create window ', "Error", MBJDK);
return 0;
/* Show our window */
ShowWindow(hWnd,nCmdSliow);
UpdateWindow(hWnd); /* Beginning of messages cycle */
while(GetMessage(&Msg, NULL, 0, 0))
!
TranslateMessage(&Msg);
DispatchMessagef&Msg); } return Msg.wParam;
LRESULT CALLBACK EditDemoWndProc (HWND hWnd, UINT Message,
UFNT wParam, LONG IParam )
'static HWND hEditWnd; RECT Rcct;
switch(Message)
!
case WMJTREATE:
GetClientRect(hWnd, &Rect);
hEditWnd = CreatcWindow("edit", NULL,
WS_CHILD | WSJVISIBLE | WS HSCROLL! WS VSCROLL| WS BORDER | ES_LEFT | ES MULTILLNE [ ES_AUTOHSCROLL | ES^AUTOVSCROLL, 0, 0, 0, 0,
132
hWnd,
(HMENU) ID_Edit, hlnst, NULL); return 0; case WM_SIZE: MoveWindow(liEditWnd, 0, 0, LOWORD(lParam), HIWORD(IParam),
TRUE); return 0;
case WM_SETFOCUS: SetFocus(hEditWnd); return 0;
case WM_DESTROY: PostQuitMessage(O); return 0; } return DefWindowProc(hWnd,Message,wParam, IParam);
Вид окна, создаваемого программой, показан на рис. 11.
Эта программа создает окно редактирования, которое располагается поверх основного окна программы. Фактически в программе создается текстовый редактор, позволяющий осуществлять набор и редактирование текста, выделять части текста. Выделенные части текста могут быть перемещены в Clipboard посредством нажатия клавиш Shift-Delete, а после нажатия клавиш Shift-Insert текст из Clipboard'a может быть вставлен в окно.
В EditDemo
Это окно, представляющее собой простейший текстовый редактор, создано на основе многострочного окна редактирс ния.
.Этот текст выдепен и подготовлен для перемещения
ib 6i
Рис. 11. Окно редактирования с невыделенным и выделенным текстом
Управление окном редактирования, как и всех остальных элемен тов управления, осуществляется посредством посылки окну сообщений.
Перед тем, как начать рассмотрение сообщений необходимо отметить, что для нормальной работы часть текста в окне редактирования должна быть выделена. Операции производятся именно с выделенной частью текста. Копирование текста в окно редактирования осуществляется от места размещения текстового курсора.
Следующие пять сообщений не имеют параметров, а их действия очевидны из их названия:
WM_COPY - выделенная часть текста копируется в Clipboard;
WM_PASTE - содержимое Clipboard'a копируется в окно редактирования (данные вставляются только в том случае, если в Clipboard'e находится текст);
WM_CUT - выделенная часть текста удаляется из окна редактирования и помещается в Clipboard;
WM_CLEAR - выделенная часть текста удаляется из окна редактирования и не помещается в Clipboard;
WM_UNDO - отменяется последняя операция.
Для того чтобы получить границы выделения текста, необходимо использовать сообщение EM_GETSEL. Младшее слово возвращаемого значения содержит начальную, а старшее слово - конечную позицию выделения плюс 1. Другими словами,
DWORD dwPosition = SendMessage(hEditWnd, EMJ3ETSEL,
(WPARAM) 0, (LPARAM) 0); WORD wBcginPosition = LOWORD(dwPosition); WORD wEndPosition = HIWORD(dwPosition) - 1;
Если программе необходимо выделить часть текста, то она может использовать сообщение EM_SETCEL, в IParam которого необходимо указать начальную и конечную позиции выделения:
SendMessagefhEditWnd, EM^SETSEL, 0, MAKELONG(wBeginPosition, wEndPosition));
С помощью сообщения EM_REPLACESEL программа может заменить выделенный текст на другой:
SendMcssage(hEditWnd, EM_REPLACESEL, О, (LPARAM) pszNcwTcxt);
И наконец, посылка сообщения, с помощью которого приложение может скопировать в собственный буфер набранный пользователем текст, выглядит следующим образом:
SendMessagefhEditWnd, EM_GETLINE, (WPARAM) nLine, (LPARAM) (LPCSTR) pBufler);
В этом сообщении в качестве wParam указывается номер строки ( в случае однострочного окна номер строки игнорируется), а в качестве IParam - указатель на буфер, в который будет записана строка из окна редактирования.
На этом завершается рассмотрение стандартных элементов управления.
Должен заметить, что в Win32 включены новые, так называемые общие элементы управления (common controls). Многие из них уже были реализованы в приложениях, работающих в среде Windows 3.x, но до их документирования в Windows 3.x дело не дошло. Встречались попытки описать их реализацию в частности, в MSDN были описаны строка состояния, панель инструментов (toolbar), окно просмотра деревьев. Поэтому, наверное, можно сказать, что появление общих элементов управления ожидалось. И теперь мы приступаем к их изучению.
ОБЩИЕ ЭЛЕМЕНТЫ УПРАВЛЕНИЯ
Перед тем, как мы начнем изучение работы непосредственно элементов управления, мы должны научиться подключать библиотеку, реализующую эти элементы. Для нормальной работы программы с общими элементами управления необходимо выполнить два шага. Первым шагом является подключение к программе файла заголовков commctrl.h. Вторым шагом является подключение при линковашш непосредственно библиотеки. Она называется comctl32.dll.
Перед использованием любого из общих элементов управления необходимо загрузить эту библиотеку. Это делается с помощью функции InitCommonControls(), которая описана в файле commctrl.h как
void InitCommonContorls(void);
Эта функция не только загружает библиотеку общих элементов управления, но и инициализирует их подсистему. Обычно эта функция вызывается перед первым использованием одного из элементов.
Характерно, что все общие элементы управления являются дочерними окнами, т. е. они не могут создаваться и использоваться в качестве главного окна программы. Управление ими, как и обычными элементами управления, осуществляется посредством посылки им сообщений. Элементы управления информируют родительское окно о событиях, произошедших с ними, посредством передачи нотификационных сообщений.
Первым элементом управления, работу с которым мы рассмотрим, будет строка состояния (status bar). Ранее мы использовали ее в наших демонстрационных программах, но работу с ней не изучали.
РАБОТА СО СТРОКОЙ СОСТОЯНИЯ
Для того чтобы отобразить информацию о текущем состоянии программы, выполняемых операциях и режимах, в программе может использоваться элемент управления, который называют окном или линейкой состояния (status bar). Мне кажется более удачным термин «строка состояния». Включение окна состояния в программу в значительной степени изменяет внешний вид окна и позволяет создать более понятный и удобный для пользователя интерфейс.
Строка состояния может быть включена в описание диалогового окна в файле ресурсов. Но так как окно состояния является стандартным окном, то, естественно, что для его создания могут быть использованы функции и стандартные функции CreateWindowQ и CreateWindowExQ. При этом в качестве имени класса окна необходимо задать макро «STATUSCLASSNAME». В зависимости от того, какую систему кодировки использует прснрамма, можно воспользоваться также и «истинным» именем класса (msctls_statusbar32). В commctrl.h эти макро и имена описаны следующим образом:
#ifdcf_WIN32
«define STATUSCLASSNAMEW
«define STATUSCLASSNAMEA
«ifdefUNICODE
«define STATUSCLASSNAME
«else
«define STATUSCLASSNAME
#cndif
#else
«define STATUSCLASSNAME
«endif
L"msctls_statusbar32" "msctls statusbar32"
STATUSCLASSNAMEW STATUSCLASSNAMEA
"msctls statusbar"
136
Тем не менее, для создания окна состояния предусмотрена и отдельная функция CreateStatusWindowQ. В файле commctrl.h эта функция определяется так:
WINCOMMCTRLAPI HWND WINAPI CreateStatusWindowA(LONG style,
LPCSTR IpszText, HWND hwndParent, UINT wID);
WINCOMMCTRLAPI HWND WINAPI CreatcStatusWindowW(LONG style,
LPCWSTR IpszText, HWND hwndParent, UINT wID);
#ifdcf UNICODE
«define CreateStatusWindow
«else
«define CreateStatusWindow
«cndif
CrcateStatusWindowW
CreateStatusWindowA
Как можно узнать из описания функции, она требует передачи ей четырех аргументов. Первый аргумент, style, должен определять стиль создаваемого окна. У строки состояния есть единственный собственный стиль, SBARS_SIZEGRIP, который позволяет в правый угол строки состояния добавить «ручку» (внешне, честно говоря, на ручку это совсем не похоже) для изменения ее размеров. Но наличие у строки состояния единственного стиля не мешает комбинировать его со стандартными стилями окон, например с WS^CHILD и WSJVISIBLE.
Второй аргумент - IpszText - является указателем на строку, которая будет отображена в строке состояния сразу после ее создания. Ничего особенного здесь нет.
Третий аргумент - hwndParent - тоже не требует особых объяснений. Он является хэндлом родительского окна строки состояния.
Наконец, четвертый аргумент - uID - идентификатор окна состояния.
Попробуйте вызвать эту функцию (не забудьте про InitCommonControls()!) - и вы увидите, что в окне появилась строка состояния с текстом, определенным во втором аргументе этой функции.
После того, как строка состояния прорисована, иногда бывает необходимо разделить ее на несколько панелей для того, чтобы в каждой панели отображать информацию, логически не связанную с отображаемой в других панелях. Для того чтобы сделать это, необходимо послать строке состояния сообщение SB_SETPARTS. При этом wParam этого сообщения должен определять число панелей, a IParam должен содержать указатель
на массив целых чисел (число элементов массива должно быть равно wParam). Каждый элемент в этом массиве должен определять позицию (в координатах родительского окна) правой границы соответствующей части. Если элемент равен -I, то границей панели считается правая граница строки состояния. В случае успешного завершения операции функция, с помощью которой послано сообщение, возвращает TRUE. Значение FALSE должно заставить программиста поискать ошибку в собственной программе.
Приложение может определить число панелей, на которые разделена строка состояния, и их координаты с помощью сообщения SB_GETPARTS. Если IParam этого сообщения равен нулю, то функция, пославшая сообщение, возвращает число панелей строки состояния. При этом значение wParam роли не играет. Для того чтобы получить координаты панелей, wParam должен определять число панелей, для которых нужно получить координаты, a IParam должен указывать на массив целых чисел, в который будут записаны эти координаты. В этом случае функция также возвращает число панелей. Если при обработке сообщения произошла какая-то ошибка, то функция возвращает нулевое значение.
Итак, строка состояния сформирована. Но зачем она нужна без отображенной информации? Для того чтобы отобразить определенный текст в строке состояния, нужно послать ей сообщение SB SETTEXT. В качестве wParam этого сообщения используется результат логического сложения двух величин. Первая (iPart) - номер (считая от нуля) панели, в которой необходимо отобразить текст. Вторая (иТуре) определяет, как будет выглядеть текст. В качестве иТуре могут быть использованы значения, приведенные в табл. 32.
Таблица 32. Возможные типы строки состояния
Тип |
Значение |
Описание |
|
|
0x0000 |
Текст кажется вдавленным в панель |
|
SBT_NOBORDERS |
0x0 I 00 |
Панель прорисовывается без ограничительны |
\ |
|
|
линии |
|
SBT POPOUT SBTJITLREADING |
0x0200 0x0400 |
Панель прорисовывается выпуклой Используется для языков, в которых чтение и |
дет |
SnT_OWNERDRAW |
Ох 1 000 |
справа налево, как, например, в арабском За прорисовку панели отвечает родительское |
окно |
А теперь, для того, чтобы прочитать текст в панели, необходимо строке состояния послать сообщение WM_GETTEXT. wParam этого сообщения должен содержать номер панели, a IParam - указатель на строку, в которую будет записан текст, содержащийся в панели.
Это основные сообщения, используемые при работе со строкой состояния.
Для иллюстрации некоторых возможностей строки состояния, о которых я только что рассказал, рассмотрим несколько измененную программу из раздела о кнопках. Из-за мелких отличий не буду приводить текст программы целиком, а приведу только текст диалоговой функции:
BOOL CALLBACK ButtonsExampleDialogProc(HWND liDIg,
UiNT Message, WPARAM wParam, LPARAM IParam)
int i;
char cMyMessage[80];
RECT Rect;
int nBorders[3];
switch(Message)
case WMJNITDIALOG: // Set states of controls
SendDIgItemMessage(hDlg, nRadioButtonld, BM^SETCHECK,
BST_CHECKED, 0);
for(i - IDC_CHECKBOXI; i <= IDC_CHECKBOX3; i++) if(uCheckBoxesState[i - 208])
SendDlgItemMessage(hDlg, i, BM_SETCHECK, BST_CHECKED, 0); GetClientRect(hDIg, &Rect); nBorders[0] = Rect.right / 3; nBorders[l]-Reel.right/3 * 2; nBorders[2] = -1; SendDlgItemMessage(hDlg, IDC_STATUSBAR, SB_SETPARTS, 3,
(LPARAM) nBorders); return TRUE; case WM__COMMAND: switch(LOWORD(wParam))
case IDCJIADIOBUTTONI: case IDC_RADIOBUTTON2: case IDC_RADIOBUTTON3:
sprintf(cMyMessage,"RadioButton%d", LOWORD(wParam) - 203);
SendDlgItemMessage(hDlg,IDC_STATUSBAR,SB_SETTEXT, (WPARAM) 0, (LPARAM) cMyMessage);
CheckMenuRadioItem(GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), 0), IDM_RadioButtonl,IDM_RadioButton3, LOWORD(wParam) - 102, MF_BYCOMMAND); return FALSE; caseIDC_CHECKBOXl: case IDC_CHECKBOX2: case IDC_CHECKBOX3:
sprintf(cMyMessage,"CheckBox%d", LOWORD(wParam) - 207); SendDlgItemMessage(hDlg,IDC_STATUSBAR,SB_SETTEXT,
(WPARAM) 1, (LPARAM) cMyMcssage); i = LOWORD(wParam) - 208; uCheckBoxesStatc[i] = uChcckBoxesStale[i] == MF_CHECKED ?
MFJJNCHECKED : MF_CHECKED;
CheckMenuttem(GetSubMenu(GetSubMenu(GetMenu(hWnd), I), 1), LOWORD(wParam) - 103, uCheckBoxesStatc[i]); return FALSE; caseIDC_BUTTONl:
SendDlgItemMessage(hDlg, IDC_STATUSBAR, SB_SETTEXT, (WPARAM) 2, (LPARAM) "PushButton"); return TRUE; case IDC_BUTTON2; // Save the state of RadioButtons
i = IDC_RADIOBUTTON 1; while(!SendDlgItemMessage(hDlg, i, BM_GETCHECK, 0, 0))
i++;
nRadioButtonld = i; // Save the state of CheckButtons
for(i = IDC_CHECKBOXl; i <= IDC_CHECKBOX3; i++) uCheckBoxesState[i - 208] = SendDlgItemMessage(hDlg, i, BM_GETCHECK, 0, 0) == 0 ? MFJJNCHECKED : MF_CHECKED; EndDialog(hDlg,0); return TRUE; } break;
} return FALSE;
Отличия, внесенные в эту функцию, весьма невелики. Во-первых, строка состояния делится на три панели, и, во-вторых, каждый кластер объектов управления отображает сообщения в своей панели. Как видите, уважаемый читатель, ничего сложного здесь нет. Вид диалогового окна, создаваемого программой, показан на рис. 12.
Сравните внешний вид строки состояния, приведенной на рисунке, и строки состояния из раздела о кнопках.
Buttons Example
-^^fffpel-r
.. ...„. ...
Q-"R"a'df6eutton2®,
'
IBadioButtpnS.
Рис. 12. Диалоговое окно со строкой состояния
РАБОТА СО СПИНОМ
Иногда в приложениях встречаются ситуации, в которых полосы прокрутки (скроллирования) не нужны, достаточно только кнопок «вверх» и «вниз». Ярким примером такой ситуации может служить окно, открываемое в WinWord'e для Windows'95 при необходимости определить параметры страницы. Естественно, полоса прокрутки для того, чтобы чуть увеличить значение размера бумаги или сделать поля поменьше, не нужна. Для подобных случаев в Win32 предусмотрен новый элемент управления, называемый спином. Спин является особым видом линейки прокрутки и состоит только из кнопок со стрелками, которые находятся на концах линейки, и не включает линейки прокрутки. Обычно спин используется в одном из двух вариантов. Во-первых, он может применяться а 1а маленькая линейка прокрутки. В этом случае его называют up-down control'oM. Во-вторых, часто он используется в сочетании с другим элементом управления, называемым в этом случае buddy window (приятельским окном). Как правило, этим приятельским окном оказывается окно редактирования. Кстати, в приведенном ниже примере именно окна редактирования и оказываются приятельскими окнами. В этом случае элемент управления называется spin'oM. В данном разделе под словом «спин» будем понимать как up-down control, так и собственно спин.
Спин может создаваться и в составе диалогового окна в файле ресурсов, и как обычное окно посредством использования функций или _..„.. .....__.,_..,ч. При этом в качестве имени класса
. Соответствующее описание
шдом()
[ЧеобходймЬ указывать можно встретить в
сошшсгц-и:
#ifdef JWIN32
#defme UPDOWN_CLASSA
#define UPDOWN_CLASSW
#ifdefUNICODE
#dcfine UPDOWN_CLASS
#elsc
«define UPDOWN_CLASS
#endif
#else
#define UPDOWN_CLASS
#endif
"msctls_updown32" L"msctls_updown32"
UPDOWN_CLASSW UPDOWN_CLASSA
"msctls_updown"
Тем не менее, для создания спина создана специальная функция CreateUpDownControlQ, описание которого мы можем найти в файле commctrl.h:
WINCOMMCTRLAPI HWND WINAPI CreateUpDownControl(
DWORD dwSlylc, int x, int у, int ex, int cy, HWND hParent. int nID, HINSTANCE hlnsl, HWND hBuddy, int nUpper, int nLower, int nPos);
Таблица 33. Стили спина
Стиль
Значение
Описание
UDS WRAP
UDS_SETBUDDYINT UUS_ALIGNRIGHT
UDS_ALIGNLEFT UDS_AUTOBUDDY
UDS_ARROWKEYS
UDDS_MORZ
UDS NOTHOUSANDS
0x0001
0x0002 0x0004
0x0008 0x0010
0x0020
0x0040 0x0080
При достижении максимальной позиции отсчет начинается вновь с минимальной позиции и наоборот.
У спина есть приятельское окно Спин размещается справа от приятельского окна
Спин размещается слева от приятельского окна При изменении позиции спина текст в приятельском окне меняется автоматически Разрешается использование клавиатур1»1 для изменения текущего состояния спина Спин располагается горизонтально Не отображать запятую для разделения классов в числах, отображаемых в приятельском окне
142
Таблица 34. Сообщения, посылаемые спину
Сообщение
Значение
Описание
UDM_SETRANGE UDM_GETRANGE
UDM_SETPOS UDM_GETPOS
UDM_SETRUDDY UDM GETBUDDY
WMJJSER-HOI WMJJSER+102
WMJJSER+103 WMJJSER+I04
WM_USER+l05 WM USER+I06
Установка диапазона прокрутки, wParam = О, min значение - в старшем слове IParam, max значение - в младшем слове IParam Получение диапазона прокрутки, wParam и IParam должны быть равны 0, возвращает max значение в младшем, a min значение - в старшем слове возвращаемого значения Установка текущей позиции спина, wParam~0, IParam - значение новой позиции Получение текущей позиции спина, wParam и IParam должны быть равны 0, возвращает текущую позицию в младшем слове во (вращаемого значения
Определение приятельского окна, wParam хэндлу приятельского окна, IParam - О, возвращает хэндл бывшего приятельского окна
Получение хэндла приятельского окна, wParam и IParam должны быть равны О, возвращает хзндл приятельского окна в младшем слове возвращаемого значения
Параметр dwStyle определяет стиль окна. Все стили, разработанные специально для спина, начинаются с UDS_. Они приведены в табл. 33.
Как и всегда, для управления спином используются сообщения. Обычно при нажатии одной из стрелок спина родительскому окну посылается сообщение WM_VSCROLL. При этом IParam этого сообщения содержит хэндл спина. При обработке этого сообщения необходимо быть аккуратным и ire забывать, что сообщения WM VSCROLL могут поступать и от других элементов управления.
Остальные сообщения, используемые спином, приведены в табл. 34.
Для того чтобы проиллюстрировать возможности спина, рассмотрим небольшую демонстрационную программу. Для работы этой программы вам потребуется файл описаний, приведенный ниже:
#defmeIDM_F.xit l()l
#deime IDM_Pialog 102
#defineIDM About 103
^define ID OK 104
#define IDJIdit 105
fdefine ID Spin 106
Кроме этого, ниже приведен файл ресурсов, используемый
#include "spin.h"
SpinDemoMenu MENU
! POPUP "&File"
I
MENUITEM "E&xit", IDM Exit }
MENUITEM "&Dialog", IDM_Dialog
POPUP "&HcIp"
{ MENUITEM "&About", IDM_About
SpinDcmoDialog DIALOG 0, 0. 100, 100
STYLE DS_MODALFRAME | DS_3DLOOK | \VS_POPUP | WS_VISIBLE |
WS_CAPTION | WS_SYSMENU CAPTION "Spin Demo Dialog" FONT 8, "MS Sans Serif f
DEFPUSHBUTTON "OK". 1D_OK. 25, 60, 50, 14
CONTROL "", ID_Edit, "edit", ES LEFT | WSJTHILD j WS_VISIBLE |
WSJJORDER ! \VS_TABSTOP | ES_NUMBER, 25, 20, 50, 12
И наконец, непосредственно текст программы:
^include <windows.li> #includc <commctrl.h> ^include "spin.h"
HINSTANCE hlnst;
LRESULT CALLBACK SpinDemoWndProcfHWND, UINT, UINT, LONG ); BOOL CALLBACK SpinDcmoDialogProc(HWND. UINT, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE hinstancc, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )
>
HWND liWnd;
WNDCLASS WndClass ;
MSG Msg;
char szClassNamef] = "SpinDcmo";
hlnst = hlnstance;
144
/* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.stylc = CS_HREDRAW j CS_VREDRAW;
WndClass. IpfnWndProc = SpinDemoWndProc;
WndCiass.cbClsExtra = 0;
WndClass. cbWndExtra = 0;
WndCIass.hlnstance = hlnstance ;
WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);
WndClass.hCursor = LoadCursor (NULL, IDC^ARROW);
WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
WndClass. IpszMenuName = "SpinDemoMenu";
WndClass. IpszClassName = szCIassName;
if ( !RegisterClass(&WndClass) ) I
MessageBox(NULL,"Cannot register class", "Error", MB_OK); return 0;
hWnd = CreateWindow(szClassName, "Spin Demo",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, NULL, NULL, h!nstance,NULL); if(!hWnd) {
MessageBox(NULL,"Cannot create window", "Error",MB_OK); return 0;
InitCommonControlsO; /* Show our window */ ShowWindow(hWnd,nCmaShow); UpdateWindow(hWnd);
/* Beginning of messages cycle */
whilc(GetMessage(&Msg, NULL, 0, 0)) {
TranslateMcssage(&Msg); DispatchMcssage(&Msg); } return Msg.wParam;
LRESULT CALLBACK SpinDemoWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam ) I switch(Messagc)
145
case \VM_COMMAND: switch(wParam)
{ case IDM_Exil:
SendMessage(hWnd, WM_CLOSE, 0, 0);
break; case IDM_Dialog:
DialogBox(hInst, "SpinDemoDialog", hWnd, SpinDcmoDialogProc);
break;
i /
return 0;
case WM_DESTROY; PostQuitMessage(O); return 0;
I return DefWindowProc(hWnd,Message,wParam, IParam);
BOOL CALLBACK. SpinDemoDialogProc(HWND hDlg, UINT Message,
WPARAM wPararn, LPARAM IParam)
!
static HWND hEditWnd; static HWND hSpinWnd; switch(Message)
{
case WMJNITDIALOG: hEditWnd = GetDlgItem(hDlg,ID_Edit);
hSpinWnd = CreatcUpDownControl(WS_CHILD | WS_BORDER |
WS_VISIBLE ] UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 0, 12,50,50, hDlg, ID_Spin, hlnst,
hEditWnd, 100,0,50);
return TRUE; case WM_COMMAND: switch(LOWORD(wParam))
{
case ID_OK: EndDialog(hDlg,0); return TRUE;
} break;
!
return FALSE;
Spin Demo Dialog
;.]50
Рис. 13. Диалоговое окно со спином
При выборе элемента «Dialog» из меню протраммы производится ото бражение диалогового окна, вид которого показан на рис. 13.
Действия, приводящие к отображению спина, локализованы в той части программы, которая обрабатывает сообщение WM_INITDIALOG. Спин создается посредством вызова функции CreateUpDownControl(), при этом в качестве приятельского окна указывается окно редактирования, созданное как часть ресурса с описанием диалогового окна. Все остальные действия производятся автоматически. Читатель может попробовать изменить стили спина и посмотреть, к чему это приведет.
Т а б ,i и ц а 35. Стили трекбара
Слип, |
Значение |
Описание |
TBSJTORZ |
0x0000 |
Определяет горизонтальную ориентацию |
|
|
т| скбара |
TBSJiOTTOM |
0x0000 |
Шкала расположена пол ползунком (для |
|
|
горизонтального трекбара) |
TBS_RIGHT |
0x0000 |
Шкала расположена справа от трекбара (для |
|
|
вертикального трекбара) |
TBS AUTOTICKS |
0x000 1 |
Шкала трекбара создается с делениями |
TBS VERT |
0x0002 |
Определяет вертикальную ориентацию |
|
|
трекбара |
TBS_TOP |
0x0004 |
Шкала расположена над ползунком (для |
|
|
горизонтального трекбара) |
TBS_LEFT |
0x0004 |
Шкала расположена слева от трекбара (для |
|
|
вертикального трекбара) |
TBS BOTH |
0x0008 |
Шкала расположена с двух сторон трекбара |
TBS NOTICKS |
0x0010 |
Шкала трекбара создается без делений |
TBS ENABLESELRANGE |
0x0020 |
Разрешается отображение диапазона |
TBS_FIXEDLENGTH |
0x0040 |
При изменении диапазона длина трекбара не |
|
|
изменяется |
TBSJMOTHUMB |
0x0080 |
У трекбара нет слайдера |
147
РАБОТА С ТРЕКБАРОМ
Очередным клоном линейки прокрутки является ползунок (trackbar или slider). Его внешний вид достаточно эффектен и интересен. Он напоминает регулятор, используемый в аппаратуре, скажем, в качестве регулятора громкости. Небольшим отличием трекбара от линейки прокрутки является то, что у ползунка есть шкала, вдоль которой он движется. Честно говоря, мне очень не нравится переводить на русский слова, к которым я уже привык и которые обычно используются в качестве программистского сленга. Поэтому давайте будем в данном случае под словом «трекбар» понимать весь элемент управления, а под словом «слайдер» - указатель, движущийся вдоль шкалы.
К сожалению, для создания трекбара не предусмотрено специальной функции, поэтому создавать его необходимо посредством вызова функции CreateWindowQ или CreateWindowExQ. При этом в качестве имени класса следует указать макрос TRACKBARjCLASS, который описан в commctrl.h:
#ifdef JATN32
«define TRACKBAR_CLASSA
#deime TRACKBAR_CLASSW
#ifdcf UNICODE
#define TRACKBAR_CLASS
tfdefine TRACKBAR_CLASS
#endif
#else
#defme TRACKBAR_CLASS
#cndif
"msctls_trackbar32" L"msclls trackbar32"
TRACKBAR_CLASSW TRACKBAR_CLASSA
"msctls trackbar"
При создании трекбара могут использоваться стили окна, идентификаторы которых начинаются с TBS (табл. 35).
Что еще можно сказать об этих стилях? По-моему, здесь все ясно. Даже понятно, что стили TBS_HORZ, IBS BOTTOM и TBS RIGHT являются стилями, принимаемыми по умолчанию.
А теперь настало время рассмотреть сообщения, посредством которых осуществляется управление трекбаром. Все эти сообщения приведены в табл. 36.
Таблица 36. Сообщения, посылаемые трекбару
Сообщение
Значение
Описание
TBM_GETPOS TBM^GETRANGEMIN TBM_GETRANGEMAX TBMJ3ETTIC
TBM_SETTIC TBM_SETPOS
TBM_SETRANGE
TBM_SETRANGEMIN TBM_SETRANGEMAX
TBM^CLEARTICS TBM_SETSEL
'BM_SETSELSTART "BM_SETSELEND BMJ3ETPTICS BM_GETTICPOS
WMJJSER WM_USER + I WMJJSER + 2 WM_USER + 3
WMJJSER + 4 WMJJSER + 5
WMJJSER + 6
WMJJSER + 7 WMJJSER + 8
WMJJSER + 9 WMJJSER + 10
WMJJSER + 11 WMJJSER+12 WMJJSER+14 WM USER+ 15
wParam и IParam = 0, возвращает текущую позицию слайдера
wParam и IParam = 0, возвращает нижнюю границу диапазона прокрутки wParam и IParam = 0, возвращает верхнюю границу диапазона прокрутки wParam = номер (от нуля) пометки, IParam = 0, возвращает позицию в диапазоне, соответствующую указанной пометке
wParam = 0, IParam = позиции, устанавливает пометку в указанной позиции wParam = TRUE, IParam = новой позиции слайдера, слайдер устанавливается в новую позицию
wParam = флаг перерисовки (BOOL), IParam = MAKELONG(wMinimum, wMaximum), устанавливает диапазон для слайдера трекбара
wParam — флаг перерисовки (BOOL), IParam = wMinimum, установка нижней
•раницы диапазона для слайдера wParam = флаг перерисовки (BOOL), IParam = wMaximum, установка верхней границы диапазона для слайдера wParam = флаг перерисовки (BOOL), IParam = 0, удаляет текущую пометку wParam = флаг перерисовки (BOOL), IParam = MAKELONG(wMinimum, «Maximum, установка диапазона выделения в трекбаре wParam = флаг перерисовки (BOOL), IParam = wStart, установка нижней границы выделения wParam = флаг перерисовки (BOOL), IParam = wEnd, установка верхней границы выделения wParam и IParam = 0, возвращает указа-
-ель на массив, содержащий позиции юметок
wParam = номер (от нуля) пометки, ^aram = 0, возвращает позицию пометки оконных координатах
149
Окончание табл. 36
Сообщение
Значение
Описание
TBM_GETNUMTICS TBM_GETSELSTART TBM_GETSELEND TBM_CLEARSEL
TBM_SETTICFREQ TBM SETPAGESIZE
TBM GETPAGESIZE
TBM SETLINESIZE
TBM GETLINESIZE
tbm getthumbrect tbm"getchannelrect tbm_setthumbrect tbm getthumbrect
WMJJSER + 16 WM_USER+ 17 WMJJSER + 18 WMJJSER + 19
WM_USER + 20 WM USER+ 21
WM USER+ 22
WM USER+ 23
WM USER + 24
WMJJSER + 25 WMJJSER + 26 WMJJSER + 27 WM USER + 28
wParam = 0, IParam - 0,возвращает
число пометок
wParam = 0, IParam = 0, возвращает
нижнюю границу выделения
wParam = 0, IParam - 0, возвращает
верхнюю границу выделения
wParam = флаг перерисовки (BOOL),
IParam ~~ 0, сбрасывает текущее
выделение
wParam = частоте следования пометок,
IParam = номеру позиции, начиная с
которой расставляются пометки,
устанавливает шаг расстановки
пометок
wParam ~ 0, IParam — wPageSize,
определяет, на сколько позиций
необходимо передвинуть слайдср при
получении сообщений
ТВ_PAGEDOWN и TBJ>AGEUP,
возвращает предыдущее значение
этого параметра
wParam = 0, IParam = 0, возвращает
значение параметра,о котором
говорится в предыдущей строке
таблицы
wParam — 0. IParam — wLineSize,
определяет, на сколько позиций
необходимо передвинуть слайдер при
получении сообщений
TB_LINEDOWN и TB_LINEUP,
возвращает предыдущее значение
этого параметра
wParam = 0, IParam •- 0, возвращает
значение параметра,о котором
говорится в предыдущей строке
таблицы
Т а б л ч ц а 37. Коды нотификации, посылаемые трекбаром
Сообщение
Описание
TB_LINEUP
TBJLINEDOWN
TB_PAGEUP
TBJ'AGEDOWN
TB_THUMBPOSITION
TB_THUMBTRACK TB_TOP
TB_BOTTOM ТВ ENDTRACK
Нажата VKJLEFT (стрелка влево) или VK_UP (стрелка вверх)
Нажала VK_RIGHT (стрелка вправо) или VK_DOWN (стрелка вниз) Нажата VKJNEXT (PageUp) или щелчок мытью перед елайдером Нажата VK_PRIOR (PageDown) или щелчок мышью после слайдера Слайдер зафиксирован после протяжки с помощью мыши
Слайдер протягивается с помощью мыши Нажата VK_HOME (Home), слайдер устанавливается в положение, соответствующее верхней границе диапазона Нажата VK_END (End), слайдер устанавливается в положение, соответствующее нижней границе диапазона
Слайдер зафиксирован после перемещения с помощью клавиатуры
При манипуляциях с трекбаром последний посылает родительскому окну сообщения WM_HSCROLL. В младшем слове wParam содержится код нотификации, который определяет характер действия, произведенного с трекбаром. Старшее слово wParam содержит позицию слайдера в момент возникновения сообщения. Дескриптор окна трекбара записывается в IParam. В табл. 37 приведены нотификационные сообщения, используемые при работе с трекбаром.
Сообщениия WM TOP, WM BOTTOM, WM_LINEDOWN и WM LINEUP посылаются только в том случае, если пользователь воздействует на трекбар с помощью клавиатуры, TB_THUMBPOSITION и TBJTHUMBTRACK посылаются в случае работы с мышью, остальные сообщения могут посылаться в обоих случаях.
Теперь, изучив теоретически работу трекбара, мы можем рассмотреть программу, в которой демонстрируются возможности этого элемента управления. Для нормальной работы программы помимо основного файла с текстом программы необходимы еще два файла: описаний и ресурсов. Файл описаний приведен ниже:
#с!еЛпе IDM_Exit 101
#define IDM_Dialog 102
ftk-fine IDM About 103
«define ID_OK 104 «define ID_Edit 105 «define ID_Spin 106 «define IDJTrackbar 107 А теперь - файл ресурсов. «include "trackbar.h" TrackbarMenu MENU { POPUP "&File"
{ MENUITEM "E&xit", IDM_Exit
}
MENUITEM "&Dialog", IDM_Dialog POPUP "&Help"
{ MENUITEM "&About", IDM_About
TrackbarDialog DIALOG 0, 0, 100, 100
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE |
WS_CAPTION | WS_SYSMENU CAPTION "Trackbar Demo Dialog" FONT 8, "MS Sans Serif
{
DEFPUSHBUTTON "OK", ID_OK, 25, 73, 50, 14
CONTROL "", ID_Edit, "edit", ES_LEFT | WS_CHILD | WSJV1SIBLE |
WS_BORDER WSJTABSTOP | ES_NUMBER, 25, 14, 50, 12
А теперь - очередь основного файла программы. «include <windows.h> «include <commctrl.h> «include "trackbar.h"
HINSTANCE hlnst;
LRESULT CALLBACK TrackbarWndProc(HWND, UINT, UINT, LONG ); BOOL CALLBACK TrackbarDialogProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR IpszCmdParam, int nCmdShow ) {
HWND hWnd ;
WNDCLASS WndClass ;
MSG Msg;
char szClassName[] = "TrackbarDemo";
hlnst = hinstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.stylc = CSJHREDRAW | CSJVREDRAW;
WndClass.lpfnWndProc = TrackbarWndProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass. hinstance = hinstance ;
WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION); WndClass. hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); WndClass. IpszMenuName = "TrackbarMenu"; WndClass. IpszClassName = szClassName; if ( !RegisterClass(&WndClass) ) {
MessageBox(NULL, "Cannot register class", "Error", MB_OK);
return 0;
hWnd = CreateWindow(szClassName, "Trackbar Demo Program",
WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, NULL, NULL, h!nstance,NULL); if(!hWnd) {
MessageBox(NULL,"Cannot create window", "Error",MB_OK); return 0;
InitCommonControls(); /* Show our window */ ShowWindow(hWnd.nCmdShow); UpdateWindow(hWnd); /* Beginning of messages cycle */
while(GetMessage(&Msg, NULL, 0, 0)) {
TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam;
LRESULT CALLBACK TrackbarWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam ) {
switch(Message) {
case WM_COMMAND: switch(wParam)
i \
case IDM_Exit: SendMessage(hWnd, WM_CLOSE, 0, 0);
break;
case IDM_Dialog;
DialogBox(hInst, "TrackbarDialog", hWnd, TrackbarDialogPruc); break;
}
return 0;
case WMJDESTROY: PostQuitMessage(O); return 0;
return
DefWindowProc(h\Vnd,Mcssage,\vParam, IParam);
BOOL CALLBACK TrackbarDialogProc(HWND hDlg, UINT Message,
WPARAM wParam, LPARAM IParam)
{
static HWND hEditWnd; static HWND hSpinWnd; static HWND hTrackbarWnd; switch(Message) j
case WMJNITDIALOG: hEditWnd = GctDlgltemfliDlg, ID_Edit); hTrackbarWnd = GctDlgltem(hDlg, ID J'rackbar); hSpinWnd = CreateUpDownControl(WS_CHILD | WSJ3ORDER |
WS_VISIBLE j UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 0, 12,50,50,
hDlg, !D_Spin, hlnst, hEditWnd, 10,0,5); hTrackbarWnd = CreatcWindow(TRACKBAR_CLASS,"Trackbar Demo",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | TBS_AUTOTICKS, 4,75, 142.40, hDlg, NULL, hlnst, NULL); SendMessagediTrackbarWnd. TBM_SETRANGE, TRUE,
MAKELONG(0,10));
SendMessagc(hTrackbarWnd. TBM^SETPOS, TRUE, 5); return TRUE; case WMJVSCROLL: SendMessagediTrackbarWnd, TBM^SETPOS, TRUE,
GetDlgIiemInt(hDlg, ID_Edit, NULL,!)); return TRUE; case WMJISCROLL: SetDlgltemlntOiDlg, ID_Edit, SendMcssage(hTrackbarWnd,
TBMJ3ETPOS, 0,0), TRUE); case WM_COMMAND: switch(LOWORD(wParam))
case ID OK:
EndDialog(hDlg,0); return TRUE;
break;
i i
return FALSE;
После того, как эта программа будет запущена и в основном меню программы будет выбран элемент «Dialog», на экране появится диалоговое окно, вид которого показан на рис. 14.
Tiackbar Demo DialogQ!
,__...
Рис. 14. Диалоговое окно со спином и трскбаром
Попробуйте изменить положение спина. При этом изменится положение слайдера на трекбаре. Аналогично, если изменить положение слайде-ра с помощью мыши, то изменится значение в окне редактирования, которое является приятельским окном спина.
При написании этой программы использовались две новые функции. Первая, GetDlgItemInt(), описана в файле winuser.h следующим образом:
WINUSERAPI UINT WINAPI GetDlgItemInt(HWND hDlg, int nIDDlgllem,
BOOL «IpTraiislated. BOOL bSigned);
Эта функция берет число, записанное в окне редактирования (оно представлено в виде строки), преобразует его в числовой вид и возвращает числовое значение. В программе мы используем эту функцию для того чтобы считать значение спина и соответствующим образом изменить положение слайдера трекбара.
Вторая функция, описанная в том же файле, имеет следующий прототип:
WINUSERAPI BOOL WINAPI SetDIg[temTextA(HWND hDlg. int ninDlgltem,
LPCSTR IpString); WINUSERAPI BOOL WINAPI SetDlgItcmTcxtW(HWND hDlg. int nlDDlgltcm,
LPCWSTR IpString);
#ifdefUNICODE
tfdefine SetDlgltemText SetDlgltemTextW
#e!se
#defme SetDlgltemText SetDlgltemTextA
#endif//! UNICODE
Она производит действие, обратное GetDlgItemInt(), т. е. получает в качестве аргумента целое число и возвращает его представление в виде строки. В программе она используется для того, чтобы в окне редактирования отобразить номер позиции слайдера. Все просто, не так ли?
РАБОТА С ИНДИКАТОРОМ (PROGRESS BAR'OM)
Надеюсь, что читатель уже имеет опыт инсталляции программных продуктов для Windows. Там степень завершенности задачи отражается синей полосой, которая постоянно растет. По достижении ею отметки, соответствующей 100%, процесс инсталляции оказывается завершенным. Вот эта синяя полоса и является индикатором, который индицирует степень завершенности достаточно длительной задачи.
По своему внешнему виду это, наверное, самый простой из общих элементов управления. То же самое можно сказать и о работе с ним. У него только один стиль, и всего пять сообщений применяются для управления им.
Как и в случае трекбара, специальной функции для создания индикатора нет. Для создания индикатора необходимо использовать функцию CreateWindowQ или CreateWindowExQ. Для указания имени класса необходимо использовать макрос PROGRESS_CLASS, который в файле commctrl.h описан следующим образом:
#ifdef_WIN32
tfdefme PROGRESS_CLASSA
#define PROGRESS_CLASSW
#ifdef UNICODE
#defme PROGRESS_CLASS
#else
tfdefme PROGRESSJXASS
#endif
#else
#define PROGRESS_CLASS
#endif
"msctls_progress32" L"msctls_progress32"
PROGRESS_CLASSW PROGRESS_CLASSA
"msctls_progress"
156
Для управления индикатором используются сообщения. Поговорим о них.
Наверное, для того чтобы использовать индикатор, необходимо определить для него минимальное и максимальное значения (в приведенном примере, когда я говорил об инсталляционных программах, минимальное и максимальное значения равны 0 и 100 соответственно). С этой целью используется сообщение PBM_SETRANGE (WM_USER+1). wParam его должен быть равно 0, a IParam должно определяться как MAKELONG(wMinRange, wMaxRange), где wMinRange и wMaxRange -минимальное и максимальное значения. Функция, пославшая это сообщение, возвращает значения старого диапазона. Если обозначить возвращаемое значение как IReturn, то LOWORD(lReturn) будет определять нижнюю границу диапазона, a HIWORD(lReturn) будет содержать верхнюю границу диапазона.
Для установки индикатора в определенную позицию (другими словами, для определения необходимой длины индикатора) используется сообщение PBM_SETPOS (WMJJSER + 2). При этом wParam должен содержать позицию, в которую необходимо установить индикатор. IParam должен быть равным 0. Предыдущая позиция индикатора возвращается функцией, пославшей сообщение.
Сообщение РВМ DELTAPOS применяется для определения значения, на которое будет увеличена длина индикатора. wParam этого сообщения определяет приращение, a IParam должен быть равным 0. Функция, пославшая сообщение, возвращает значение предыдущей позиции.
Для определения шага, с которым будет увеличиваться длина индикатора, используется сообщение PBM^SETSTEP, wParam которого определяет шаг, a IParam должно быть равно нулю. По умолчанию, шаг приращения равен 10.
Сообщение РВМ STEPIT указывает индикатору, что необходимо осуществить увеличение длины, используя при этом все текущие установки, т. е. текущую позицию и текущий шаг. Оба параметра сообщения должны быть равны 0.
И, как всегда, демонстрационная программа, с помощью которой читатель сможет увидеть управление индикатором в действии.
Вся программа состоит из трех файлов. Первый - файл заголовков:
fldefinelDC MSCTLS TRACKBARI 101
Adeline IDFvf Exit 101
#dcfme IDM_Dialog 102
#definc IDM_About 103
#defincID_OK. 104
#dcilne ID_Cdit 105
#deiine ID_Spin 106
#define ID^ProgressBar 107
Второй файл - файл ресурсов, он также приводится ниже.
#include "ProgressBar.h"
ProgrcssBarMenu MENU
{
POPUP "&File"
!
MENUITEM "E&xit", IDM Exit
MENUITEM "&Dialog", IDM_Dia!og
POPUP "&Help"
{
MENUITEM "&About", IDM_About
ProgressBarDialog DIALOG 0, 0, 100, 100
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WSJVISIBLE |
WS_CAPTION i WS_SYSMENU CAPTION "Progrcssbar Demo Dialog" FONT 8, "MS Sans Serif
{
DEFPUSHBUTTON "OK", ID_OK, 25, 73, 50. 14
CONTROL "", ID_Edit, "edit", ES_LEFT | WS_CH1LD | WS_VISIBLE [
WS_BORDER | WS_TABSTOP | HS_NUMBER, 25, 14, 50. 12 >
И, естественно, основной файл программы.
#include <windows.h>
#include <commctrl.h>
#include "ProgressBar.h"
HINSTANCE hlnst;
LRESULT CALLBACK ProgressBarWndProc ( HWND, UINT, UINT, LONG ); BOOL CALLBACK ProgressBarDialogProc(I!WND, UINT, WPARAM,
LPARAM);
int WINAPI WinMain ( HINSTANCE hinstancc, HINSTANCE hPrcvInstancc, LPSTR IpszCmdParam, int nCmdShow )
HWND hWnd ;
WNDCLASS WndClass ;
MSG Msg;
char szClassNamef] = "ProgressBarDemo"
hlnst = hinstancc;
/* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.stylc = CS_HREDRAW j CS_VREDRAW;
WndClass.IpfhWndProc = ProgressBarWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hlnstance = hlnstance ;
WndClass.hlcon = Loadlcon (NULL,!DI_APPLICATION);
WndClass.hCursor = LoadCursor (NULL, 1DC_ARROW);
Wndclass.hbrBackground - (HBRUSH) GetStockObject (WHITE_BRUSH);
WndClass.IpszMenuNamc = "ProgressBarMenu";
WndClass. IpszClassName = szClassName;
if ( !RegistcrClass(&WndClass)) {
MessageBox(NULL,"Cannot register cIass","Error",MB_OK); return 0;
} hWnd = CrealeWindow(szClassName, "Progressbar Demonstration Program",
WS_OVERLAPPED\VINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, Mnstance,NULL); if(!hWnd) {
MessageBox(NULL,"Cannot create window","Error",MB_OK); return 0;
/* Show our window */ ShowWindow(hWnd,nCmdShow); Update Window(hWnd);
/* Beginning of messages cycle */
while(GetMessage(&Msg, NULL, 0, 0)) {
TranslateMessagc(&Msg); DispatchMessage(&Msg); ( return Msg.wParam;
LRESULT CALLBACK ProgressBarWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam )
j t
swilch(Mcssage) {
case WM_COMMAND; swilch(LOWORD(wParain))
159
case IDM_Dialog:
DialogBoxfhlnst, "ProgressBarDialog", liWnd, ProgressBarDialogProc);
break; case IDMJExit:
SendMessage(hWnd, WM_CLOSE, 0,0);
break;
}
return 0;
case WM_DESTROY: PostQuitMessage(O); retuni 0;
! return DefWindowProc(hWnd,Message,wParam, IParam);
BOOL CALLBACK ProgressBarDialogProc(HWND hDlg, UINT Message,
WPARAM wParam, LPARAM IParam)
{
static HWND hEditWnd; static HWND hSpinWnd; static HWND hProgressBarWnd; int i; switch(Message)
{
case WMJNITDIALOG: hEditWnd = GetDlgItem(hDlg, ID_Edit);
hSpinWnd = CreatcUpDownControl(WS_CHILD | WS_BORDER |
WS_VISIBLE | UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 0, 12,50,50, hDlg, ID_Spin, hlnst, hEditWnd, 10,0,5);
hProgressBarWnd = CreateWindow(PROGRESS CLASS,
"ProgressBar Demo", WS_CHILD j WSJVISIBLE, 10,75, 130,20, hDlg, NULL, hlnst, NULL); SendMessage(hProgressBarWnd, PBM^SETRANGE, 0,
MAKELPARAM(O.IO));
SendMcssage(hProgressBarWnd, PBM_SETSTEP, 1, 0); SendMessage(hProgressBarWnd, PBM_SETPOS, 5, 0); return TRUE; case WM_VSCROLL: SendMessage(hProgressBarWnd, PBM_SETPOS, GetDlgItemInt(hDlg,
ID_Edit,NULL,l),0); return TRUE; caseWM COMMAND:
160
switch(LOWORD(wParam))
{
case ID_OK: EndDialog(hDlg,0); return TRUE; } break;
} return FALSE;
Вид диалогового окна, возникающего после выбора пользователем элемента «Dialog» в главном меню программы, показан на рис. 15.
Piogiessbai Demo . |
4XI |
|
, : - •' •' |
"-- ...:.,:.'4i.;-:;;v,W>»i';..-';.v" ---:: \: !^"i';,V;"«Tv::,«™. • ,,.- ,', • -.;•:•*?- •-•>-/.: |
cm --..-' |
•••••• |
:}'-- |
;. r.......0.____,:|| |
•i'ij"-. |
|
V^-V""V. |
Эта программа по своему действию очень похожа на приведенную в предыдущем разделе. Разница состоит в том, что спин управляет не трекбаром, а индикатором. Рекомендую читателю нажать несколько раз кнопки спина для того чтобы позиция спина изменилась, и посмотреть, что произойдет с индикатором. При разборе программы особое внимание следует уделить обработке сообщений WMJNITDIALOG и WM_VSCROLL.
Далее рассмотрим окна подсказок (Tooltip Controls) и списки изображений (ImageLists). При изучении Win32 я не увидел тех моментов, когда два этих элемента управления использовались бы самостоятельно. Они являются только вспомогательными элементами.
РАБОТА С ОКНАМИ ПОДСКАЗОК
Окна подсказок - это небольшие всплывающие окна, которые содержат одну строку текста, объясняющую назначение какого-либо инструмента (tool) родительского окна. Под инструментом в данном случае понимается либо элемент управления, присутствующий в родительском
161
окне (пример - полоса инструментов в WinWord'e), либо прямоугольная область внутри рабочей области окна.
Окна подсказок почти всегда скрыты. Отображаются они только в том случае, если пользователь задерживает курсор мыши на инструменте. Подсказка появляется рядом с инструментом и исчезает тогда, когда пользователь либо нажимает клавишу (мыши или клавиатуры), либо убирает курсор с инструмента. Одна подсказка может появляться при задержке курсора на разных инструментах.
К сожалению, не все общие элементы управления имеют специальную функцию для своего создания ( © ). Окна подсказок тоже создаются только посредством применения CreateWindow() или CreateWindowExQ. В этом случае для их создания необходимо использовать макрос TOOLTIPS^CLASS, который в файле commctrl.h описан следующим образом:
#ifdef_WIN32
^define TOOLTIPS_CLASSW
#defmc TOOLTIPS_CLASSA
#ifdef UNICODE
ftleflne TOOLTIPS_CLASS
#else
#define TOOLTI PS CLASS
#cndif
#clse
#define TOOLTIPS (CLASS
#endif
L"tooltips_class32" "tooltips_class32"
TOOLTIPS CLASSW TOOI.TIPS_CLASSA
"tooltips_class"
При создании подсказки могут использоваться два стиля, специально разработанные для окон этого типа - TTS_ALWAYSTIP и TTS_NOPREFIX. Подсказка, имеющая стиль ITS ALWAYSTIP, появляется при помещении курсора на инструмент вне зависимости от того, активно или не активно родительское окно.
Необходимо отметить еще одну возможность создания окон подсказок. Дело в том, что в Win32 некоторые элементы управления имеют специальный стиль, обычно оканчивающийся на TOOLTIPS. Он позволяет программе не создавать собственные окна подсказок, а использовать встроенные возможности системы. Разумеется, этот способ использования подсказок намного проще.
Достаточно часто подсказки используются в панели инструментов, при этом каждая кнопка в панели инструментов соответствует элементу
162
меню. При этом подсказки, как правило, совпадают с текстом, отображаемым в соответствующем элементе меню. Если окно подсказки создается со стилем TTS_NOPREFIX, то система автоматически удаляет знак амперсанта, если он присутствует в строке меню.
Помимо этого, подсказка сама по себе может быть активной и неактивной. Акпганая подсказка появляется при нахождении курсора на инструменте, неактивная подсказка ire отображается ни в каких случаях.
Т а б л и ц а 38. Сообщения, посылаемые "подсказкам"
С сюощсшк'
Описание
ТТМ_ ACTIVATE ТТМ SETDI-LAYTIMF.
ТТМ ADDTOOL TTM_DELTOOL
TTM_NEWTOOLRECT TTM_RELAYEVENT
ТТМ GF.TTOOLINFO TTM_SETTOOLINFO ТТМ НГГП-ST
ТТМ GF.TTEXT TTMJJPDATETIPTEXT TTMJiETTOOLCOUNT ТТМ ENUMTOOLS
TTM_GETCURRENTTOOL ТТМ WINDOWFROMPOINT
WM USER
WM USER + 3
WM USER • 7
WM USER ~ 13
WM USER + i 6
Подсказка делается активной или неактивной. wParam = TRUE - под-ска зка активна, IParam всегда = О Определяются интервал инициализации, интервал отображения и интервал повторного отображения Регистрирует инструмент, с которым будет работать подсказка Удаляет ранее добавленный инструмент
Определяет границы окна подсказки Передаст сообщение от мыши окну подсказки для обработки Запрос информации об инструменте, с которым работает подсказка Установка информации для инструмента
Определение, попадает ли точка в указанный для инструмента прямоугольник, и, если попадает, получение информации об инструменте
Получение текста подсказки, отображаемой с инструментом Установка текста подсказки для инструмент
Получение числа инструментов, с которыми работает подсказка Позволяет программе последовательно перебрать все инструменты, с которыми работает окно подсказки Получение информации о текущем инструменте
Выдача окна подсказки в месте, определяемом не положением курсора, а параметрами сообщения
Так как подсказка является окном, то для управления ею используются сообщения. Их список сообщений с кратким описанием приведен в табл. 38. Рассмотрим наиболее часто используемые сообщения.
Для того чтобы подсказка работала с тем или иным инструментом, необходимо этот инструмент включить в список инструментов, с которыми работает окно подсказки. Для этого окну подсказки надо направить сообщение TTM_ADDTOOL. WParam этого сообщения должен быть равен 0, a IParam содержать указатель на структуру типа TOOLINFO. Эта структура описана в файле commctrl.h :
typedef struct tagTOOLINFOA {
UINT cbSize;
UINT uFlags;
HWND hwnd;
UINT uld;
RECT reel;
HINSTANCE hinst;
LPSTR IpszText; } TOOLINFOA, NEAR «PTOOLINFOA, FAR «LPTOOLINFOA;
typedef struct tagTOOLINFOW {
UINT cbSize;
UINT uFlags;
HWND hwnd;
UINT uld;
RECT rcct;
HINSTANCE hinst;
LPWSTR IpszText; } TOOLINFOW, NEAR *PTOOLINFOW, FAR *LPTOOLINFOW;
#ifdef UNICODE tfdcfine TOOLINFO
#define PTOOLINFO
#defme LPTOOLINFO
#else
tfdefine TOOLINFO tfdefine PTOOLINFO tfdefine LPTOOLINFO
#endif
TOOLINFOW PTOOLINFO W LPTOOLINFOW
TOOLINFOA PTOOLINFOA LPTOOLINFOA
Первое поле этой структуры - cbSize - должно содержать размер в байтах структуры типа TOOLINFO. Сам факт присутствия поля, содержащего такую информацию, говорит о том, что фирма Microsoft не исключает возможности изменения и/или дополнения этой структуры.
164
Таблица 39. Битовый файлы, определяющие вил и поведение подсказки
Фл;и |
Значение |
Описание |
TTFJDISHWND TTF CENTERTIP TTF_RTLREADING |
0x0 1 0x02 0x04 |
Флаг установлен - поле uld содержит хэндл инструмент;!, иначе - идентификатор инструмента Центрирует подсказку под инструментом Отображает текст справа налево, как в арабском |
|
|
Я'|ЫКС |
TTF_SUBCLASS |
Ох 10 |
Подсказка должна перехватывать сообщения WM MOUSEMOVE, адресованные инструменту |
Поле hwnd определяет родительское окно инструмента.
Поле uld обычно содержит идентификатор инструмента. Если поле uFlags включает TTF_IDISHWND, то поле uld содержит хэндл окна, внутри которого находится область, используемая в качестве инструмента.
Следующее поле - reel - определяет координаты окна инструмента относительно левого верхнего угла клиентской области окна, определяемого полем hwnd. Если поле uFlags включает флаг TTF_IDISHWND, поле rect игнорируется.
В поле hinst хранится хэндл экземпляра программы, которая содержит строковый ресурс, определяющий текст подсказки. Если это поле не равно нулю, то поле IpszText содержит идентификатор строкового ресурса.
Как, надеюсь, читатель уже понял, поле IpszText может интерпретироваться по-разному. Итак, вариант первый. Если значение поля IpszText равно LPSTR TEXTCALEBACK, то именно окно, хэндл которого указан в поле hwnd, получает нотификационное сообщение TTN__NEEDTEXT, уведомляющее о том, что родительское окно инструмента должно определить, какой текст подсказки должен быть отображен. Второй вариант -поле содержит идентификатор строкового ресурса, в котором определен текст сообщения. Этот вариант используется тогда, когда поле hinst не равно 0. Кроме этого, признаком того, что поле содержит идентификатор строкового ресурса, является старшее нулевое слово. И, наконец, третье, наиболее часто использующееся поле, содержит указатель на строку, содержащую текст подсказки.
Таким образом, мы видим, что структура типа TOOLINFO полностью определяет, что используется в качестве инструмента - элемент управления или область экрана, а также где находится текст подсказки - в ресурсах, в строке программы или он определяется родительским окном инструмента.
В любой момент программа может изменить текст подсказки. Для этого окну подсказки посылается сообщение ТТМ UPDATETIPTEXT, wParam которого должен быть равным 0, a IParam - указывать на структуру типа TOOLINFO.
Программа может получить текст, который используется для выдачи подсказки об инструменте с помощью посылки окну подсказки сообщения ТТМ GETTEXT. В этом сообщении wParam должен быть равным 0. IParam, как и в предыдущем случае, должен содержать указатель на структуру типа TOOLINFO, в которой определяется инструмент, подсказка о котором запрашивается. Ноле IpszText указывает на буфер, в который будет записан текст подсказки.
Для того чтобы отобразиться, окно подсказки должно получить сообщение от мыши. Так как Windows посылает сообщения только тому окну, поверх которого находится курсор, программа должна использовать сообщение TTM_R.ELAYEVENT для того чтобы транслировать сообщение окну подсказки. wParam этого сообщения должен быть равным нулю, a IParam должен содержать указатель на структуру типа MSU, в которой хранится информация о транслируемом сообщении. При этом необходимо учесть, что окно подсказки обрабатывает информацию только о сообщениях, приведенных ниже: WM IJiUTTONDOWN;
" WM LBUTTONUP;
* WM]MUUTTONI)OWN: ]WM MBUTTONUP;
* WM^MOUSEMOVF;
* WM RBUTTONDOWN;
* WM RBUTTONUP.
Если инструмент представляет собой прямоугольную часть окна, то тогда никаких сложностей не появляется. Если же инструмент является системным окном (таким, например, как кнопка), то в этом случае возникают определенные трудности. Программа должна будет или перехватывать сообщения посредством использования hook'oB, или подменить оконную функцию системного окна (осуществить subclassing). К сожалению, рассмотрение вопросов, связанных с поок'амн и subclassing'oM выходит за рамки этой книги, поэтому я вынужден буду остановиться только на подсказках, связанных с областью окна. Уважаемый читатель!
166
Предлагаю вам изучить вопросы о перехвате сообщений и подмене оконных функций самостоятельно.
Когда окно подсказки получает транслированное сообщение WM MOUSEMOVE, оно определяет, находится ли курсор в области, к которой привязана подсказка. При положительном ответе окно подсказки устанавливает таймер. О конце интервала окно снова проверяет, находится ли курсор в нужной области. При втором положительном ответе формируется текст подсказки, копируется в окно подсказки и окно выдается на отображение. Подсказка отображается до тех пор, пока окно подсказки не получит сообщения о нажатии или отжатнн клавиши мыши или о перемещении курсора за пределы интересующей области.
При работе окно подсказки использует три определенных временных интервала. Первый из них, называемый интервалом инициализации, определяет период, в течение которого курсор должен находиться в пределах интересующей области для того, чтобы отобразилась закладка. Второй - интервал повторного отображения - определяет задержку между последовательными отображениями окна подсказки в тех случаях, когда курсор скользит по инструментам, например, по панели инструментов. Третий интервал - интервал отображения - определяет время, в течение которого подсказка находится на отображении в тех случаях, когда курсор находится внутри интересующей области или в пределах границы инструмента. Все чти периоды могут быть определены с помощью сообщения ТТМ SETDELAYT1ME. wParam этого сообщения определяет, какой интервал устанавливается. IParam определяет длительность интервала в миллисекундах. Допустимые значения wParam приведены в табл. 40.
В тол) случае, когда инструментом является область окна, размеры и/или положение которой изменились, окну подсказки необходимо послать сообщение ТТМ NEWTOOLRECT для того, чтобы подсказка появлялась в нужном месте. wParam этого сообщения всегда равен 0.
Тай .1 и и а 40 Идентификаторы временных интервалов
П„р |
Значение |
Описание |
TTDT AUTOMATIC TTDT RP.SHOW ТТГУГ"ЛЬ'ТОРОР TTDT INITIAL |
0 1 |
Все интервалы вычисляются автоматически на основе IParam Определяется интервал повторного отображения Определяется интервал отображения Определяется интервал инициализации |
IParam этого сообщения должен указывать на структуру типа TOOLINFO, поля hwnd и uld которой должны определять инструмент, а поле rect - новые границы инструмента. В том случае, когда инструмент реализован как окно, информировать подсказку о его изменении не нужно, так как подсказка определит факт нахождения курсора в границах инструмента по хэндлу окна.
Перед своим отображением окно подсказки посылает родительскому окну нотификационное сообщение TTN_SHOW, а перед скрытием -TTN_POP. В данном случае нотификационные сообщения посылаются с помощью сообщения WM NOTIFY.
Для получения информации об инструменте программа может использовать сообщения TTM_GETCURRENTTOOL и TTMJ3ETTOOLINFO. Изменить информацию об инструменте можно с помощью сообщения TTM_SETTOOLINFO. Если программе требуется, чтобы подсказка с данным инструментом больше не работала, окну подсказки нужно направить сообщение ТТМ DELTOOL. Параметры этих сообщений однотипны и ничего сложного в них нет. Рекомендую читателю изучить работу этих сообщений самостоятельно.
Как уже было сказано, сами по себе окна подсказок не используются, поэтому демонстрационной программы я не приведу. Тем не менее, в разделе, посвященном закладкам, будут даны примеры другого способа использования подсказок, применимого, к сожалению, только к общим элементам управления, которые появились в Windows NT и Windows'95. В чем состоит этот способ? Дело в том, что при создании некоторых элементов управления можно указать стиль, позволяющий этим окнам реагировать на сообщения WMJNOTIFY с нотпфикационным кодом TTNJNEEDTEXT. Скажем, для закладок этот стиль называется TCSJTOOLTIPS, для панели инструментов - TBSJTOOLTIPS и т. д. В этих случаях родительскому окну элемента управления в качестве IParam сообщения WM_ NOTIFY передается указатель на структуру типа TOOLTIPTEXT, описание которой, находящееся в файле commctrl.h имеет следующий вид:
typcdef struct tagTOOLTIPTEXTA {
NMHDR heir;
LPSTR ipszTcxt;
char s/Tcxt[8()];
HINSTANCE hinst;
UINT u Flags; ! TOOLTIPTEXTA, FAR «LPTOOLTIPTEXTA;
typcdef struct tagTOOLTIPTEXTW {
NMHDR hdr;
LPWSTR IpszText;
WCHAR szText[80];
HINSTANCE hinst;
UINT uFIags; ) TOOLTIPTEXTW, FAR *LPTOOLTIPTEXTW;
#ifdcf UNICODE
#define TOOLTIPTEXT Wcllne LPTOOLTIPTEXT
#else
tfdefmc TOOLTIPTEXT
#definc LPTOOLTIPTEXT
#endif
TOOLTIPTEXTW LPTOOLTIPTEXTW
TOOLTIPTEXTA LPTOOLTIPTEXTA
Первое поле этой структуры, тоже структура, но типа NMHDR, описана в файле winuser.h так:
typcdef struct tagNMHDR
i
HWND hwndFrom;
UINT idFrom;
UINT code; //NM_code } NMHDR; typedef NMHDR FAR * LPNMHDR;
hwndFrom - хэндл элемента, пославшего нотификационное сообщение, idFrom - идентификатор этого элемента, code - код нотификационного сообщения. Вроде бы все ясно.
Второе поле стуктуры типа TOOLTIPTEXT (как бы не запутаться с этими структурами!) - IpszText - может содержать указатель на строку, выдаваемую в качестве подсказки, или, если поле hinst не равно 0, содержит идентификатор строкового ресурса, определяющего текст выдаваемой подсказки.
Вместо того чтобы определять указатель на строку, можно скопировать эту строку в буфер szText, который является третьим полем структуры типа TOOLTIPTEXT.
Поле hinst является хэндлом экземпляра, содержащего строковый ресурс с текстом подсказки. Если IpszText является указателем на строку подсказки, это поле должно быть равным NULL.
И наконец, последнее поле - uFIags - содержит комбинацию флагов TTFJDISHWND и TTF RTLREADING, которые были рассмотрены ранее.
Анализируя поля этих двух структур, можно определить элемент, для которого определяется подсказка. Для того чтобы эта подсказка появи-лась на экране, достаточно определить либо указатель на строку подсказки, либо ее идентификатор в таблице строк (не забыть при этом о поле hinst!), либо скопировать эту строку в предлагаемый буфер. И все! Пример подобного использования подсказок приведен в разделе о работе с закладками.
РАБОТА СО СПИСКОМ ИЗОБРАЖЕНИЙ
И Windows предусмотрен интересный элемент, который лично я могу назвать элементом управления с большой натяжкой. Тем не менее, этот элемент активно используется при работе с другими элементами управления, например, с закладками, речь о которых еще впереди. Я имею в виду список изображений (Image List). Он представляет собой коллекцию изображении одинакового размера, к каждому из которых можно осуществить доступ по его индексу. Список изображении используется для эффективного управления и манипулирования большими наборами изображении.
Наверное, этот элемент управления самостоятельно не используется еще и потому, что он не является окном. Список изображений - это всего-навсего структура в памяти, обеспечивающая простой доступ к изображениям.
Как и в случае обычных списков, при работе со списками изображений можно создавать и удалять списки, добавлять, удалять и изменять элементы списков. Специфическим именно для списков изображений являются операции по прорисовке и перетаскиванию изображений.
Списки изображений могут быть немаскированными и маскированными. Немаскированный список представляет собой один большой цветной bitmap, который, в свою очередь, состоит из одного или нескольких изображений. Маскируемый список состоит из двух больших bitmap'oB, первый из которых, цветной, содержит непосредственно список изображений, а второй, монохромный, содержит список масок -по одной для каждого элемента.
При прорисовке немаскируемого изображения оно просто копируется на соответствующее устройство. R случае прорисовки маскируемого изображения биты изображения комбинируются с битами маски, создавая обычно прозрачные области, сквозь которые видно то изображение, которое было на устройстве до прорисовки.
Для создания списка изображений приложению необходимо вызвать функцию ImageList Create(), которая описана в файле commctrl.h следующим образом:
WINCOMMCTRLAPI HIMAUEL1ST WINAM
lst Crcatc(int ex. int су,
UfNT Hags. int cliiitial, inl cGrow):
G этой функции первые два аргумента, сх и су, определяют размер в пикселах каждого изображения. Третий аргумент, flags, указывает тип списка изображений. Для каждого типа в файле commctrl.h предусмотрен. макрос, начинающийся с ILC . Список этих типов приведен в табл. 41.
Размер создаваемого bitmap 'а вычисляется, исходя из значения четвертого аргумента. Этот аргумент определяет, сколько изображений должен включать bitmap. Если на каком-то этапе число изображений, включенных в bitmap, достигнет предельного значения, то система автоматически расширит bitmap, добавив место для хранения еще определенного числа изображений, которое определяется последним параметром функции - cGrow.
Т а Я л и ц а 41. Флаги, используемые при создании списка изображении
|
Флаг |
Значение |
Описании |
|
ILC COLOR |
0x1)000 |
Используется флаг ни умолчанию, обычно |
|
|
|
ILC COLOR4. для старых драйверов - |
|
|
|
ILC COLORDDH |
|
ILC MASK |
0x0001 |
Создается маскированный bitmap |
|
ILC_COLORDDO |
OxOOFE |
Используется bitmap, зависящий от устройства |
|
|
|
(устаревший формат) |
|
ILC_COI.OR4 |
O.\0004 |
U качестве bitmap 'а. содержащею изображения, |
|
|
|
используется 16-цвстнын bitmap |
|
ILC COLOR» |
0x0008 |
И качестве bitmap'a, содержащего изображения. |
|
|
|
используется 256-цветный bitmap |
|
II. С COLOR 1П |
0x0010 |
I) качестве bitmap'a. содержащего изображения. |
|
|
|
используется bitmap, допускающий одновременное |
|
|
|
использование до 65536 цветок |
|
If С COLOR24 |
0x0018 |
В качестве bitmap'a, содержащего изображения, |
|
|
|
используется bitmap, допускающий до 2**24 цветов |
|
ILC COLOR32 |
ОхП('.20 |
Н кичестнс hitmap'a. содержащего изображения. |
|
|
|
нсполмусгси bitmap, допускающий до 2**J2 цвет он |
|
ILC I'ALETIl- |
ОхОЙОО |
Со списком изображений используется цнстовая |
|
|
|
палитра |
170
171
При успешном выполнении функция возвращает хэндл созданного списка. NULL должен заставить программиста вздохнуть и найти ошибку.
Но что же происходит при создании списка изображений? Помимо создания непосредственно bitrnap'a с указанными характеристиками, функция создает контекст, совместимый с экраном, и выбирает созданный bitmap в качестве текущего для этого контекста. В случае маскированного bitmap'a функция создает два экранно-совместимых контекста, при этом для одного в качестве текущего она выбирает bitmap с изображениями, а для другого - bitmap с масками.
Естественно, что при использовании такой техники резко уменьшается время, необходимое для копирования изображения на экран. Как результат - программы, использующие список изображений, могут работать значительно быстрее тех, которые перед использованием вынуждены загружать изображения из ресурсов.
Для того чтобы удалить список изображений из памяти (отдельные изображения, из которых состоит список, остаются на своих местах, уничтожаются только указатели, что, собственно, и делает набор изображений списком), необходимо вызвать функцию ImageList_Destroy(), которая описана следующим образом:
WINCOMMCTRLAPI BOOL WINAPI ImageList_Dcstroy(HIMAGELIST himl);
Единственным аргументом этой функции является хэндл удаляемого списка изображений. Возвращенное этой функцией значение FALSE говорит о том, что при удалении списка произошла какая-то ошибка, и список из памяти не удален.
Итак, надеюсь, читатель понял, что такое список изображений и для чего он служит. Теперь возникают очередные вопросы: как добавлять изображения в список, удалять их и производить манипуляции с ними?
Для того чтобы добавить изображение в список, необходимо воспользоваться функцией ImageList_Add(), описание которой имеет следующий вид:
WINCOMMCTRLAPI int WINAPI ImageList_Add(HIMAGELlST him!,
HBITMAP hbmlmage, HBITMAP hbmMask);
Первый аргумент этой функции - himl - очевиден: хэндл списка изображений. Второй аргумент - hbmlmage - представляет собо хэндл добавляемого в список изображения. Третий аргумент - hbmMask - хэндл
монохромного bitmap'a, который содержит маски. В случае немаскированного списка третий аргумент игнорируется.
Описание функции ImageList_AddMasked() приведено ниже:
WINCOMMCTRLAPI int WINAPI ImageList_AddMasked(HIMAGELIST himl,
HBITMAP hbmlmage, COLORREF crMask);
Эта функция действует почти так же, как и предыдущая, но маска генерируется автоматически. Для генерации маски необходимо задать цвет. Если в изображении встречается пиксель указанного цвета, то цвет этого пикселя заменяется на черный, а соответствующий бит маски - на 0. В результате, при прорисовке изображения пиксели указанного цвета становятся прозрачными. Аргументы этой функции также очевидны. Первый - хэндл списка изображений, второй - хэндл включаемого в список изображения, третий - цвет пикселей, которые необходимо сделать прозрачными, при прорисовке.
Для добавления в список иконки или курсора используется макрос ImageList_AddIcon(), первым аргументом которого необходимо указать хэндл списка изображений, а вторым - хэндл добавляемой иконки или добавляемого курсора. Макрос возвращает индекс добавленного изображения.
При необходимости программа может создать новую иконку или курсор, используя изображение и маску из списка изображений. Для этой цели необходимо использовать функцию ImageList_GetIcon(). Её описание приведено ниже:
WINCOMMCTRLAPI HICON WINAPI ImageList_GetIcon(HIMAGELIST himl,
int i, UINT nags);
Первый аргумент этой функции - хэндл списка изображений. Второй -индекс изображения, на основе которого будет создана иконка или курсор. Третий аргумент - флаги прорисовки, которые можно найти в таблице, приведенной при описании функции ImageList_Draw().
Функция возвращает хэндл созданной иконки или курсора.
К этому моменту мы научились добавлять изображения в список. А для удаления изображения нужно вызвать функцию ImageList_Remove(), описанную так:
WINCOMMCTRLAPI BOOL WINAPI ImageList_Removc(HIMAGELIST himl,
int i);
173
Автор уверен, что даже не заглядывая дальше, читатель догадался, что первым аргументом является хэндл списка изображений, а вторым -индекс удаляемого изображения. Если вместо индекса изображения подставить -1, то функция удалит все изображения из списка, но не удалит сам список. Для удаления всех изображений из списка можно воспользоваться макросом ImageList_RemoveAH(), единственным аргументом которого является хэндл списка изображений.
Для замены изображения в списке служит функция ImageList_Replace(). Её описание находим в файле commctrl.h:
WINCOMMCTRLAPI BOOL WINAPI ImagcList_Replace(HIMAGFLLIST him],
int i,
HBITMAP hbmlmage, HBITMAP hbmMask);
Аргументы этой функции вполне понятны: первый - хэндл списка изображений; второй - индекс замещаемого изображения; третий и четвертый - хэндлы нового изображения и его маски. Если список немаскированный, четвертый аргумент игнорируется.
Очередная функция - ImageList_ReplaceIcon() - описана так:
WINCOMMCTRLAPI int WINAPI ImageList_RcplaceIcon(HIMAGELIST himl,
int i. HICON hicon);
Нужно ли описывать аргументы этой функции?
Если в функции ImageEist_ReplaceIcon() второй аргумент заменить на -1, то иконка или курсор будут не замещать старое изображение, а добавляться в список. Этот нюанс используется в макросе ImageList_ AddlconQ, аргументами которого являются хэндл списка изображений и хэндл добавляемой иконки или курсора.
Вполне вероятно, что если второй аргумент функции ImageList_Replace() равен -1, то изображение не замещает старое, а добавляется. Проверку этой гипотезы я оставляю читателю.
Для того чтобы прорисовать изображение, хранящееся в списке, необходимо использовать функцию ImageListJDraw(). Вполне понятно, что для прорисовки изображения вызывается что-то типа функции BitBlt(), которая требует два контекста устройства, координаты и прочее. Вспомним, что контекст устройства, в котором хранится список изображений, у нас ' есть. Координаты отдельного изображения моментально вычисляются по его индексу. И что остается? Хэндл контекста, на кото-
рый будет копироваться изображение, координаты в этом контексте и флаги прорисовки.
Функция ImageList_Draw() имеет следующий прототип:
WINCOMMCTRLAPI BOOL WINAPI ImageList_Draw(HIMAGELIST himl,
int i,
HOC hdcDst, intx, int y, UINT (Style);
Сравните аргументы этой функции с теми, наличие которых мы вычислили чуть выше. На всякий случай поясню. Первый аргумент -хэндл списка изображений, второй - индекс изображения, третий - хэндл контекста, на который будет копироваться изображение, четвертый и пятый - координаты в этом контексте, начиная с которых будет скопировано изображение, и, наконец, шестой - именно флаги прорисовки. Они приведены в табл. 42.
Привлекательность использования списков изображений повышает еще одно обстоятельство. При использовании списков возможно использование специальных функций, позволяющих пользователю перемещать изображения (drag-and-drop) на экране: во-первых, с минимальными затратами на написание нового кода; во-вторых, без заметного мерцания.
Таблица 42. Флаги прорисовки отдельного изображения в списке изображений
Значение
ILD_NORMAL
ILD TRANSPARENT
ILD_BLEND25 ILD_BLEND50
ILD_MASK
ILD IMAGK
ILD OVERLAYMASK
ILD SELECTED
ILD FOCUS
ILD BLEND
0x0002 0x0004
0x0010
0x0020
OxOFOO
ILD BLEND50 ILD BLEND25 ILD~BLEND50
Обычное копирование изображения Каждый белый бит маски заставляет соответствующим бит изображения прорисовываться как прозрачный Снижение интенсивности цветов изображения на 25 %
Снижение интенсивности цветов изображения на 50 % Прорисовка маски Прорисовка изображения
Операция перемещения изображения начинается вызовом функции ImageList_BeginDrag(), прототип которой выглядит следующим образом:
WINCOMMCTRLAPI BOOL WINAPI ImageList_BcginDrag(IMAGELIST himlTrack,
int iTrack, int dxHotspot, int dyHotspot);
В число аргументов этой функции входят хэндл списка изображений, индекс перемещаемого изображения и координаты «горячего пятна» (hot spot'a - о проблемы перевода!) внутри изображения. Горячее пятно - это пиксель, по которому определяется точное положение изображения на экране. Обычно горячее пятно определяется таким образом, чтобы оно совпадало с горячим пятном курсора мыши. С этим горячим пятном нам еще предстоит помучиться.
Эта функция начинает операцию по перетаскиванию изображения, накладывая курсор мыши на изображение, определяя горячее пятно и прорисовывая изображение в начальной позиции. Помимо этого, функция также сообщает системе о том, что обновление каких-либо частей экрана запрещено.
Следом за функцией ImageList_BeginDrag() обычно используется функция ImageList_DragEnter(). Описание этой функции можно найти в файле commctrl.h:
WINCOMMCTRLAPI BOOL WINAPI ImagcList_DragEnter(IIWND hwndLock,
int x, int y);
Эта функция запрещает обновление указанного окна во время выполнения операции «drag-and-drop» и прорисовывает перемещаемое изображение в промежуточных позициях (до того, как будет отпущена клавиша мыши). В некотором смысле можно сказать, что эта функция делает изображение курсором мыши (конечно, нельзя понимать это буквально, сходство чисто внешнее). Первый аргумент этой функции понятен -хэндл окна, обновление которого запрещается. Этим окном является то окно, в котором производится перемещение изображения. Второй и третий аргументы определяют координаты той точки, в которой необходимо прорисовать изображение. ВНИМАНИЕ! В данном случае необходимо указывать координаты оконные, а не координаты в рабочей области окна. Таким образом, до обращения к этой функции необходимо определить ширину границы окна, высоту заголовка и, при необходимости, ширину полосы меню. В демонстрационной программе это сделано при
функции GetSystemMetrics(). Рекомендую читателю изучить эту функцию самостоятельно.
Функция ImageListJDragMove() описана следующим образом:
WINCOMMCTRLAPI BOOL WINAPI I mage I ist_DragMove(int x, int y);
Эта функция перемещает изображение, но не прорисовывает его. Попробуйте в демонстрационной программе убрать функцию lmageList_DragEnter() и посмотреть, что получится.
Последней функцией, обеспечивающей «drag-and-drop», является lmageList_Endnrag(). Эта функция завершает перемещение, но не разрешает обновление окна и не производит прорисовку перемещенного изображения. Для разрешения обновления окна необходимо вызвать функцию ImageList_DragLeave(), передав ей в качестве аргумента хэндл окна, а затем прорисовать изображение, например, с помощью функции ImageList Draw().
Л теперь, как всегда, демонстрационная программа. В этой программе при создании окна производится прорисовка двух икон в левой верхней части рабочей области. Иконки могут быть скопированы в другое место при использовании операции «drag-and-drop». Думаю, читателю не составит труда при необходимости изменить эту программу так, чтобы иконки не копировались, а перемещались.
При разработке программы я допустил определенного рода плагиат. Одну из иконок я «выдрал» из программы pview95, другую - из примера mixtree, поставляемых с Borland Си- 5.0. Если читатель захочет, то он легко может заменить иконки на свои.
//include <windo\vs.h> //include <commctrl.h>
//define CX_ICON 32 //define CYJCON 32
UINSTANCE hlnst;
LRLSULT CALLBACK ImageListWndProc ( HWND, UINT, UINT, LONG );
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hl'revlnstancc, LPSTR IpszCmdParam, int nCmdShow )
HWND hWnd ;
WNDCLASS WndClass ;
MSG Msg;
char szClassNamcf] ^ "ImageList";
177
hlnst = hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = ImageListWndProc;
WndClass.cbClsExtra = 0;
WndCIass.cbWndExtra = 0;
WndClass.hlnstance = hlnstance ;
WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
WndClass.hbrBackground = (HBRUSH) GetStockObjcct (WHITE_BRUSH);
WndClass.lpszMenuName = "";
WndClass.lpszClassName = szClassName;
if ( !RegisterClass(&WndClass)) {
MessageBox(NULL,"Cannot register class","Error",MBJ3K); return 0;
}
hWnd = CreateWindow(szClassName, "Image List Demo Program",
WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT, CWJJSEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, NULL, NULL, hlnstance.NULL);
if(!hWnd)
{
MessageBox(NULL,"Cannot create window», «Error», MB_OK); return 0; }
InitCommonControlsO; /* Show our window */ ShowWindow(hWnd,nCmdSliow); UpdateWindow(hWnd);
/* Beginning of messages cycle */
whi!e(GetMessage(&Msg, NULL, 0, 0))
t i
TranslateMessage(&Msg); DispatchMessage(&Msg);
} return Msg.wParam;
LRESULT CALLBACK ImageListWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam )
<
static HIMAGELIST hlmageList;
static int i;
static HOC hDC, hPaintDC;
PAINTSTRUCT PaintStruct;
RECT rBigRect = {0, 0, CXJCON * 2, CYJCON};
RECT rLittleRecll = {0, 0. CX_ICON, CYJCON};
static POINT Point, pHotSpot;
static BOOL bCapture = FALSE;
static int nXBordcr, nYBordcr. nYCaption;
switch(Message) {
case WM_CREATE: hDC = GctDC(hWnd);
hlmageList - IniageListJJreate(CXJCON, CYJCON, ILCJvIASK, 3, 3); ImageList_AddIcon(hImageList, Loadlmage(hlnst, "Mixtree.ico",
IMAGEJCON, 0, 0, LRJJ3ADFROMFILE)); ImageList_AddIcon(hImageList, Loadlmage(hlnst, "Pview95.ico",
IMAGEJCON, 0, 0, LRJ.OADFROMFILE)); nXBordcr = GetSystemMclrics(SM_CXBORDER); nYBorder = GetSystemMctrics(SMJCYBORDER); nYCaption = GetSystemMetrics(SMJCYCAPTION); return 0;
case WMJ'AINT:
hPaintDC = BcginPaint(hWnd, &PaintStruc(); for(i = 0; i < 2; i++) Image-ListJ)raw(hImageList, i, hPaintDC, i * CXJCON, 0,
ILD_NORMAL); EndPaint(hWnd. &PaintStruct); return 0;
case WMJ.BUTTONDOWN: Point.x = LOWORD(lParam); Point.y = HIWORD(lParam); if(PtInRect(&rBigRect, Point)) >
SetCapture(hWnd); bCapture = TRUE; if(Pt!nRect(&rLittlcRectI, Point))
i = 0; else
i= I;
pHotSpot.x = Point.x - i * CXJCON; pHotSpot.y = Point.y;
ImageListJ3eginDrag(hImageList, i, pHotSpot.x, pHotSpot.y); ImageListJJragEnterfhWnd, Point.x + nXBordcr, Point.y + nYBorder +
nYCaption); }
return 0;
case WMJVIOUSEMOVE: iffbCapture) ImageListJ>agMove(LOWORD(!Param), HIWORD(lParam) + nYBorder
-i- nYCaption); return 0;
case WM_LBUTTONUP: if(bCapture)
ImageListJSndDragO;
ImageList_DragLeave(hWnd);
ImageListJ>aw(hImageList, i, hDC, LOWORD(lParam)- pHotSpot.x,
HIWORD(lParam) - pHolSpot.y, ILDJMORMAL); ReleaseCapturc(); bCapture = FALSE;
}
return 0;
case WM_DESTROY: ReleascDC(hWnd, hDC); ImageList_Destroy(hImageList); PostQuitMessage(O); return 0;
\ j
return DefWindowProc(hWnd,Message,wParam, IParam); >
Вид окна до произведения операций «drag-and-drop» показан на рис. 16. На рис. 17 приведен вид этого же окна после выполнения нескольких операций копирования иконок.
Мне бы хотелось, чтобы читатель обратил внимание на возможность легкого перемещения и копирования изображений. Что бы пришлось делать в том случае, если бы здесь не использовался список изображений?
В Image List Demo Progiam
Рис. 16. Окно с двумя изображениями in списка изображений до операции "drag-and-drop"
Ш Image List Demo Program
mmm\
Рис. 17. Предыдущее окно после нескольких операций "drag-and-drop"