МЕНЮ И АКСЕЛЕРАТОРЫ
ПОДКЛЮЧЕНИЕ МЕНЮ К ОКНУ
В предыдущей главе мы написали и исследовали программу, создающую окно. В этой главе мы должны научиться подключать меню к нашему окну и, естественно, научиться реагировать на сообщения, исходящие от меню. Мы узнаем о том, какие типы меню и элементов меню бывают, изучим различные способы построения меню.
Любой, кто хоть немного работал в Windows, знает, что меню распо лагаются сразу под заголовком окна и позволяют пользователю осуществить выбор возможностей, предоставляемых программой. Помимо этого, существуют также всплывающие меню, которые могут появляться в любой точке экрана. Обычно их содержание зависит от того, на каком окне щелкнули клавишей мыши. Их изучение начнем с характеристик меню.
Давайте представим себе главное меню программы как древовидную структуру. Основная часть - корень нашего дерева - это непосредственно главное меню. Само по себе оно представляет только структуру в памяти, не отображается на экране, не содержит ни одного элемента, но хранит указатель на список структур, описывающих подключаемые к нему элементы и всплывающие (popup) меню. Если читатель вспомнит свой опыт работы с Windows, он согласитсяс тем, что в окне в качестве основного меню отображается именно набор рорир-меню. В свою очередь, рорир-меню должно указать на список структур очередного, более низкого уровня и т. д. Конечные элементы меню никаких указателей на списки не имеют, но хранят некий идентификатор действия (назовем его идентификатором элемента меню), которое должна произвести программа при выборе данного элемента меню. Используя эти структуры, мы можем построить меню практически любой глубины и сложности.
Эта многоуровневая древовидная структура описывается в файле ресурсов. Описание меню имеет вид:
MenuName MENU [параметры] // это - главное меню
Описание всех рорир-меню и элементов меню второго уровня
В данном случае MenuName - это имя создаваемого нами меню. Слово MENU обозначает начало определения меню. Параметры мы пока, до изучения работы с памятью, расматривать не будем.
В Win32 API для описания меню существуют два ключевых слова. Первое - POPUP - специфицирует всплывающее меню. Второе -MENUITEM - описывает обычный элемент меню.
Всплывающие меню описывается следующим образом:
POPUP «Имя» [,параметры] // <- описание рорир-меню
I i
Описание всех рорир-меню и элементов очередного уровня
У конечного элемента меню в его описании есть еще одна характеристика - тот самый идентификатор действия:
MENUITEM «Имя», MenuID [.параметры]
В обоих случаях «Имя» - это тот текст, который будет выведен на экран при отображении меню (обратите внимание - при описании главного меню выводимого на экран текста нет!). В том случае, когда вместо имени окна записано слово SEPARATOR (без кавычек!), на ме сте элемента меню появляется горизонтальная линия. Обычно эти горизонтальные линии (сепараторы или разделители) используются для разделения элементов подменю на логические группы (логика определяется только программистом и никаких особенностей не содержит).
Если в имени меню встречается символ «&», то следующий за ампер-сандом символ на экране будет подчеркнут одинарной чертой. Этот элемент меню можно будет вызывать с клавиатуры посредством одновременного нажатия клавиши Alt и подчеркнутого символа.
Таблица 14. Параметры, описывающие элемент меню в файле ресурсов
CHECKED
ENABLED DISABLED GRAYED
MENURREAK
MENUBARBREAK
Рядом с именем элемента может отображаться небольшой значок, говорящий о том, что соответствующий флаг установлен
Элемент меню доступен
Элемент меню недоступен, но отображается как обычный Элемент меню недоступен и отображается серым цветом Горизонтальные меню размещают следующие элементы в новой строке, а вертикальные - в новом столбце То же, что и предыдущее, но в случае вертикального меню столбцы разделяются вертикальной линией
MenuID - идентификатор действия. Он может быть передан функции окна, содержащего меню. Значение идентификатора определяется пользователем. Функция окна в зависимости от полученного MenuID производит определенные действия.
Параметры же описывают способ появления элемента на экране. Возможные значения параметров приведены в табл. 14.
Попробуем создать описание небольшого меню. Горизонтальное меню (menubar) позволит выбирать подменю «File», «Examples» и конечный элемент «Help». Подменю «File» будет содержать элементы «Open » и «Exit», разделенные горизонтальной линией, а подменю «Examples» -несколько конечных элементов.
Ниже приведен текст скрипта для этого меню:
MyMenu MENU
{
POPUP "&File"
{
MENUITEM "&Open", 101
MENUITEM SEPARATOR MENUITEM "E&xit", 102 }
POPUP "&Examples"
{ POPUP "Example 1"
{
MENUITEM "1&Г, 103 MENUITEM "1&2", 104
} POPUP "Example &2"
{
MENUITEM "2&1", 105 MENUITEM "2&2", 106
MENUITEM "&Help", 111 }
Следует обратить внимание на то, что идентификаторы действия есть только у MENUITEM'ов. Popup-меню идентификаторов не содержат.
Теперь необходимо сделать так, чтобы меню стало доступным программе. В интегрированной среде это делается следующим образом:
к проекту добавляется файл ресурсов (желательно, чтобы имя файла ресурсов совпадало с именем программы);
в текст программы вносится изменение - при определении класса окна полю IpszMenuNaine структуры типа WNDCLASS присваивается указатель на строку, содержащую имя меню. В данном случае WndClass. IpszMenuNaine = «MyMenu»;
производится перекомпиляция проекта.
Если читатель работает не в интегрированной среде, то ему необходимо до момента линкования откомпилировать ресурсы, а затем с помощью линкера присоединить их к исполняемому файлу. Попробуйте произвести эти действия с тем проектом, в котором вы создавали нашу первую программу. Если вы все сделали правильно, то у окна должно появиться меню, с которым можно немного поиграть. Попробуйте поэкспериментировать с описанием меню в файле ресурсов и видоизменить и непосредственно меню, и внешний вид popup-меню и элементов меню.
Таким образом, с помощью добавления к программе меню мы определили функциональность нашей программы. Конечно, тот пример, который здесь приведен, предназначен только для того, чтобы продемонстрировать возможности по управлению меню с помощью ресурсов. Более того, из сказанного можно сделать вывод, что возможности Win32 по управлению меню с помощью ресурсов достаточно скудны. Да, это так. Существует еще масса функций, позволяющих манипулировать меню. Мы приступим к их рассмотрению после того, как научимся реагировать на манипуляции, производимые с меню.
Реакция окна на сообщения от меню
Как уже было сказано выше, элементы в меню могут быть обычными, запрещенными и «серыми». Для пользователя обычные и запрещенные элементы выглядят одинаково, а текст в «серых» элементах напечатан серым шрифтом. Но только обычные элементы позволяют пользователю произвести выбор. Запрещенные и «серые» элементы меню могут быть только подсвечены, но с их помощью произвести выбор нельзя. Кроме этого, существуют отмечаемые элементы меню. В них слева от текста может находиться какой-либо значок. Если значок есть, то считают, что флажок, определяемый этим элементом, установлен. Если флажок сброшен, то значок отсутствует.
С другой стороны, в элементе меню может находиться как текст, так и картинка (bitmap). С точки зрения пользователя никакой разницы в применении меню с текстом или с картинкой нет.
Перед тем, как начать серьезный разговор о меню, напомню, что основному меню и всем всплывающим меню Windows присваивает хэндлы (другими словами, все они являются структурами в памяти). Этот факт в дальнейшем будет играть важную роль.
Давайте попробуем поговорить о меню с точки зрения сообщений. При смене подсвеченного элемента меню (если, к примеру, пользователь «пробегает» по элементам меню с помощью клавиш со стрелками вверх и вниз) в оконную процедуру посылается сообщение WM_MENUSELECT. Это сообщение посылают все элементы меню. Когда же пользователь производит выбор (нажимает клавишу «Enter», к примеру), сообщение WM_COMMAND оконной процедуре посылают только обычные элементы меню. Запрещенные и «серые» элементы меню в этом случае никаких сообщений не посылают. В элементах wParam и lParam посылаемых сообщений хранится информация, достаточная для того, чтобы программа смогла определить, какие действия ей необходимо выполнить случае выбора пользователем того или иного элемента меню.
Вспомним, что помимо обычного меню у окна в большинстве случаев есть еще и системное меню. Сказанное относится и к системному меню. Отличие между обычным меню и системным состоит в том, что оконной процедуре посылаются сообщения WM SYSMENUSELECT и WM_SYSCOMMAND. Кроме этого, сообщения WM_SYSCOMMAND оконная процедура получает и в случае нажатия кнопок минимизации, максимизации и закрытия окна, которые находятся не в системном меню, а в правом углу заголовка окна.
Рассмотрим параметры сообщения WMSYSMENUSELECT более подробно. В младшем слове wParam оконная процедура получает сведения о том, какой элемент стал подсвеченным. Если учесть, что макросы LOWORD() и HIWORDO выделяют соответственно младшее и старшее слово 32-битного аргумента, и назвать источник сообщения ultem, то можно записать:
ultem = (UINT) LOWORD(wParam);
В зависимости от обстоятельств смысл ultem различается:
если подсвеченный элемент является конечным и не влечет за собой вызов popup-меню, то ultem содержит идентификатор элемента меню;
если подсвеченный элемент при выборе влечет за собой вызов popup-меню, то ultem содержит номер (индекс) этого элемента в том меню, в котором оно находится;
В старшем слове wParam содержатся характеристики подсвеченного элемента. Аналогично предыдущему,
fuFlags - (UINT) HIWORD(wParam);
Возможные значения fuFlags приведены в табл. 15.
Таблица 15. Характеристики подсвеченного элемента меню
Флаг |
Знамение |
Описание |
MFJ3ITMAP |
Ox00000004L |
Вместо строки в качестве элемента меню |
|
|
применяется bitmap |
MF CHECKED |
OxOOOOOOOSL |
Элемент отмечаемый (со «значком») |
MF DISABLED |
Ox00000002L |
Элемент запрещен |
MF_GRAYED |
0x0000000 IE |
Элемент запрещен и отображается серым |
|
|
цветом |
MF HILITE |
OxOOOOOOSOL |
Элемент подсвечен |
MF MOUSESELECT |
OxOOOOSOOOL |
Элемент выбран мышью |
MF_OWNERDRAW |
0x00000 100L |
За прорисовку элемента отвечает не система, |
|
|
а программа |
MF_POPUP |
0x000000 10E |
Элемент вызывает появление рорчр-меню |
|
|
более низкого уровня |
MF SYSMENU |
Ox00002000L |
Элемент из системного меню |
IParam содержит в себе хэндл того меню, которому принадлежит подсвеченный элемент. Обозначив хэндл меню как hMenu, получим:
hMenu = (HMENU) IParam;
Теперь пришла очередь рассмотрения сообщения WM_COMMAND. Как и в случае с \VM_SELECTMENU, младшее слово wParam содержит сведения об источнике сообщения. Так как сообщение WM_COMMAND посылается только конечными элементами меню, то в младшем слове wParam
содержится идентификатор выбранного элемента меню. На языке С
wID = LOWORD(wParam);
Старшее слово wParam указывает, от какого управляющего элемента пришло сообщение. Если сообщение пришло от меню, то это слово равно нулю, т. е.
wNotifyCodc = HIWORD(wParam) = 0;
IParam в случае сообщения от меню ВСЕГДА равно NULL!
Теперь мы знаем вполне достаточно, чтобы написать программу, реагирующую на манипуляции с меню. Но я хотел бы, чтобы читатель набрался терпения и изучил еще одну тему. Ранее мы пришли к выводу о том, что возможности языка управления ресурсами достаточно скудны. Тем не менее, в Win32 существует множество функций, позволяющих манипулировать меню. Остановимся на этих функциях и выясним, каким образом можно реализовать меню без обращения к ресурсам.
Меню без использования ресурсов
Перед тем, как начать рассмотрение функций, предназначенных для работы с меню, я хотел бы сказать, что являюсь сторонником комбинированного использования меню, определенного в виде ресурса, и функций Win32. Решение об использовании того или иного способа должен принимать программист в соответствии с задачами, которые должна решать разрабатываемая программа. Если в примере я придерживаюсь какого-то одного способа, то читатель должен понимать, что пример - это всего-навсего демонстрация возможностей Win32, а не призыв всегда и везде делать именно так, как сделано в предлагаемом примере. Еще раз повторю - программист свободен в выборе применяемых технологий.
Как было сказано выше, меню имеет строгую древовидную структуру, которая начинается с меню первого уровня (оно обычно называется главным меню программы или тепиЬаг'ом и располагается сразу под заголовком окна). К этому меню первого уровня могут быть присоединены как конечные элементы меню, так и элементы, выбор которых приводит к появлению так называемых всплывающих (popup) меню, к которым, в свою очередь, присоединяются элементы очередного уровня и т. д. Перед началом создания меню вся его структура должна быть тщательно продумана. Неплохо, если бы программист имел перед глазами графическое представление этого меню. Если все предварительные вопросы решены, то мы готовы приступить к созданию меню.
Итак, для создания меню необходимо выполнить следующие действия:
выбрать подменю самого низкого уровня, которые содержат только конечные элементы меню, и создать их с помощью функций CreateMenuQ или CreatePopupMenuQ в зависимости от потребностей. Эти функции возвращают хэндл созданного меню. Меню создается пустым;
посредством функции AppendMenuQ добавляем в них требуемые элементы;
создаем меню следующего, более высокого уровня, и добавляем в них требуемые элементы и меню, созданные нами на предыдущем шаге;
повторяем эти шаги до тех пор, пока создание всех подменю не будет закончено;
создаем главное меню программы посредством использования функции CreateMenuQ;
присоединяем созданные подменю самого высокого уровня к главному меню программы с помощью функции AppendMenuQ;
присоединяем меню к окну посредством использования функции SetMenuQ;
прорисовываем меню с помощью функции DrawMenuBarQ. Если в ходе программы сложилась такая ситуация, что меню оказалось не присоединенным к окну, перед выходом из программы обязательно уничтожаем его, вызывая функцию DestroyMenuQ (присоединенное к окну меню уничтожается автоматически при уничтожении окна).
Для того чтобы проиллюстрировать сказанное, давайте разберем небольшую программу. Я постарался написать эту программу так, чтобы в ней имелись основные типы элементов меню, и была бы возможность обработать выдаваемые меню сообщения. Чтобы придать программе функциональность, информация о получении сообщения будет выдаваться в строку состояния - небольшую область внизу окна (возможности строки состояния мы будем изучать позже). При запуске у окна возникает меню. Внизу экрана появляется строка состояния, в которой будет отображаться информация о выбранном элементе меню. Основное меню окна состоит из двух всплывающих меню, «File» и «Help», и элемента меню, в котором вместо строки отображается bitmap. В первом всплывающем меню находятся два элемента, «Enable exit» и «Exit», во втором -один элемент, «About», который остается запрещенным в течение всего периода существования окна. Кроме этого, элемент «Exit» при запуске объявляется «серым», т. е. из программы можно выйти только через системное меню. Однако в случае выбора элемента «Enable exit» «Exit» становится обычным, а вместо «Enable exit» возникает «Disable exit». При выборе элемента с bitmap'ом отображается окно сообщений с текстом о том, что выбран именно этот элемент. На этом возможности программы исчерпываются.
#include <windows.h>
#include <commctrl.h>
const IDM_Enable_Disable = 0;
const IDM_Exit = i;
const IDM_About = 2;
const lDP_File = 3;
const IDP_Help = 4;
char* pMcssages[] {
"Enable or disable exit", "Exit from the program", "About this program", "File operations", "Help operations", "Menu example", "System menu"
LRESULT CALLBACK MenuDemoWndProc ( HWND, UINT, UINT, LONG );
HWND hStatusWindow;
UINT wld;
HMENU hMenu,hFileMenu,hHe!pMenu;
HINSTANCE hlnst;
int APIENTRY WinMain ( HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )
HWND hWnd ; WNDCLASS WndClass ; MSG Msg;
hlnst = hinstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.lpfiiWndProc = (WNDPROC) MenuDemoWndProc;
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 J3RUSH);
WndClass. IpszMenuName = NULL;
WndClass. IpszClassName = "MenuExample";
if ( !RegisterCiass(&WndClass) )
{
MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;
hWnd = CreateWindow("MenuExample", "Program No 2",
WS_O VERLAPPEDWIN DOW,
CWJJSEDEFAULT,
CWJJSEDEFAULT,
CWJJSEDEFAULT,
CWJJSEDEFAULT,
NULL, NULL,
hlnstance.NULL); if(!hWnd)
{ MessageBox(NULL, "Cannot create window", "Error", MB_OK);
return 0;
InitCommonConlrols();
hStatusWindow = CreateStatusWindow(WS_CHILD | WSJVISIBLE,
if(!hStatusWindow)
"Menu sample", hWnd,w!d);
MessageBox(NULL, "Cannot create status window", "Error", MB_OK); return 0;
/* Try to create menu */
AppendMenu( (hFileMenu=CreatePopupMenu()), MF_ENABLED, MF_STRING,
IDM_Enable_Disable, "&Enable exit");
AppendMenu(hFileMenu, MF_GRAYED MF_STRING, IDM_Exit,"E&xil"); AppendMenu((hHelpMenu=CreatePopupMenu()),MF_DISABLEDMF_STRING,
IDM_About, "&About"); hMenu = CreateMenu(); AppendMenufliMenu, MFJZNABLED | MFJ>OPUP, (UINT) hFileMenu,
AppendMcnu(hMenu, MF_ENABLED | MFJPOPUP, (UINT) hHelpMenu,
"&Help");
SetMenufhWnd, hMcnu); /* Show our window */
ShowWindow(hWnd, nCmdShow);
UpdatcWindow(hWnd);
DrawMenuBar(hWnd);
/* Beginning of messages cycle */
while(GelMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg);
DispatchMessage(&Msg); i t
return Msg.wParam;
LRESULT CALLBACK MenuDemoWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam ) {
RECT Reel; static UINT nFlag - MFJENABLED;
char* pContentf]
i i
"Enable exit",
"Disable exit" };
static UINT nlndex = 0; static НВ1ТМЛР hBitmap; int nDimcnsion;
switch(Message) { caseWM CREATE:
nDimension = GetSystemMetrics(SM_CYMENU);
hBitmap = Loadlmage(hlnst, "msdogs.bmp", IMAGE_BITMAP,
nDimension * 2, nDimension, LR_LOADFROMFILE); AppendMenu(GetMenu(hWnd), MF^BITMAP, IDM_Bitmap, hBitmap); break;
case WM_COMMAND: switch (wParam)
{ case IDM_Enable_Disable:
EnableMenuItem(hFileMenu, IDM_Exit, MF_BYCOMMAND | nFIag);
nFlag = ( nFIag = MF_ENABLED ) ? MF_GRAYED : MFJ-NABLED;
nlndex = ( nlndex == 0) ? I : 0;
ModifyMenu(hFileMenu, IDM_Enable_Disable, MF_BYCOMMAND | MF_STRING, IDM_EnabIe_Disable, pContent[ntndex]);
break; case IDM_Exit:
SendMessage(hWnd, WM_CLOSE, NULL, NULL);
break;
} case WM_SIZE:
SendMessage(hStatusWindow, WM_SIZE, wParam, IParam);
GetClientRect(hWnd, &Rect);
return 0;
case WM_MENUSELECT: // Selection is losted
iff ((UINT) HIWORD(wParam) == Oxffff) & ((HMENU) IParam = 0))
{ SendMessage(hStatusWindow ,SB_SETTEXT, (WPARAM) 0,
(LPARAM) PMessages[5]); return 0;
I if ((UINT) HIWORD (wParam) & MF_SYSMENU)
{ SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,
(LPARAM) pMessages[6]); return 0;
} if ((UINT) HIWORD(wParam) & MF_POPUP)
{ SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,
(LPARAM) pMessages[3 + LOWORD(wParam)]); return 0;
} SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0, (LPARAM)
pMessages[LOWORD(wParam)]); return 0;
case WM_DESTROY: DeleteObject(hBitmap); PostQuitMessage(O); return 0;
return DefWindowProc(hWnd,Message,wParam, IParam);
Листинг № З. Программа, демонстрирующая возможности по манипулированию меню.
Вид окна, создаваемого программой, показан на рис. 7.
Как и в случае нашей первой программы для Windows, давайте рассмотрим эту программу. Естественно, включаем файлы заголовков Win32. Включение файла «commctrl.h» обусловлено вызовом функций для работы со строкой состояния, заголовки которых находятся в этом файле. Далее идут многочисленные описания и определения, объяснять которые я не буду, они сразу же становятся понятными при разборе программы. В функции WinMain() все ясно до момента вызова функции InitCommonControls(). Но и здесь нет ничего страшного. Эта функция вызывается всегда перед использованием библиотеки общих элементов управления, к которым относится и строка состояния. К меню и нашей задаче эта функция имеет весьма далекое отношение. Интерес вызывает фрагмент, который начинается после строки комментария /* Try to create menu */.
[Menu example
ppic. 7. Окно с меню, содержащим bitmap
Таблица 16. Битовые флаги, определяющие поведение и вид элемента меню
Флаг
Назначение
MF_BITMAP
MF_CHECKED
MFJDISABLED
MF^ENABLED
MF_GRAYEU
MF_MENUBARBREAK
MF_MENUBREAK
MF_OWNERDRAW MF_POPUP
MF^SEPARATOR
MF_STRING
MF UNCHECKED
Вместо строки символов в качестве элемента меню используется изображение (bitmap) Отмечаемый элемент меню Запрещенный, но не «серый» элемент меню Разрешенный элемент меню Запрещенный «серый» элемент меню То же, что и следующее, но вертикальные столбцы отделяются вертикальной чертой
Очередной элемент меню размещают в новой строке (menubar) или в новом столбце За прорисовку элеме!гга отвечает программа, а не Windows Выбор элемента влечет за собой появление меню более низкого уровня Горизонтальная черта
В качестве элемента меню используется строка символов Неотмечаемый элемент меню
Как уже было сказано, сначала мы создаем меню с помощью вызова функции CreatePopupMenuQ, которая возвращает нам хэндл созданного меню hFileMenu. В это меню мы с помощью функции AppendMenuQ добавляем два элемента, «Enable exit» и «Exit». Функция AppendMenuQ заслуживает того, чтобы поговорить о ней подробнее.
При вызове функции AppendMenuQ она получает четыре аргумента. Аргумент первый - хэндл того меню, в которое добавляется элемент. Ничего сложного или интересного в этом вызове нет. Второй аргумент -комбинация битовых флагов, определяющих внешний вид и поведение добавляемого элемента меню. Перечень и назначение флагов приведены в табл. 16.
Заметим, что некоторые флаги не могут быть установлены одновременно. Например, MF_BITMAP, MF_STRING и MFJPOPUP, MF_ENABLED, MF_DISABLED и MF GRAYED.
Интерпретация третьего параметра зависит от того, установлен ли во втором параметре флаг MF POPUP, т. е. является ли добавляемый элемент меню конечным элементом или влечет за собой вызов очередного меню. В первом случае - конечный элемент - параметр содержит идентификатор этого элемента. Если же добавляется меню, то параметр содержит хэндл добавляемого меню.
И последний параметр тоже интерпретируется в зависимости от установленных флагов. Установлен флаг MF_BITMAP - параметр содержит хэндл bitmap'а. Установлен флаг MF_STRING - параметр содержит указатель на строку символов. Установлен MF_OWNERDRAW - параметр содержит информацию, используемую программой при прорисовке элемента.
Разобравшись с функцией AppendMenuQ, мы можем не останавливаться на последующих ее вызовах и остановиться на вызове функции SetMenuQ.
Что означает «создать меню»? Это, как и в случае создания окна, означает всего лишь создание и последующее заполнение некоторых структур в памяти. После создания меню не принадлежит никакому окну и до поры до времени бесполезно блуждает в глубинах памяти. Для того чтобы меню могло выполнять свои функции, оно должно быть «закреплено» за одним из окон. Функция SetMenuQ и привязывает меню к конкретному окну. Аргументы этой функции очевидны - хэндл закрепляемого меню и хэндл окна, к которому меню прикрепляется. После вызова этой функции указатель на меню включается в структуру окна и может нормально использоваться.
После отображения и прорисовки окна вызывается функция DrawMenuBarQ для прорисовки меню. И у этой функции очевидно наличие одного аргумента - хэндла окна, которому принадлежит прорисовываемое меню.
К этому моменту мы произвели все действия, требующиеся для создания меню. Далее программа запускает цикл обработки сообщений и начинает посылать сообщения функции окна. Перейдем к рассмотрению оконной функции. Перед этим я прошу читателя не пугаться, увидев обращение к функции SendMessageQ. Она только посылает окну (хэндл окна-адресата - первый аргумент) сообщение (второй аргумент) с заданными wParam (третий аргумент) и IParam (четвертый аргумент). В данном случае посылаются сообщения строке состояния для того, чтобы в ней отобразился текст, на который указывает IParam.
При этом оконная функция самостоятельно обрабатывает только четыре сообщения - WM_COMMAND, WMJVlENUSELECT, WM_SIZE и WMJ3ESTROY. WMJ5ESTROY мы уже рассматривали. WM_SIZE в этом примере используется для того, чтобы при изменении размеров окна строка состояния могла бы нормально перерисоваться, т. е. сейчас это сообщение нас не интересует. В настоящий момент интерес представляют только сообщения WM_MENUSELECT и WM COMMAND.
Начнем с рассмотрения WM_MENUSELECT. У этого сообщения есть одна особенность. Если поле fuFlags, т. е. старшее слово wParam, равно Oxffff и при этом поле hMenu (т. е. IParam) равно NULE, то это означает, что меню закрылось (не стало выделенных элементов), так как пользователь нажал Escape или щелкнул мышкой где-нибудь вне меню. В этом случае в строке состояния появляется текст, описывающий назначение программы в целом («Menu example»). При получении сообщения от системного меню в строке состояния возникает «System menu». Во всех остальных случаях текст в строке состояния описывает назначение подсвеченного элемента меню. Непосредственно ход обработки этого сообщения ясен из листинга и особых пояснений не требует.
Но обработка WM_MENUSELECT - это всего лишь прелюдия к настоящей работе, которая происходит тогда, когда пользователь выбирает конечный элемент меню посредством нажатия клавиши «Enter» или щелкнет левой клавишей мышки на элементе. В этом случае оконная процедура получает сообщение WM_COMMAND. Для того чтобы определить, как реагировать на сообщение, мы должны проанализировать младшее слово wParam, которое хранит идентификатор элемента меню, и в зависимости от его значения предпринимать те или иные действия. В данном случае оконная функция может получать \VM_COMMAND только с двумя идентификаторами - IDMJEnable^Disable и IDMJ3xit. При получении последнего мы осуществляем выход из программы. При обработке первого я демонстрирую использование двух функций -EnableMenuItemQ и ModifyMenuQ.
При получении WM_COMMAND, младшее слово wParam которого равно IDM_Enable_Disable, производятся следующие действия:
с помощью функции EnableMenuItemQ запрещается или делается доступным элемент «Exit»;
с помощью функции ModifyMenuQ изменяется текст элемента, выбор которого приводит к состоянию элемента «Exit».
Эти функции достаточно показательны и их разбор поможет читателю еще глубже понять функции, работающие с меню.
Функция EnableMenuItemQ позволяет программисту изменять состояние элемента (разрешенный, запрещенный, «серый») меню по своему усмотрению. При вызове функции, ей передаются три аргумента. Первый аргумент - хэндл того меню, которому принадлежит элемент. В нашем случае меняется состояние элемента, находящегося в меню «File», хэндл которого hFileMenu. Второй аргумент определяет тот элемент, состояние которого изменяется, но каким способом происходит определение, а также в какое состояние переходит элемент, зависит от третьего аргумента, который в очередной раз представляет комбинацию битовых флагов.
Возможные флаги приведены в табл. 17.
Таблица 17. Флаги, используемые при вызове функции EnableMenuItem()
Флаг
Значение
MF_BYCOMMAND MF_BYPOSITION
MF_ENABLF.D MF_DISABLED MF GRAYED
Изменяемый элемент меню определяется по его идентификатору
Изменяемый элемент меню определяется по его номеру (индексу) в меню
После вызова функции элемент становится разрешенным После вызова функции элемент становится запрещенным После вызова функции элемент становится «серым»
После изменения состояния элемента «Exit» с разрешенного на серое и наоборот, необходимо изменить текст в элементе, от которого зависит это состояние. Это изменение производится посредством вызова функции ModifyMenu(), которой передаются пять аргументов. Первые два аргумента функционально подобны аргументам EnableMenuItemQ, т. е. первый аргумент - хэндл меню, которому принадлежит изменяемый элемент, а второй аргумент определяет непосредственно изменяемый элемент. Можно было бы сказать, что и третий аргумент функционально подобен, но он представляет собой комбинацию флагов, во-первых, определяющих элемент, подлежащий изменению (MF_BYCOMMAND или MF_BYPOSITION), а во-вторых, определяющих состояние элемента после изменения (перечень этих флагов в точности соответствует приведенному в табл. 16). Четвертый аргумент указывает или идентификатор измененного элемента, или хэндл нового меню (если, конечно, в третьем аргументе установлен флаг MF_POPUP). И наконец, последний аргумент - новое содержание измененного элемента. В зависимости от того, какой флаг установлен в третьем аргументе (MF_BITMAP, MF^STRING или MF^OWNERDRAW), последний аргумент содержит хэндл bitmap'а, указатель на строку или информацию, используемую при прорисовке элемента.
Таким образом, с помощью только функций Win32 мы создали меню и изменили его состояние.
Надеюсь, что при чтении этого раздела и разборе приведенного примера читатель понял технические приемы работы с меню, и теперь может применять полученные знания при разработке собственных программ. Описанными функциями отнюдь не исчерпываются возможности Win32 по управлению меню. Например, в меню можно не добавлять, а вставлять элементы посредством функции InsertMenuQ, функция DeleteMenuQ удаляет элемент из меню, информацию о меню можно получить с помощью функций GetMenuQ, GetMenuStringQ, GetMenuIternCountQ и других.
В рамках этой книги нет возможности описать все функции работы с меню. Надеюсь, что читатель, получив начальные знания, проявит любознательность и сам найдет в системе помощи Win32 сведения о функциях, работающих с меню. Тем не менее, в примере, который приводится в разделе, посвященном созданию диалоговых окон, можно будет найти еще две функции, работающие с меню.
Изучение работы с меню на этом не заканчивается. Нераскрытым остался еще один вопрос - подключение акселераторов меню, который будет рассмотрен ниже.
АКСЕЛЕРАТОРЫ
Итак, мы научились создавать и манипулировать элементами меню. Но, к большому разочарованию тех, кто привык работать без мышки, у наших меню есть один серьезный недостаток. Выбор элементов мы можем производить только последовательно, входя в главное меню, подменю, подменю... и так до тех пор, пока не дойдем до нужного элемента. А у многих программ есть возможность обращаться к элементам меню напрямую посредством использования некоторых комбинаций клавиш. Возникает закономерный вопрос - как сделать так, чтобы и в наших программах была такая возможность?
Комбинации клавиш, которые при нажатии автоматически выбирают соответствующий им элемент меню (даже в тех случаях, когда оно не активно и не отображается), называются акселераторами. Это название (в переводе с английского акселератор означает ускоритель) выбрано достаточно удачно, ибо в тех случаях, когда пользователь запомнил их и привык к их использованию, ввод команд осуществляется намного быстрее, чем активизация меню и выбор этих команд.
Акселераторы являются одним из типов ресурсов, т. е. для того, чтобы использовать акселераторы, нам необходимо в файле ресурсов создать таблицу акселераторов. Она имеет следующий формат:
TableName ACCELERATORS
{ Keyl, MenuIDl [,тип] [,параметр]
Keyn, MenuIDn [,тип] [.параметр]
TableName - это определяемое пользователем имя таблицы акселераторов. Key определяет клавишу или комбинацию клавиш, при нажатии
которой происходит ввод команды. Тип определяет, является ли клавиша стандартной (это значение применяется по умолчанию) или виртуальной. Параметр может принимать одно из следующих значений: NOINVERT, ALT, CONTROL и SHIFT. Обычно при использовании акселераторных комбинаций меню отображается так, словно мы выбрали команду обычным способом. NOINVERT означает, что при использовании акселератора внешне меню на ввод команды никак не отреагирует, даже если будет активно и отображено. Присутствие ALT указывает, что для получения акселераторной комбинации одновременно с указанной клавишей необходимо нажать клавишу Alt. CONTROL говорит о том, что одновременно с клавишей должна нажиматься клавиша Control, a SHIFT требует одновременного с клавишей нажатия Shift.
В качестве клавиши можно указать либо ее символ в кавычках, либо код ASCII-символа, либо код виртуальной клавиши, определенной в файлах заголовков. При использовании ASCII-кода в качестве типа должно быть указано ASCII, а в случае применения виртуальной клавиши тип должен быть VIRTKEY. Виртуальная клавиша - это системно-независимый код, определенный для основного набора служебных клавиш. Этот набор включает клавиши F1-F12, стрелки и т. д. Коды виртуальных клавиш определены в заголовочных файлах. Все их идентификаторы начинаются с букв VK (Virtual Key). Разница между виртуальной клавишей и ASCII-символом с точки зрения пользователя состоит в том, что виртуальные клавиши не различают прописных и строчных букв, в отличие от ASCII-символов.
При определении акселераторов можно пойти на небольшую хитрость. Представим себе, что в качестве акселератора мы указали заглавную букву и, скажем, ALT. В этом случае нам придется одновременно нажимать три клавиши - букву, клавишу SHIFT (необходимо сделать символ заглавным!) и клавишу Alt. Таким образом, при указании в качестве основной клавиши заглавной буквы, можно определять трехкла-вишные акселераторы. Кстати, если мы хотим, чтобы для вызова команды использовалась клавиша Control, то можно символ в кавычках предварить знаком л.
Примерами акселераторов в файле ресурсов могут служить следующие записи:
«a», IDM_TheJFirst_Item, ALT // определяется комбинация Alt-a
«A», IDM_The_Second_Item, ALT // определяется комбинация
Shift-Alt-a
Таблица акселераторов должна быть загружена в память после создания окна до начала работы с меню. Поэтому желательно вызов функции LoadAccelerator(), осуществляющей загрузку таблицы акселераторов, вставить в текст программы сразу же после создания окна.
Функция LoadAcceleratorQ при вызове должна получить два аргумента. Первый аргумент - это хэндл экземпляра программы, а второй - имя таблицы акселераторов. В результате вызова мы получаем хэндл нового объекта - таблицы акселераторов в памяти.
Но и это еще не все. Если рассуждать логически, то каждое нажатие акселераторной комбинации должно генерировать сообщение WM_COMMAND. Для этого акселераторы и создавались. Поэтому, даже после загрузки таблицы в память программа не сможет на них правильно реагировать, если мы не будем использовать функцию TranstateAccelerator(), которая преобразует сообщения от клавиатуры в сообщения WM_COMMAND. Описание этой функции можно найти в заголовочном файле winuser.h:
WINUSERAPI int WIN API TranslateAcceleratorA(HWND hWnd,
HACCEL hAccTable, LPMSG IpMsg); WINUSERAPI int WINAPI TranslateAcceleratorW(HWND hWnd,
HACCEL hAccTable, LPMSG IpMsg); «ifdefUNICODE
#define TranslateAccelerator TranslateAcceleralorW «else
#dcfine TranslateAccelcrator TranslateAccelcratorA «endif// (UNICODE
Аргументы этой функции в достаточной степени очевидны. Первый аргумент -хэндл окна, которому принадлежит меню с акселераторами, второй - хэндл таблицы акселераторов, с помощью которой производится генерация сообщения WM_COMMAND, третий - указатель на сообщение. TranslateAcceleratorQ возвращает Heir/левое значение, если нажата акселераторная комбинация и нуль в противном случае. Поэтому с учетом вызова этой функции цикл обработки сообщений должен выглядеть следующим образом:
while(GetMessagc(&Msg, NULL, О, О))
( i
if( !TranslaleAccclcrator(hWnd, hAccel, &Msg))
!
TranslateMessage(&Msg); DispatchMcssage(&Msg);
return Msg.wParam;
Итак, с созданием таблиц акселераторов мы разобрались. Дело за малым - рассмотреть небольшой пример. В данном случае я не стал изобретать велосипед, и сделал следующее:
в программе, взятой из предыдущего примера, создал меню не в программе, а в файле ресурсов;
определил в файле ресурсов акселераторные комбинации;
добавил в цикл сообщений обработку акселераторных комбинаций.
В результате получились файлы, которые приведены ниже:
«define IDM_Enable_Disable О «define IDMJExit 1 «define IDM_About 2 «define IDP_File 3 «define IDPJHelp 4
Листинг № 4. Файл определений:
#include «menu.h»
MyMenu MENU
POPUP "&File" {
MENUITEM "Enable exit\te", IDM_Enable_Disable, GRAYED
MENUITEM "E&xit", IDM_Exit
POPUP "&Help"
MENUITEM "About\ta", IDM About, DISABLED
MyMenu ACCELERATORS {
«x», IDM_Exit, ASCII «a», IDM_About, ASCII «e», IDM_Enable_Disable, ASCII «d», IDM_Enable_Disable, ASCII
Листинг № 5. Файл ресурсов:
«include <windows.h>
«include <commctrl.h>
«include "menu.h"
char* pMessages[] = {"Enable or disable exit", "Exit from the program", "About this program", "File operations", "Help operations", "Menu example", "System menu"};
long WINAPI HelloWorldWndProc ( FTWND, UINT, UINT. LONG );
HWND hStatusWindow;
UINT wld;
HMENU hMenu,hFileMenu,hHelpMenu;
HrNSTANCE hlnst;
int APIENTRY WinMain (HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )
!
HWND hWnd ;
WNDCLASS WndClass ;
MSG Msg;
HACCEL hAccel;
hlnst = hinstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style = CSJHREDRAW | CSJVREDRAW;
WndClass.lpfnWndProc = (WNDPROC) HelloWorldWndProc;
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 = "MyMenu";
WndClass.IpszClassName = "MenuExample";
if ( !RegisterClass(&WndClass)) !
MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0;
} hWnd = CreateWindowC'MenuExample", "Program No 2",
WS_OVERLAPPEDWINDOW,
CWJJSEDEFAULT,
CW_USEDEFAULT,
CWJJSEDEFAULT,
CWJJSEDEFAULT,
NULL, NULL,
hlnstance,NULL); if(!hWnd)
{
MessageBox(NULL,"Cannot create window","Error",MBJ3K); return 0;
InitCoinmonControlsQ;
hStatusWindow = CreateStatusWindow(WSJTHILD | WS_VISIBLE,
"Menu sample",
hWnd,w!d); if(!hStatusWindow)
{
MessageBox(NULL,"Cannot create status window","Error",MB JDK); return 0;
/* Load the accelerators table */
hAccel = LoadAccelerators(hInst,"MyAccelerators"); /* Show our window */
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
hFilcMcnu = GetSubMenu(GetMenu(hWnd),0);
/* Beginning of messages cycle */
while(GetMessage(&Msg, NULL, 0, 0)) {
if( !TranslateAccelerator(hWnd,hAccel,&Msg)) {
TranslateMessage(&Msg); DispatchMessage(&Msg);
return Msg.wParam;
long WINAPI HelloWorldWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam )
!
RECT Rect;
static UINT nFIag = MFJENABLED; char* pContent[]
>
t
"&EnabIe exit\te", "&Disable exit\td"
};
static UINT nlndcx = 0;
switch(Message) {
case WMJCOMMAND: switch (wParam)
)
case IDMJinable J3isable:
EnableMenuItem(hFileMenu, IDMJЈxit, MFJBYCOMMAND | nFIag); nFIag - ( nFIag -= MFJZNABLED )'.' MFJ3RAYED : MFJENABLED; nlndex = ( nlndex = 0) ? 1 : 0;
ModifyMcnu(hFileMenu, IDM_Enable_Disable, MF_BYCOMMAND | MF_STRING, IDM_Enable_Disable, pContentfnlndex]); break;
case IDM_Exit:
SendMessage(hWnd, WM_CLOSE, NULL, NULL); break;
} case WM_SIZE:
SendMessage(hSlatusWindow,WM SIZE.wParam,IParam);
GetClicnlRcct(hWnd,&Rect);
return 0;
case WM_MENUSELECT: // Selection is lostcd
if ( ((UINT) HIWORD(wParam) == Oxfffl) & ((HMENU) IParam == 0))
{ SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,
(LPARAM) pMessages[5]); return 0;
} if ((UINT) HIWORD(wParam) & MF_SYSMENU)
{ SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,
(LPARAM) pMessages[6]); return 0;
if ((UINT) HIWORD(wParam) & MF_POPUP)
(
ScndMcssage(hStatusWindow, SB^SETTEXT, (WPARAM) 0.
(LPARAM) pMessages[3 + LOWORD(wParam)]); return 0;
! SendMessage(hStatusWindow,SB_SETTEXT. (WPARAM) 0, (LPARAM)
pMcssages[LOWORD(wParam)]); return 0;
case WM_DESTROY: PostQuitMessage(O); return 0; } return DefWindowProc(hWnd,Message,wParam, IParam);
Листинг № 5. Программа, демонстрирующая возможности акселераторов меню.
Мне бы хотелось, чтобы читатель поэкспериментировал с этой программой, попробовал переопределить акселераторы, и вновь провел несколько экспериментов.
Завершим рассмотрение темы о меню ответом на вопрос о том, как можно создать акселераторы без использования ресурсов.
Таблица 18. Возможные значения флагов поля fVirt структуры типа ACCEL
Флаг
Значение
FALT FCONTROL
FNOFNVERT FSHIFT
FVIRTKEY
При нажатии акселераторной комбинации должна быть нажата
клавиша Alt
При нажатии акселераторной комбинации должна быть нажата
клавиша Control
Внешне меню не реагирует на нажатие акселераторной комбинации
При нажатии акселераторной комбинации должна быть нажата
клавиша Shift
Поле key определяет виртуальную клавишу. Если это поле не
установлено, то считается, что поле key содержим символ ASCII
Для создания таблицы акселераторов применяется функция CreateAcceleratorTableQ, которой в качестве аргументов передаются адрес массива структур типа ACCEL и число структур в этом массиве.
Назначение полей структуры ACCEL должно быть понятно читателю, внимательно прочитавшему текущий раздел. В файле winuser.h эта структура описана следующим образом:
typedef struct tagACCEL {
BYTE fVirt; /* Also called the flags field */
WORD key;
WORD cmd; ( ACCEL, «LPACCEL;
Если мы вспомним формат описания акселератора в файле ресурсов, то сразу можно догадаться о том, что поле cmd - это аналог поля Menuld, key соответствует Key, а значения поля fVirt являются комбинациями флагов (табл. 18), которые однозначно соответствуют полям Тип и Параметр.
И наконец, чтобы завершить тему об акселераторах, замечу, что при уничтожении окна автоматически из памяти удаляются только акселераторы, созданные с помощью функции LoadAccelerator(). В случае, если использовалась функция CreateAcceleratorTable(), программа должна сама заботиться об удалении таблицы из памяти. Для этого применяется функция DestroyAcceleratorTableQ, в качестве аргумента которой передается хэндл таблицы акселераторов.
Мы завершили рассмотрение темы, связанной с меню и акселераторами. К этому моменту читатель должен быть готов к тому, чтобы самостоятельно использовать ПОЧТИ все возможности по управлению меню,
предоставляемые Win32. За пределами нашего внимания остался один пункт. Он связан с созданием в памяти структуры типа MENUITEMTEMPLATE и использования ее для создания меню посредством вызова функции EoadMenuIndirect(). В книге Чарльза Петцольда есть одна фраза, которая меня не только развеселила, но и подвигла на изучение этого совершенно бесполезного (это моя личная точка зрения, но тогда-то я еще не знал этого!) вопроса. Вот эта фраза: «If you're brave, you can try using it yourself (Если ты смелый, ты можешь самостоятельно попробовать использовать ее (функцию LoadMenuIndirectQ).» Больше времени на разбор этой функции я терять не стану. Уважаемый читатель! Если вы не желаете прислушаться к моему совету, изучите, пожалуйста, третий способ создания меню самостоятельно.
Очередное ура! Мы прошли еще одну тему! Теперь мы умеем полностью определять порядок взаимодействия программы с пользователем через меню. Для того чтобы завершить раздел, посвященный непосредственному взаимодействию пользователя с программой, осталось всего ничего - начать и завершить тему о диалоговых окнах и обо всем, что с ними связано.
ДИАЛОГОВЫЕ ОКНА И ИХ ЭЛЕМЕНТЫ
В предыдущей главе мы разобрались с порядком создания меню и ак-селераторных комбинаций. Но любой работавший с Windows знает, что возможности программы, обеспечивающие взаимодействие с пользователем, отнюдь не ограничиваются рамками меню. Основным средством «общения» пользователя с программой являются диалоговые окна (их также называют диалогами). В этом разделе мы рассмотрим работу диалоговых окон и их взаимодействие не только с пользователем и программой, но и окнами более низкого уровня, называемыми элементами управления, которые выполняют большую часть черновой работы, незаметной не только пользователям, но и программистам.
Диалоговые окна можно классифицировать по двум критериям. Первый критерий - это модальность диалогового окна. Второй критерий, не всегда заметный, заслуживает особого внимания. Второй критерий фактически определяет, с одной стороны, возможности диалогового окна, а с другой стороны, ответственного за обработку сообщений посылаемых диалоговому окну. В подавляющем большинстве случаев обработку сообщений, адресованных диалоговому окну, производит функция диалогового окна (о ней речь впереди), но иногда диалоговое окно своей функции не имеет (она запрятана в «глубинах» системы) и всю обработку производит система. Возможности таких окон очень ограничены, в
основном они предназначены для выдачи сообщений пользователю. Об этом говорит даже их название - окна сообщений. Сейчас мы определим, что такое модальное и немодальное окно, рассмотрим процесс создания диалоговых модальных и немодальных диалоговых окон, остановимся на некоторых элементах управления диалоговыми окнами, а потом поговорим об окнах сообщений. Надеюсь, что даже после такого краткого экскурса в область диалоговых окон читатель сможет спокойно манипулировать окнами и элементами управления.
Модальные и немодальные диалоги
Диалоговые окна бывают модальными и немодальными.
Наиболее часто используются модальные окна. Эти окна не дают пользователю возможности работать с другими окнами, созданными приложением, породившим диалоговое окно, но разрешают переключаться на работу с другими приложениями. Для того чтобы пользователь мог продолжить работу с другими окнами своего приложения, необходимо завершить работу с диалоговым окном.
В особых случаях, представляющих угрозу системе и требующих немедленной реакции оператора, могут использоваться системные модальные окна. Эти окна не позволяют переключаться ни на какое другое окно. Естественно, что и применять системное модальное окно нужно с умом.
Немодальные диалоговые окна не требуют своего завершения для продолжения работы, и пользователь может во время работы с ними свободно переключаться на любое приложение.
РАБОТА С ДИАЛОГОВЫМИ ОКНАМИ
Диалоговое окно позволяет вводить и получать информацию, которую сложно или вовсе невозможно ввести через меню. Я уже говорил о том, что диалоговое окно имеет в своем составе некие элементы, окна более низкого уровня. Их называют элементами управления. Примером одного из таких элементов могу служить кнопки, которые, наверное, видел любой, хоть чуть-чуть поработавший с Windows. Так как без элементов управления диалоговые окна теряют всякий смысл, то рассмотрим, что такое
КНОПКИ, СПИСКИ И ПРОЧЕЕ...
Как уже было сказано, элементы управления - это ОКНА более низкого по отношению к диалоговому окну уровня. Предлагаю отметить то, что элементы управления никогда не могут использоваться как самостоятельные окна. Они всегда используются на фоне какого-то окна, которое
является для них родительским окном. Элементы управления, таким образом, всегда являются дочерними окнами, другими словами, у них всегда присутствует стиль WM_CHILD.
Как и любые другие окна, элементы управления могут получать и выдавать сообщения. Правда, это относится не ко всем элементам управления, но... Стоп! Давайте прервемся на секунду.
Мне бы хотелось обратить внимание читателя на один интересный момент. Для посылки сообщения обычно используют функции SendMessageQ и SendDlglteinMessageQ. Дело в том, что значение, которое возвращают эти функции, зависит только от того сообщения, которое они отправили. Таким образом, если вам необходимо узнать по возвращенному значению, что произошло в результате обработки того или иного сообщения, ищите описание возвращаемых значений не в описаниях функций, а в описаниях сообщений.
По умолчанию подавляющее большинство сообщений от элементов управления получает диалоговое окно, которому они принадлежат. Диалоговое окно должно каким-то образом их обрабатывать. Отсюда очередной вывод - у диалогового окна должна быть собственная оконная функция.
Каждому элементу управления присваивается идентификатор. При каком-либо воздействии на этот орган управления со стороны пользователя диалоговое окно получает сообщение, содержащее идентификаторы элемента и типа производимого пользователем действия. Диалоговая функция обрабатывает эти сообщения и выполняет соответствующие действия. Этот процесс происходит параллельно с обработкой сообщений в оконной функции. При этом нужно заметить, что в отличие от обычного окна, «нормальное» диалоговое окно Eie имеет своего цикла обработки сообщений. Цикл обработки сообщений запускается один раз при запуске программы.
Элементами управления могут быть кнопки (buttons), которые мы уже использовали в окнах сообщений, переключатели (check boxes), селекторы (radio buttons), списки (list boxes), комбинированные списки (combo boxes), линейки прокрутки (scroll bars) и статические элементы (statics). Все элементы в этом перечне относятся к категории базовых, и все они присутствовали и в Windows 3.x. На их основе Microsoft разработала серию новых элементов управления (common controls), которые позволили расширить возможности интерфейса с пользователем и улучшить внешний вид приложений. Мы рассмотрим как базовые, так и новые общие (как еще можно перевести на русский язык название «common controls»?) элементы управления.
100
Я уже упоминал, что основную часть работы диалогового окна выполняют элементы управления. Поэтому рассмотрим сначала вопрос о том, как может быть создано диалоговое окно, а потом на примерах -работу каждого типа элементов управления.
СОЗДАНИЕ ДИАЛОГОВОГО ОКНА
Диалоговое окно, как и меню, может быть создано несколькими способами: во-первых, с помощью описания его в файле ресурсов и, во-вторых, во время выполнения программы. Наиболее часто используется описание диалога в файле ресурсов. Лучше всего при создании диалога воспользоваться редактором ресурсов, с помощью которого может быть создан текстовый файл, содержащий описание диалогового окна. Ресурсы диалога в этом текстовом файле задаются оператором DIALOG, который имеет следующий формат:
DialogName DIALOG [DISCARDABLE] CAPTION «Заголовок окна» STYLE <Стили диалогового окна> FONT n, <имя шрифта>
X, Y, Width, Height
Описание элементов диалога
В данном случае DialogName - это имя диалогового окна. Опция DISCARDABLE станет совершенно ясной при рассмотрении вопроса об организации памяти в Windows. Параметры X и Y - это координаты верхнего левого угла диалогового окна, Width и Height - ширина и высота диалога. STYLE описывает стили окна. Здесь могут использоваться как стили, применяемые для описания обычных окон (об этих стилях мы говорили при создании первой программы для Windows), так и стили, применяемые только в диалоговых окнах. Эти новые стили приведены в табл. 19.
Приведенных выше сведений вполне достаточно, чтобы написать заготовку диалогового окна в файле ресурсов. Но какой смысл описывать диалоговое окно, если в нем нет ни одного из элементов управления? Ведь даже закрыть такое диалоговое окно (если в нем, конечно, нет системного меню) невозможно! Значит, нам необходимо срочно научиться описывать эти элементы !
Я уже упоминал о том, что в «недрах» Win32 есть масса предопределенных объектов. В частности, там находятся и некоторые предопределенные классы окон. К таким классам относятся кнопки (класс «button»), списки (класс «listbox»), комбинированные списки (класс «combobox»), окна редактирования (класс «edit»), полосы прокрутки класс «scrollbar»), статические элементы (класс «static»). У каждого класса есть свой определенный набор стилей, которые определяют внешний вид и поведение элементов управления, относящихся к данному классу.
Управление окном каждого класса, а также получение информации от него производится с помощью обмена управляющими сообщениями. О действиях пользователей с ними элементы управления оповещают свои родительские окна через нотификационные сообщения. Предлагаю читателю запомнить это, так как мы еще неоднократно вспомним о предопределенных сообщениях.
Приступим к изучению элементов управления. Вспомним, что мы уже неоднократно встречались с кнопками. Давайте и начнем с описания обычных кнопок (buttons).
Таблица 19. Стили диалоговых окон
Стиль |
Значение |
Эффект |
DS^ABSALIGN |
0x000 1L |
Положение диалогового окна исчисляется в |
|
|
экранных координатах |
DS_SYSMODAL |
Ox0002L |
Создается системное модальное диалоговое |
|
|
окно |
DSJDLOOK |
Ox0004L |
Создается диалоговое окно, имеющее |
|
|
зрительную иллюзию трехмерности |
DS_FIXEDSYS |
OxOOOSL |
Вместо SYSTEM FONT используется |
|
|
SYSTEM_FIXED^FONT |
DS_NOFAILCREATE |
0x00 10L |
Диалоговое окно создается, несмотря на то, |
|
|
что при его создании произошли ошибки |
DS LOCALEDIT |
Ox0020E |
В 32-битных приложениях не используется |
DS_SETFONT |
Ox0040L |
Определяется шрифт, который будет |
|
|
применяться в диалоговом окне |
DS MODALFRAME |
OxOOSOL |
Создается модальное диалоговое окно |
DS NOIDLEMSG |
OxOlOOL |
|
DS^SETFOREGROUND |
Ox0200L . |
Поместить диалоговое окно на передний |
|
|
план |
DS CONTROL |
Ox0400L |
|
DS_CENTER |
OxOSOOL |
Диалоговое окно помещается в центр |
|
|
рабочей области |
DS CENTERMOUSE |
OxlOOOL |
|
DS_CONTEXTHELP |
Ox2000L |
|
Кнопки
Перед тем, как начать рассказ о кнопках, хочу предостеречь читателя. Дело в том, что можно использовать кнопки и в обычных окнах. Но они, как и большинство элементов управления, проектировались для использования именно в диалоговых окнах. Использование кнопок в обычных окнах не рекомендуется, ибо это увеличивает риск того, что программа будет работать неправильно.
Кнопка - это имитация на экране обычной кнопки или переключателя. В этом разделе под кнопками я также подразумеваю не только PushButtons (обычные нажимаемые кнопки), но и Check Boxes (обычно это небольшие квадратики, в которых можно установить или не установить пометку) и Radio Buttons (небольшие кружочки, В ОДНОМ из которых стоит точка). Пользователь может установить курсор мыши на кнопку, щелкнуть клавишей мыши - и кнопка пошлет диалоговому окну сообщение WM_COMMAND. To же произойдет и в случае, если пользователь сначала выберет кнопку клавишей Tab, а потом нажмет Enter.
В параметрах сообщения WM_COMMAND содержится информация, которой достаточно, чтобы диалоговое окно узнало, от какой кнопки пришло сообщение, какое действие требуется выполнить, и каким образом пользователь инициировал выдачу сообщения.
При этом необходимо отметить, что обычная кнопка (её называют PushButton) не помнит того, что с ней делали, т. е. она на короткое время становится нажатой, а затем возвращается в исходное состояние. Можно нажать кнопку десять раз подряд, и все десять раз она пошлет диалоговому окну одно и то же сообщение, если, конечно, кнопка не сделана запрещенной. CheckBox помнит о том, что он находится в одном из двух состояний - установленном или не установленном, некоторые CheckBox'bi могут находиться еще и в неопределенном состоянии.
Говорить о состоянии одной RadioButton бессмысленно. Дело в том, что RadioButton'bi предназначены для осуществления выбора одного из нескольких взаимоисключающих вариантов, поэтому можно говорить о группе (иногда говорят кластере) RadioButton'ов. В частности, для объединения RadioButton'ов в кластеры служит такой элемент управления, как группа (GroupBox). Обычно группы используются для группирования органов управления только для улучшения дизайна диалоговых окон. Что же касается RadioButton'oB, то без обрамляющей группы существование кластера не имеет смысла.
Формат описания кнопок в окне диалога достаточно прост:
CONTROL «Заголовок», ButlonlD, class, styles, X, Y, Width, Height
X, Y, Width, Height - это все ясно. Все то же самое, что и при описании непосредственно диалогового окна. «Заголовок» - надпись на кнопке или рядом с кнопкой. ButtonID - идентификатор кнопки, т. е. значение, которое посылается диалоговому окну при нажатии кнопки в качестве LOWORD (wParam). Через HIWORD(wParam) диалоговое окно получает код нотификации, т. е. код того действия, которое произвел пользователь. Примерами действия пользователя могут служить нажатие клавиши Enter, двойной щелчок правой или левой клавишей мыши и так далее. А источник, т. е. хэндл инициировавшего сообщение окна, сообщения содержится в IParam (я напомню, что если сообщение приходит от меню, то IParam всегда равен 0, а если от акселератора - 1). Все легко и просто. Сложности начинаются при рассмотрении класса, определяющегося полем class, типа кнопки и стиля кнопки, которые определяется параметром style.
Для кнопок, вне зависимости от того, PushButton ли это, RadioButton или CheckBox, класс всегда определяется как «button».
Читатель, наверное, уже привык к тому, что ответы на большинство вопросов можно найти в файлах заголовков и файлах помощи Win32. Поэтому и сейчас, как всегда, смотрим в заголовочный файл winuser.h, выбираем оттуда стили кнопок, которые начинаются с букв BS_, и сводим их в табл. 20. Надоело изучать эти таблицы? Ничего, тяжело в учении - легко в бою! А.В. Суворов, «Наука побеждать».
А теперь, когда мй изучили весь вопрос теоретически, попробуем разобраться со всем этим разнообразием кнопок и стилей. Напишем небольшую программу, в которой будут присутствовать все виды кнопок (но отнюдь не все виды стилей). От кнопок будем получать сообщения и обрабатывать их. Мы также научимся устанавливать кнопки в те или иные состояния. В предлагаемой программе ничего не делается, за исключением того, что демонстрируется, как устанавливать кнопки в то или иное состояние и считывать состояние, в котором кнопки находятся.
В программе создается диалоговое окно, имеющее кластер RadioButtons, состоящий из трех кнопок, три CheckBox'a, а также две PushButton. Состояния RadioButtons и CheckBoxes до отображения диалогового окна могут быть определены через меню. Затем проявляется обратная связь - состояния RadioButtons и CheckButtons определяют состояния элементов меню после закрытия диалога. PushButton с надписью «Cancel» приводит к закрытию диалогового окна.
Т а б л и ц а 20. Стили кнопок
Флаг-
Значение
Эффект
BS_ PUSHBUTTON BS DEFPUSHBUTTON
BS_CHECKBOX
BS^AUTOCHECKBOX
BS^RADIOBUTTON BSJSTATE
BS^AUTO3STATE
BS_GROUPBOX BSJJSERBUTTON
BS_AUTORADIOBUTTON
BS_OWNERDRAW BS_LEFTTEXT
BSJTEXT BSJCON BS^BfTMAP BS_LEFT
BS_RtGHT BS CENTER
OxOOOOOOOOL 0x00000001L
Ox00000002L
Ox00000003L
Ox00000004L OxOOOOOOOSL
Ox00000006L
Ox00000007L OxOOOOOOOSL
Ox00000009L
OxOOOOOOOBL Ox00000020L
OxOOOOOOOOL Ox00000040L OxOOOOOOSOL 0x000001OOL
Ox00000200L Ox00000300L
Создается обычная кнопка Создается обычная кнопка, которая срабатывает при нажатии «Enter» даже тогда, когда не выбрана Создастся CheckBox, при нажатии состояние автоматически не изменяется, забота об этом ложится на программу
Создается CheckBox, который автоматически меняет свое состояние при нажатии
Создается Radio Button, автоматически состояние не меняется То же, что и BS_CHECKBOX, но имеет три состояния - включенное, отключенное и неопределенное, автоматически состояние не меняет То же, что и предыдущее, но состояние меняется автоматически Группа
Устаревший стиль, необходимо использовать BS_OWNERDRAW То же, что и RadioButton, но при нажатии состояние меняется автоматически
За прорисовку кнопки отвечает программа, а не система Текст помещается слева от RadioButton'a или CheckBox'a, то же, что и BS_RIGHTBUTTON Внутри или рядом с кнопкой отображается текст
Внутри кнопки или рядом с кнопкой отображается иконка Внутри кнопки или рядом с кнопкой отображается bitmap Размещает текст у левого края прямоугольника, выделенного для размещения кнопки Размещает текст у правого края прямоугольника, выделенного для размещения текста Размещает текст по горизонтали в центре прямоугольника, выделенного для размещения кнопки
Окончание табл. 20
Флаг
BSJTOP
BS_BOTTOM
BSJVCENTER
BS_PUSHL1KE
BSJvfULTILINE
BS_NOTIFY
BS_FLAT
BS RIGHTBUTTON
Значение
Ox00000400L OxOOOOOSOOL OxOOOOOCOOL
0x0000IOOOL Ox00002000L Ox00003000L
OxOOOOSOOOL Ox00000020L
Эффект
Размещает текст у верхнего края прямоугольника, выделенного для размещения кнопки Размещает текст у нижнего края прямоугольника, выделенного для размещения кнопки Размещает текст по вертикали в центре прямоугольника, выделенного для размещения кнопки Делает Checkbox или RadioBulton внешне похожими на PushBulton При необходимости текст разбивается на несколько строк Разрешает посылку родительскому окну нотификационных сообщении BN_DBLCLK, BN KILLFOCUSи BN^SETFOCUS Не добавляется имитация трёхмерности изображения элемента управления
RadioButton или CheckBox размещаются справа от надписи (то же, что и BS LEFTTEXT)
При работе программы любое изменение состояния кнопок приводит к выдаче сообщения в строке состояния диалогового окна.
Ниже приволен файл описаний, использующийся при работе демонстрационной программы:
#dcfineIDM EXIT |
101 |
#defmeIDM RadioButtonl |
102 |
#defmc IDM_RadioButton2 |
103 |
tfdcfine IDM_RadioButton3 |
104 |
#dcfine IDM CheckButton 1 |
105 |
#define IDM_CheckButton2 |
106 |
#define IDM_CheckButton3 |
107 |
Adeline IDM DisplayDialog |
108 |
#dcimclDC BUTTON! |
201 |
«define IDC BUTTON2 |
202 |
#ddine IDC GROUPBOX 1 |
203 |
#defineIDC RADIOBUTTON 1 |
204 |
#define IDC RADIOBUTTON2 |
205 |
#defme IDC
#defme IDC
#defme IDC~
#define IDC
#defme IDC~
#define IDC
RADIOBUTTON3
GROUPBOX2
^CHECKBOX!
CHECKBOX2
CHECKBOX3
STATUSBAR
206
207
208
209
210
301
А теперь - основной файл программы:
#includc <windows.h> ^include <commctrl.h>
#iiiclude "buttons.h"
HINSTANCE hlnst; HWND hWnd ; int nRadioButtonld;
UINT uCheckBoxesState[3] - {MFJJNCHECKED, MF_UNCHECKED, MFJJNCHECKED};
long WINAPI ButtonsExampleWndProc ( HWND, UINT, UINT, LONG ); BOOL CALLBACK ButtonsExampleDialogProc(HWND, UINT, WPARAM,
LPARAM);
int WINAPI WinMain ( HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )
WNDCLASS WndClass ;
MSG Msg;
char szClassName[] = "ButtonsExample";
hlnst — hlnstance; /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style = CSJHREDRAW | CS_VREDRAW;
WndClass.IpfnWndProc = ButtonsExampleWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hlnstance = hlnstance ;
WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION);
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);'
WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
WndClass.IpszMenuName = "ButtonsExampleMenu";
WndClass.IpszClassName = szCIassName;
if ( !RegisterClass(&WndClass))
'i
MessageBoxfNULL,"Cannot register class","Error",MB_OK); return 0; ~~ } liWnd ^ CreatcWindow(szClassName, "Button Use Example",
107
WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, CW_USEDEFAULT, NULL, NULL, Mnstance,NULL);
ifl[!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 ButtonsExampleWndProc (HWND hWnd, UINT Message,
UINT wParam, LONG IParam )
{
static BOOL bFlag = FALSE; static HMENU hMenu I , hMenu2; int i;
switch(Message)
{
case WM_CREATE:
hMenu I =GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), 0); hMenu2 = GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), I); CheckMenuRadioItem(hMenu 1 , IDM_RadioButton 1 , IDM_RadioButton3 ,
IDM_RadioButtonl, MF_BYCOMMAND); nRadioButtonld = IDM_RadioButtonl + 102; for(i = 0; i < 3; i++)
ClieckMenuItem(hMenu2, IDM_CheckButton 1 + i, MF_UNCHECKED); break;
case WM_COMMAND: switch(LOWORD(wParam)) {
case IDM^EXIT:
SendMessage(hWnd, WM_CLOSE, 0,0); break;
case IDM_RadioButton 1 : case IDM_RadioButton2: case IDM_RadioButton3: nRadioButtonld = LOWORD(wParam) + 102;
Cht-ckMenuRadiohem(hMenuI, lpM_RadioButtonI, IDM_RadioButton3,
LOWOR6(wParam), MF_BYCOMMAND); break;
case IDM_CheckButtonl: case IDM_CheckButton2: case IDM_CheckButton3: i - LOWORD(wParam) - 105; uCheckBoxesState[i] = uCheckBoxesState[i] == MF_CHECKED ?
MF_UNCHECKED : MF_CHECKED; ChcckMenuItcm(liMenu2, LOWORD(wParam), MF_BYCOMMAND
uCheckBoxesState[i]); break;
case IDM_DisplayDialog:
DialogBox(hInst, "ButtonsExample", hWnd, ButtonsExampleDialogProc); break; }
break;
case WMJ5ESTROY: PostQu i tMcssage(O); return 0; i return DefWindowProc(hWnd,Message,wParam, IParam);
BOOL CALLBACK. ButtonsExampleDialogProc(HWND hDIg, UINT Message,
WPARAM wParam, LPARAM IParam)
f \
int i;
char cMyMcssage[SO];
switch(Messagc)
f
case WMJNITDIALOG: // Set slates of controls
ScndDlgltemMcssageOiDlg. nRadioButtonld, BM SETCHECK,
BST_CHECKED, 0);
lbr(! -- [DC CHECKBOX!; i <- IDC_CHECKBOX3; i++) if(uCheckBoxesState[i - 208])
SeiidDlgItemMessagc(hDlg, i, BM_SETCHECK, BST_CHECKED, 0); return TRUE: case WM_COMMAND: swilch(LOWORD(wParanv)
case IDC_RADIOBUTTON1: case IDC_RADIOBUTTON2: case IDC_RADIOBUTTON3:
sprintf(cMyMcssage,"Message from RadioButton%d", LOWORD(wParam) - 203);
SendDlgItemMessage(hDlg,IDC_STATUSBAR,SB_SETTEXT,
(WPARAM) 0, (LPARAM) cMyMessagc);
CheckMenuRadioItem(GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), 0), IDM_RadioButton 1, IDM_RadioButton3, LOWORD(wParam)- 102, MF_BYCOMMAND); return FALSE; caseHXJCHECKBOXl: case IDC_CHECKBOX2: caseIDC_CHECKBOX3: sprintf(cMyMessage,"Message from CheckBox%d",
LOWORD(wParam) - 207); SendDlgItemMessage(hDlg,IDC_STATUSBAR,SB_SETTEXT,
(WPARAM) 0, (LPARAM) cMyMessage); i = LOWORD(wParam) - 208; uCheckBoxesState[i] = uCheckBoxesState[i] == MF_CHECKED ?
MF_UNCHECKED : MF_CHECKED;
CheckMenuItem(GetSubMenu(GetSubMenu(GetMenu(hWnd), 1), 1), LOWORD(wParam) - 103. uCheckBoxesState[i]); return FALSE; caseIDC_BUTTONl:
SendDlgItemMessage(hDlg,roC_STATUSBAR,SB_SETTEXT, (WPARAM) 0,
(LPARAM) "Message from PushButton"); return TRUE; case IDCJ3UTTON2: // Save the state of RadioButtons
i = IDC_RADIOBUTTON 1; while(!SendDlgItemMessage(hDlg, i, BM_GETCHECK, 0, ()))
nRadioButtonld = i; // Save the state of CheckButtons
for(i = RXJCHECKBOX1; i <= IDC_CHECKBOX3; i++) uCheckBoxesState[i - 208] = SendDlgltemMessagefhDlg, i, BM_GETCHECK, 0, 0) = 0 ? MF_UNCHECKED : MF_CHECKED; EndDialog(hDlg,0); return TRUE;
i /
break; return FALSE;
При линковании программы необходимо использовать фага ресурсов:
#include "buttons.h"
ButtonsExample DIALOG 50, 50, 154, 108
STYLE DS_MODALFRAME | DSJDLOOK | DS_CONTEXTHELP |
WS_POPUP | WS_VISIBLE | CAPTION "Buttons Example" FONT 8, "MS Sans Serif
WS CAPTION WS SYSMENU
CONTROL "PushButton", IDC_BUTTONl. "button", BS_PUSHBUTTON |
BS_CENTER | BS_NOTIFY | WS_CHILD | WSJVISIBLE |
WS_TABSTOP, 8, 72, 64, 16 CONTROL "RadioButtonl", IDC_RADIOBUTTON 1, "button",
BS_AUTORADIOBUTTON | WS „CHILD | WS_VISIBLE |
WSJTABSTOP, 8, 12,64, 16 CONTROL "RadioButton2", IDCJRADIOBUTTON2, "button",
BS_AUTORADIOBUTTON | BS_FLAT | WS_CHILD
WSJVISIBLE | WSJTABSTOP, 8, 28, 64, 16 CONTROL "RadioButton3", IDC__RADIOBUTTON3, "button",
BS_AUTORADIOBUTTON | BS_LEFTTEXT | WS_CHILD |
WSJVISIBLE I WSJTABSTOP, 8, 44, 64, 16 CONTROL "Groupl", IDCJ3ROUPBOX1, "button", BS_GROUPBOX |
WS^CHILD | WSJVISIBLE | WS_GROUP, 4, 4, 72, 60 CONTROL "CheckBoxl", IDC_CHECKBOX1, "button", BS_AUTOCHECKBOX |
WS^CHILD | WS_VISIBLE | WS_TABSTOP, 82, 12, 64, 16 CONTROL "CheckBox2", IDC_CHECKBOX2, "button", BS_AUTOCHECKBOX |
WS_CHILD | WSJVISIBLE | WSJTABSTOP, 82, 28, 64, 16 CONTROL "CheckBox3", IDC_CHECKBOX3, "button", BS^AUTOCHECKBOX |
WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 43, 64, 16 CONTROL "Group2", IDC_GROUPBOX2, "button", BS_GROUPBOX |
WS_CHILD | WS_VISIBLE WS_GROUP, 78, 4, 72, 60 CONTROL "Cancel", IDC_BUTTON2, "button", BS_PUSHBUTTON |
BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 72,
64, 16 CONTROL "StatusWindow 1", IDC^STATUSBAR, "msct!s_statusbar32", 3 |
WS_CHILD | WS_VISIBLE, 0, 116, 154, 12 }
ButtonsExampleMenu MENU f
POPUP "&File" ;
MENUITEM "E&xit", IDM_EXIT \
POPUP "&Dialog"
f t
POPUP "Initialize &RadioButtons"
f (
MENUITEM "Set RadioButton&l", IDM_RadioButton I MENUITEM "Set RadioButton&2", IDM_RadioButton2 MENUITEM "Set RadioButton&3", IDM_RadioButton3
i
POPUP "Initialize &CheckButtons"
111
\
MENUITEM "Set CheckButton&l", IDM_CheckButtonl MENUITEM "Set CheckButton&2", IDM_CheckButton2 MENUITEM "SetCheckButton&3", IDM_CheckButton3
MENUITEM SEPARATOR
MENUITEM "Displa&y Dialog", IDM_DisplayDialog
Buttons Example
Рис. 8. Диалоговое окно с кнопками различных стилей
На рис. 8 показано диалоговое окна, которое создается данной программой. Функция WinMainQ полностью стандартна и ничего нового не содержат.
При обработке сообщения WM^CREATE мы узнаем о новой возможности, связанной с меню. С помощью функции CheckRadioMenuItem() можно заставить группу элементов меню работать как кластер RadioButtons. В этом случае установка отметки у одного элемента меню приводит к сбросу отметки у всех других элементов меню, входящих в состав группы. Характерно, что при определении группы мы должны указать минимальный и максимальный идентификаторы элементов меню, включаемых в группу. Элементы, включаемые в меню, должны иметь идентификаторы, попадающие в определенный интервал, а не произвольно определенные. Этим мы будем пользоваться при определении и отображении в меню состояния кластера RadioButtons.
При обработке того же сообщения мы встречаем еще одну функцию, позволяющую установить отметку у элемента меню, - CheckMenultemQ. Эта функция позволяет изменять состояние только одного элемента меню. С помощью этой функции мы будем устанавливать и отображать состояние CheckButtons.
Наверное, читателю небезынтересно узнать, что эти две функции для отметки элементов по умолчанию используют различные значки. Рекомендую читателю обратить внимание на то, какие значки используются для отметки в обеих функциях.
Но эта программа написана отнюдь не для того, чтобы рассказывать о новых возможностях меню. При обработке сообщения от элемента меню с надписью «Display Dialog» создается диалоговое окно, в котором и содержатся те кнопки, о которых мы говорили. В зависимости от того, какое диалоговое окно должно быть создано, могут быть использованы функции DialogBox() и CreateDialogQ. Функция DialogBoxQ создает модальное диалоговое окно. Немодальное диалоговое окно создается с помощью функции CreateDialogQ. Мы используем функцию DialogBoxQ.
В файле winuser.h эта функция описана следующим образом:
WINUSERAPI int WINAPI DialogBoxParamA(HINSTANCE hlnstance,
LPCSTR IpTcmplateName, HWND hWndParent, DLGPROC IpDialogFunc, LPARAM dwInitParam); WINUSERAPI int WINAPI DialogBoxParamW(HINSTANCE hlnstance,
LPCWSTR IpTemplatcName, HWND hWndParent, DLGPROC IpDialogFunc, LPARAM dwInitParam);
#ifdef UNICODE
#define DialogBoxParam DialogBoxParamW
#elsc
#dcfine DialogBoxParam DialogBoxParamA
#endif// IUNICODE
#dcfine DialogBoxA(hInstance, IpTemplatc, hWndParent, IpDialogFunc) \
DialogBoxParamA(hInslance, IpTemplate, hWndParent, IpDialogFunc, OL)
//define DialogBoxW(hInstance, IpTemplate, hWndParent, IpDialogFunc) \
DialogBoxParamW(hInstance, IpTemplate, hWndParent, IpDialogFunc, OL)
//ifdef UNICODE
//define DialogBox DialogBoxW
//else
//define DialogBox DialogBoxA
#endif // IUNICODE
Видно, что функция DialogBoxQ фактически является частным случаем функции DialogBoxParamQ. На их различии мы остановимся чуть позже, а сейчас рассмотрим аргументы DialogBoxQ.
Первый аргумент понятен, мы его используем в каждой программе. Второй аргумент - указатель на имя шаблона, использующегося при
построении диалога. В нашем случае диалог сохранен в виде ресурса, поэтому мы указываем имя ресурса.
Третий аргумент - хэндл родительского окна. О том, что такое родительское окно, нужно сказать особо.
Во-первых, если мы представим окна, начиная с Desktop'a, как располагающиеся друг над другом (в так называемом Z-порядке), то дочернее окно обязательно будет над родительским, от этого зависит порядок обработки сообщений. Во-вторых, сообщения о действиях с диалоговым окном (нотификационые сообщения) система будет посылать именно родительскому окну. В-третьих, при закрытии родительского окна дочернее окно закрывается автоматически. Возможно, конечно, создание диалоговых окон без родителя, но тогда придется писать огромное количество кода, обеспечивающее нормальное функционирование окна.
Обычно родительским окном является то, оконная функция которого создает диалоговое окно. В программе основное окно программы является родительским по отношению к диалоговому окну.
И наконец, последний аргумент - указатель на функцию диалогового окна, т. е. на функцию, которая будет обрабатывать получаемые от элементов управления сообщения.
В дополнение к этому заметим, что последний аргумент функции DialogBoxParamQ - это какой-то параметр, определяемый программистом, который может быть передан функции диалогового окна.
Итак, мы рассмотрели функцию основного окна и остановились на функции окна диалога. На очереди - ее рассмотрение.
Диалоговая функция очень напоминает функцию окна, но имеет ряд отличий. Во-первых, обычная оконная функция возвращает значение типа LRESULT. Диалоговая функция возвращает значение типа BOOL. Во-вторых, обычная функция окна передает сообщения, обрабатывать которые не нужно, процедуре обработки по умолчанию (DefWindowProc()). Диалоговая функция в том случае, если она обработала сообщение, возвращает TRUE, а в противном случае FALSE. Другими словами, диалоговая функция должна вернуть FALSE в том случае, если ей необходимо передать сообщение для дальнейшей обработки в «недрах» Windows.
Для функции диалогового окна аналогом сообщения WM_CREATE является сообщение WM_INITDIALOG. Диалоговая функция получает его после создания диалогового окна в памяти, но до его отображения. Обычно именно при обработке этого сообщения производится инициализация диалога. Программа, которую мы сейчас разбираем, не является исключением. При обработке WM_INITDIALOG мы встречаемся с одним исключением из стройной системы правил Win32. Если программе необ-
ходимо, чтобы система продолжила обработку сообщения WIVMNITDIALOG, то после обработки этого сообщения программа должна вернуть TRUE. Рекомендую читателю немного поэкспериментировать с программой и попробовать после обработки WMJNITDIALOG вернуть вместо TRUE значение FALSE. Думаю, разница будет заметна сразу (по моему мнению, она бросается в глаза).
При обработке сообщения WM_DIALOG в программе производится установка состояний RadioButtons и CheckButtons. Здесь мы встречаемся еще с одной интересной функцией. Давайте вспомним, что любой элемент управления является окном. Для управления окнами используются сообщения, а для того, чтобы послать сообщение окну, нужно знать хэндл окна - адресата. При создании диалога мы определяли идентификатор элемента управления. Win32 позволяет определить хэндл окна -элемента управления. Для этого предназначена функция GetDlgltemQ, возвращающая искомый хэндл. Теперь мы можем послать сообщение окну с помощью функции SendMessageQ. Таким образом, нам нужно написать что-то типа SendMessage(GetDlgItem(...));
Для того чтобы облегчить жизнь программистам, в Win32 включена функция SendDlgltemMessageQ, объединяющая две упомянутые выше функции в одну. Прототип этой функции можно найти в файле winuser.h:
WINUSERAPI LONG WINAPI SendDlgItemMessageA(HWND hDIg,
int nlDDlgltem, UINT Msg, WPARAM wParam, LPARAM IParam);
WINUSERAPI LONG WINAPI SendD!gItemMessageW(HWND hDIg,
int nlDDlgltem, UINT Msg, WPARAM wParam, LPARAM IParam);
tfifdefUNICODE
#define SendDlgltemMessage SendDlgltemMessageW
#else
#defme SendDlgltemMessage SendDlgltemMessageA
#endif //! UNICODE
Первый аргумент - хэндл диалогового окна, функция диалога получает его от системы при вызове. Второй аргумент - идентификатор элемента управления. Третий аргумент - посылаемое элементу сообщение. Для управления кнопками служат сообщения, идентификаторы которых начинаются с ВМ_. Все определенные в winuser.h идентификаторы приведены в табл 21.
Т а б л и ц а 21. Сообщения, посылаемые кнопкам
Идснтифнкачор |
Значение |
Описание |
ВМ_ОЕТСНЕСК. |
OxOOFO |
Получить состояние отметки CheckBox'a или |
|
|
RaJioButton'a |
ВМ SETCHECK |
OxOOFl |
Установить отметку в CheckBox'a или RadioButton'a |
ВМ GETSTATE |
OxOOF2 |
Получить состояние кнопки |
BM_SETSTATE |
ОхООРЗ |
Установить состояние подсветки кнопки (имитация |
|
|
удержания нажатой клавиши мыши на кнопке) |
ВМ SETSTYLE |
OxOOF4 |
Изменить стиль кнопки |
BPv-fcLICK |
OxOOF5 |
Симуляция нажатия кнопки мыши |
BM_GETIMAGE |
OxOOF6 |
Получить хэндл изображения (иконки или bitmap'a), |
|
|
связанною с кнопкой |
BM_SETIMAGE |
OxOOF? |
Связать изображение с кнопкой |
Идентификаюр |
Значение |
Описание |
BST_ UNCHECKED BST_CHECKED BSTJNDETERMINATE BST PUSHED BST_BST_FOCUS |
0x0000 0x000 1 0x0002 0x0004 0x0008 |
Checkbox или RadioButton делается неотмеченной (может устанавливаться) ChcckBox или RadioButton делается отмеченной (может устанавливаться) Состояние CheckBox не определено (может устанавливаться) Кнопка нажата (только в ответ на запрос о состоянии кнопки) Кнопка имеет клавиатурный фокус (только в ответ на запрос о состоянии кнопки) |
Четвертый и пятый аргументы - это wParam и IParam посылаемого сообщения. Для каждого сообщения они определяются отдельно. Надеюсь, что читатель разобереться с параметрами этих сообщений самостоятельно по файлам помощи. Состояния кнопок имеют идентификаторы, начинающиеся с BST_ (табл. 22).
После того, как мы разобрали функцию SendDigIteinMessage(), все остальное в программе вызывать каких-либо трудностей не должно.
Окно сообщений
Но возникает вопрос: неужели же даже для простейших действий, например, для запроса подтверждения о необходимости удаления файла, необходимо писать функцию диалогового окна? Ответ очевиден: нет, не нужно. Для решения этих вопросов вполне достаточно гак называемого окна сообщений.
116
Окно сообщений, которое мы неоднократно использовали, является простейшим типом диалогового окна. Его назначение определяется его названием. Ни для чего другого, кроме вывода сообщения пользователю и предложения нажать одну из имеющихся кнопок, эти окна не предназначены. Тем не менее, работу с этим типом диалогов я хотел бы рассмотреть очень подробно, так как она очень часто используется не только для выдачи сообщений пользователю, но и для отладки. Функция, с помощью которой создастся окно сообщений, называется MessageBoxQ.
В файле winuser.h эта функция описана следующим образом:
WINUSERAPI int WINAPI McssagcBoxA(HWND hWiid. LPCSTR IpTcxt,
LPCSTR IpCaption, UINT uTypc);
WINUSERAPI int WINAPI MessageBoxW(HWND liWnd . LPCWSTR IpText,
LPCWSTR IpCaption, UINT uTypeV
#ifdcfUNICODE
#define MessageBox MessageBoxW
#clse
#defmc MessageBox McssaucBoxA
#cndi(7/ IUNICODF.
Первый аргумент этой функции - liWnd - хэндл родительского окна, т. е. того окна, которому будут посылаться сообщения от окна сообщений (извините меня, уважаемый читатель, за тавтологию. В данном случае я прошу не путать окно сообщений ДЛЯ ПОЛЬЗОВАТЕЛЯ с сообщениями ДЛЯ ОКОН). Второй аргумент - IpText - указатель на строку, содержащую отображаемый внутри окна текст. Перед отображением этот текст может быть отформатирован с помощью функции sprintfQ. Третий аргумент - IpCaption -заголовок окна сообщений. (Мне, например, использующему окна сообщений в основном для вывода отладочных сообщений, нравится давать окну заголовки типа «Hurray!» или «At last...».) Четвертый аргумент - иТуре - определяет тип окна сообщений, т. е.: перечень кнопок, отображаемых в окне сообщений; иконку, отображаемую в окне сообщений; кнопку, считающуюся кнопкой по умолчанию; модальность окна сообщений.
Наверное, вы неоднократно видели на экране окно сообщений с различным набором кнопок. Этот набор состоит из кнопок «OK», «Retry», «Abort» и др. Наличие каждой из таких кнопок определяется флагами, установленными в четвертом аргументе. Возможные значения флагов иТуре можно найти в файле winuser.h. Все они начинаются с букв MB (табл. 23).
Таблица 23. Типы окон сообщений
Флаг |
Значение |
Эффект |
MB OK |
OxOOOOOOOOL |
Кнопка «OK» |
MB OKCANCEL |
0x0000000 1 L |
Кнопки «OK» и «Cancel» |
MB ABQRTRETRYIGNORE |
Ox00000002L |
Кнопки «Abort», «Retry», «Ignore» |
MB YESNOCANCEL |
Ox00000003L |
Кнопки «Yes», «No», «Cancel» |
MB YESNO |
0x000000041. |
Кнопки «Yes», «No» |
MB RETRYCANCF.L |
OxOOOOOOOSL |
Кнопки «Retry», «Cancel» |
Флаг |
Значение |
Эффект |
MB ICONHAND |
0x000000 10L |
Иконка с изображением знака «Stop» |
MBJCONQUESTION |
Ox00000020L |
Иконка с изображением вопроситель- |
|
|
ного знака |
MBJCONEXCLAMATION |
0x0000003 OL |
Иконка с изображением восклицатель- |
|
|
ного знака |
MB ICONASTERISK |
Ox00000040L |
Иконка с изображением буквы i |
MB ICONINFORMATION |
Ox00000040L |
Иконка с изображением буквы i |
MB ICONSTOP |
0x000000 10L |
Иконка с изображением знака «Stop» |
Флаг |
Значение |
Эффект |
MB DF.FBUTTON1 MB DEFBUTTON2 MB DEFBUTTON3 |
OxOOOOOOOOL 0x00000 100L Ox00000200L |
Первая кнопка работает но умолчанию Вторая кнопка работает по умолчанию Третья кнопка работает по умолчанию |
Т а б л и ц а 26. Идентификаторы, определяющие модальность окна сообщений
Флаг |
Значение |
Эффект |
|
MB |
_APPLMODAL |
OxOOOOOOOOL |
Разрешаются переключения на другие |
MB |
_SYSTEMMODAL |
0x00001000 |
приложения Не разрешаются переключения на другие |
MB |
TASKMODAL |
0x00002000 |
приложения Применяется в случае отсутствия роди- |
|
|
|
тельского окна для запрещения ввода в |
|
|
|
другие окна |
Нажатая клавиши |
Числовое -jiia'K'Miic |
Возвращаемое функцией значение |
ок |
1 |
IDOK |
Cancel |
2 |
IDCANCEL |
Abort |
3 |
IDABORT |
Retry |
4 |
IDRETRY |
Ignore |
5 |
IDIGNORF, |
Yes |
6 |
IDYES |
No |
7 |
IDNO |
|
8 |
IDCLOSE |
|
9 |
IDHELP |
Следующие флаги определяют, какая из кнопок будет считаться кнопкой по умолчанию (табл. 25). Модальность окна сообщений определяют флаги, приведенные в табл. 26.
Ну, вот, кажется и все. Мне бы хотелось обратить внимание читателя на то, что комбинировать с помощью логических операций можно только величины из разных таблиц. Это заметно даже при просмотре численных значений. Что произойдет при комбинировании этом, известно только фирме Microsoft. Например, указав в порядке эксперимента одновременно флаги MB_RETRYCANCEL и MB_ABORTRETRYIGNORE, я вообще не получил никакой кнопки в окне сообщений. Пришлось завершать процесс аварийно.
Итак, выдавать сообщения мы научились. Не хватает самой малости, выяснить, как приложение узнает о том, какую кнопку нажал пользователь. Но здесь дело обстоит просто. Возвращаемое функцией MessageBoxQ значение напрямую определяется тем, какую кнопку нажал пользователь (табл. 27).
К сожалению, два последних значения, которые я нашел в заголовочном файле winuser.h, в файлах помощи фирмы Microsoft не описаны Об их назначении можно только догадываться по их названиям.
Мы закончим рассмотрение темы об окнах сообщений очередной демонстрационной программой. Для того чтобы увидеть, что возвращает функция MessageBoxQ, я воспользовался обычным окном сообщений из интегрированной среды Borland C++ 5,0. В демонстрационной программе я не стал мудрствовать лукаво и выдал на отображение олно-
единственное окно сообщений с одной иконкой и тремя кнопками, «Abort», «Retry» и «Ignore». При нажатии клавиш «Retry» и «Ignore» в окне Message появляются сообщения о том, какая клавиша нажата. При нажатии клавиши «Abort» работа программы прекращается:
#include <windows.h>
int WINAPI WinMain(HINSTANCE hlnstance.HINSTANCE hPrevInstance, LPSTR IpszCmdLine, int nCmdShow)
{
int nRcsult = IDRETRY; int nlndex; char* pcMessage[] {
"Retry key pressed", "Ignore key pressed"
while( (nRcsult = MessagcBox(NULL, "And what do you want to see?", "See in Message window, please", MB ABORTRETRYIGNORE | MBJCONASTERISK)) != IDABORT7)
{ switch(nResult)
{ case IDRETRY:
nlndex — 0;
break; case IDIGNORE:
nlndex = 1 ;
break;
} OutputDebugString(pcMessage[nIndex]);
} return 1;
На рис. 9 показано окно сообщений, создаваемое программой.
See in Message window, please
Рис. 9. Окно сообщений с тремя кнопками
Единственное, что осталось неясным в этой программе - это функция OutputDebugString(). Ее единственным аргументом является указатель на строку, которая должна быть передана отладчику. В случае отсутствия отладчика, содержимое строки появляется в окне сообщений интегрированной среды.
Мы разобрали вопросы о создании окна сообщений и о работе кнопок. Теперь я расскажу о таком мощном элементе, как окно списка. Наверное, это наиболее часто используемый элемент управления (не считая, конечно, кнопок), а возможности, которые список предоставляет программисту, иногда просто поражают. И это без учета комбинированных списков, всевозможных производных типа окон просмотра деревьев и т.д.
СПИСКИ
Окно списка - это совокупность строк и/или картинок, которые отображаются в скроллируемом окне. Список позволяет добавить элемент в список, удалить элемент из списка, найти элемент, получить общее число элементов и т. д. Образно говоря, список - это некое уменьшенное подобие базы данных, позволяющее выполнять большинство стандартных функций по управлению базой данных.
Все списки делятся на две большие группы. В первую группу входят списки, которые позволяют выбрать только один элемент из всех имеющихся. Вторую группу составляют списки, позволяющие выбрать одновременно несколько элементов, удовлетворяющих определенному критерию.
У списков есть еще одно очень важное свойство. С каждым элементом списка мы можем связать некоторый объект в памяти. Другими словами, то, что мы видим в списке на экране, может быть только вершиной айсберга. Сам айсберг, сиречь информация, связанная с элементом, может храниться глубоко в недрах Win32. Список не может быть очень большим (список, как и любая динамическая структура данных, хранится в памяти), но он может быть весьма удобным инструментом для создания и хранения небольших объемов данных. Кстати, для хранения данных можно создать окно списка, но на отображение его не выводить. В памяти будет создана динамическая структура, с которой удобно работать.
Список может быть создан вместе с диалоговым окном в качестве ресурса, а также посредством использования функции CreateWindow(). В последнем случае в качестве имени класса необходимо указывать «LISTBOX». В подавляющем большинстве случаев списки создаются в ресурсах, поэтому мы остановимся именно на этом способе. При работе в редакторе ресурсов ничего сложного в создании списка нет. Некоторые
сложности возникают при создании ресурса в текстовом редакторе, jio все эти сложности преодолимы. Формат описания окна списка в файле ресурсов ничем не отличается от описания, которое мы использовали для описания кнопок. Дабы читателю не пришлось разыскивать это описание, я приведу его еще раз:
CONTROL «Заголовок», ListboxID, «listbox», styles, X, Y, Width,
Height
Здесь я заменил класс «button» на класс «listbox», ибо именно к этому классу относятся списки. Возможные стили списков мы получим, обратившись к файлу winuser.h (табл. 28).
Все сообщения, с которыми работают списки, можно разделить на несколько логических групп. Постараюсь описать три группы, в которые я включил сообщения, представляющиеся мне наиболее важными.
Сообщения, обеспечивающие добавление и удаление элемента
Для того чтобы добавить элемент в список, необходимо просто послать списку сообщение LB ADDSTRING. При этом wParara должен быть равным нулю, a IParam должен указывать на добавляемый к списку объект. Этот элемент совсем необязательно должен быть строкой. Если у списка не установлен стиль LBS_HASSTRING, то IParam, указывает на объект, связанный с элементом. Для того чтобы получить или изменить эти данные, можно воспользоваться сообщениями LB_GETITbMDATA и LB_SETITEMDATA.
Если у списка установлены стили LBS^SORT и LBS HASSTRING, то строка добавляется в список, после чего происходит сортировка. Если стиль LBS SORT не указан, строка добавляется в конец списка. Если, наоборот, указан стиль LBS SORT, но не указан LBS HASSTRING, то список посылает родительскому окну одно или несколько сообщений WM_COMPAREITEM, которые позволяют определить, где должен быть расположен включаемый элемент. Возвращает это сообщение либо номер, под которым элемент включен в список, либо одно из двух значений, говорящих об ошибке: LB_ERR - встретилась ошибка; LB_ERRSPACE - не хватило памяти для размещения элемента.
Элемент может быть добавлен в список и другим способом. Отличие сообщения LBJNSERTSTRING от предыдущего состоит в том, что wParam этого сообщения содержит номер (считается от нуля) элемента, ПОСЛЕ которого нужно включить данный элемент. Кроме этого, сортировка элементов в этом случае не производится. Возвращаемые значения точно такие же, как и в предыдущем случае.
Особо нужно рассмотреть случай, когда необходимо создать список файлов в текущей директории, Для того чтобы облегчить жизнь программисту, в систему было включено сообщение LB_DIR. В качестве wParam этого сообщения записываются атрибуты файлов, имена которых необходимо добавить в список. Возможные значения этого параметра приведены в табл. 29.
Т а б л и ц а 28. Стили окон списков
Флаг'
Значение
Описание
LBS NOTIFY
LBS SORT
LBS NOREDP.AW
LBS MULTIPLESEL
LBS OWNERDRAWFIXED
LBS OWNERDRAWVARIABLE
LBS HASSTRING LBSJJSETABSTOPS
LBS NOINTEGRALHEIGHT
LBS_MULTICOLUMN LBSJVVANTKEYBOARDINPUT
LBS_EXTENDEDSEL LBS DISABLENOSCROLL
LBSJMODATA LBS_NOSEL
LBS STANDARD
0x0001L
Ox0002L Ox0004L
Ox0008L 0x001OL
Ox0040L Ox0080L
OxOIOOL
Ox0200L Ox0400L
OxOSOOL OxlOOOL
0x20001 Ox4000L
Посылает сообщение родительскому окну о щелчке или двойном щелчке клавишей мыши
Строки сортируются по алфавиту Внешний вид списка не изменяется даже тогда, когда производятся изменения Список позволяет множественный выбор Родительское окно ответственно за прорисовку элементов, все -элементы списка одинаковой высоты То же, что и предыдущее, но элементы списка могут быть разной высоты Элементы списка - строки Разрешает расширять символы табуляции, встречающиеся в строках Список создается точно такого же размера, который указан в программе, выравнивание не производится
В списке создается несколько колонок, он скроллируется по горизонтали Позволяет приложению обрабатывать ввод с клавиатуры тогда, когда список удерживает фокус ввода
Позволяет списку с множественным выбором использовать для выделения клавишу Shift совместно с мышью или другие клавиатурные комбинации Показывать запрещенную линейку прокрутки тогда, когда в списке недостаточно элементов для прокрутки Устаревший стиль
Элементы списка видны, но выделение запрещено
LBSJvlOTIFY | LBS_SORT | WSJVSCROLL | WS_BORDER
Таблица 29. Атрибуты файлов, добавляемых в окно списка
Параметр |
Значение |
Описание |
DDL_READWRITE |
0x0000 |
Включить только файлы, доступные для чтения и |
|
|
•записи, без дополнительных атрибутов |
DDLJIEADONLY |
0x000 1 |
Включить в список только файлы, доступные для |
|
|
чтения |
DDL HIDDEN |
0x0002 |
Включить в список скрытые файлы |
DDL SYSTEM |
0x0004 |
Включить в список системные файлы |
DDL DIRECTORY |
0x00 1 0 |
Включить в список поддиректории |
DDL ARCHIVE |
0x0020 |
Включить в список архивные файлы |
DDL POSTMSG |
0x2000 |
|
DDL DRIVES |
0x4000 |
Включить в список имена дисководов |
DDL EXCLUSIVE |
0x8000 |
Включать в список файлы только с указанными |
|
|
атрибутами |
IParam сообщения LB_DIR указывает на строку, которая определяет, какие файлы необходимо добавить в список. Строка формируется по правилам, принятым ещё в MS DOS, то есть, к примеру, для того, чтобы отобразить все файлы в директории MyDir на диске С: необходимо записать «c:\VMyDir\\*.*»
Удаление элемента из списка производится посредством посылки списку сообщения LB DELETESTRING. В wParam этого сообщения необходимо указать номер удаляемого элемента. При анализе возвращаемого значения необходимо учесть, что при нормальном удалении возвращается число оставшихся элементов списка. Значение LB ERR должно указать программисту на то, что он неверно указал номер удаляемого элемента.
Вторая большая группа сообщений - это
Сообщения, обеспечивающие навигацию в списке
Под навигацией в списке я понимаю возможность программы определить, где ешходится указатель (выделенный элемент) списка и/или установить указатель на тот элемент, который в данный момент требуется программе. Возможности здесь достаточно обширные.
Наверное, наиболее часто для определения места выделенного элемента в списке будет использоваться сообщение LB_GETCURSEL. Никаких параметров это сообщение не использует, и wParam, и IParam должны быть равны 0. Если возвращаемое значение равно LB_ERR, в списке нет выделенных элементов.
Сделать элемент выделенным позволяет сообщение LB_SETCURSEL, wParam которого должен содержать номер элемента, который должен
стать текущим. Это сообщение имеет дело только со списками, позволяющими одиночный выбор.
Узнать, какая строка или какие данные хранятся в элементе списка, можно с помощью сообщения EB_GETTEXT. wParam должно хранить индекс интересующего нас элемента, a IParam должно указывать на буфер, в который будут записаны строка или указатель на ассоциированные данные.
Число элементов в списке может быть определено посредством сообщения LB GETCOUNT. Параметры этого сообщения не используются и должны быть установлены в 0, а возвращает оно число элементов в списке. Одна тонкость - число элементов всегда на 1 больше индекса последнего элемента списка. Например, в списке один элемент. Его помер будет равным нулю, но EB_GETCOUNT вернет 1.
И последней группой сообщений, на которых мы остановимся, являются
Нотификациопные сообщения
Если у списка установлен стиль EBS_NOTIFY, то список будет оповещать родительское окно о том, какие события с ним произошли посредством нотификационных сообщений. Нотификационные сообщения в случае списка - это сообщения WM_COMMAND, у которых младшее слово wParam содержит идентификатор окна списка, старшее слово wParam - код нотификации, a IParam - хэндл окна списка.
Кодов нотификации всего шесть (табл. 30).
Таблица 30. Коды нотификационных сообщений, посылаемых окнами списков
Код нотификации |
Описание |
LBN ERRSPACE LBN SELCHANGE LBN DBLCLK LBN SELCANCEL LBN SETFOCUS LBN KILLFOCUS |
Не хватает памяти Выделенным стал другой элемент Пользователь сделал двойной щелчок клавишей мыши Пользователь снял выделение Список получил клавиатурный фокус Список потерял клавиатурный фокус |
Для нормальной компиляции программы требуется файл ресурсов:
#include "list.h"
ListBox DIALOG 50, 50, 150, 140
STYLE DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP |
WS_POPUP | WS_VTSIBLE WS_CAPTION WS_SYSMENU CAPTION "ListBox Example" FONT 8, "MS Sans Serif
{
PUSHBUTTON "Display", ID_OK, 15, 100, 50, 14
PUSHBUTTON "Cancel", ID_Cancel, 85, 100, 50, 14
CONTROL "Families", ID_MyListBox, "listbox", LBS_STANDARD |
WS_CHILD | WS_VISIBLE | WS_TABSTOP, 15, 16, 120, 65 CONTROL "Families", -1, "static", SSJLEFT | WS_CHILD | WS_VISIBLE, 16, 6,
120, 10 CONTROL "StatusBar", ID_StatusBar, "msctls_statusbar32", 3 | WS_CHILD
WS_VISIBLE, 0,129, 150,12
ListBoxMenu MENU
{ POPUP "&File"
{ MENUITEM "E&xit", IDM_Exit
} MENUITEM "&Display Dialog", IDM_DisplayDialog
}
Далее следует файл заголовков, также используемый в программе:
#defme IDM_Exit 101
#defme IDM_CanceI 102
#define IDM_DisplayDialog 103
#defmeID_OK 201
#defme ID_Cancel 202
«define ID_MyListBox 203
#defme ID_StatusBar 204
И, наконец, основной файл программы:
#include <windows.h>
#include "list.h" ^include <commctrl.h>
HINSTANCE hlnst;
LRESULT CALLBACK ListBoxExampleWndProc(HWND, U1TMT, UINT, LONG);
126
BOOL CALLBACK ListBoxExampleDialogProc(HWND, UINT, WPARAM,
LPARAM);
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance,
LPSTR IpszCindParam, int nCmdShow ) {
HWND hWnd ; WNDCLASS WndClass ; MSG Msg; char szClassName[] = "ListExample";
hlnst= hlnstance;
InitCommonControls(); /* Registering our window class */ /* Fill WNDCLASS structure */
WndClass.style - CS_HREDRAW | CS_VREDRAW;
WndClass.IpfnWndProc = ListBoxExampleWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hlnstance = hlnstance ;
WndClass.hlcon = Loadlcon (NULL,IDI_APPLICATION),
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
WndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
WndClass.IpszMenuName = "ListBoxMenu";
WndClass.IpszClassName = szClassName;
if ( !RegisterClass(&WndClass)) !
MessageBox(NULL,"Cannot register class","Error",MB_OK); return 0; } hWnd = CreateWindow(szClassName, "ListBox Example Program",
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, CWJJSEDEFAULT, NULL, NULL, hlnstance.NULL); if(!hWnd) {
MessageBox(NULL,"Cannot create window","Error",MBJ3K); return 0;
/* Show our window */
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
/* Beginning of messages cycle */
while(GetMcssage(&Msg, NULL, 0, 0))
TranslatcMessage(&Msg);
127
DispatchMessage(&Msg);
} return Msg.wParam;
LRESULT CALLBACK ListBoxExampleWndProc(HWND liWnd, UINT Message,
UINT wParam, LONG IParam )
{
switch(Message) !
case WM_COMMAND: switch(LOWORD(wParam))
{ case IDM_DisplayDialog:
DialogBox(hInst, "ListBox", hWnd, ListBoxExampleDialogProc);
break; case IDM_Exit:
SendMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
break;
case WM JDESTROY: PostQuitMessage(O); return 0;
v i
return DefWindowProc(hWnd, Message, wParam, IParam);
BOOL CALLBACK ListBoxExampleDiaIogProc(HWND hDlg, UINT Message,
WPARAM wParam, LPARAM IParam)
{
int i; // 1 like «Ivanov, Petrov, Sidorov ...» TV-program. ©
LPSTR pszltems[12] = {"Berdiev", "Vasilkov", "Ivanov", "Petrov", "Sidorov", "Johnson", "Jackson", "Tompson", "Pererepenko", "Khabibullin", "Novozhenov", "Mamedov"};
char cMessage[36] = "Message about ";
char cltem[12];
static HWND hListBox;
switch(Message)
{
case WMJNITDIALOG: hListBox = GetD!gItem(hDlg, ID_MyListBox); for(i = 0;i< 12;i++) SendMessage(hListBox, LB^ADDSTRING, (WPARAM) 0, (LPARAM)
pszltems[i]); return TRUE;
case WM_COMMAND: switch(LOWORD(wParam)) {
case ID_MyListBox:
if(HIWORD(wParam) == LBN_SELCHANGE) { SendMessagc(hListBox, LB^GETTEXT, SendMessage(hListBox,
LB_GETCURSEL, 0, 0), (LPARAM) cltcm); strcpy(cMessage + 14, cllcm); SendDlgItemMessage(liDlg, ID_StatusBar, SB_SETTEXT,
(WPARAM) 0, (LPARAM) cMessage); }
break;
case ID_Cancel: EndDialogfliDlg, 0); break; } break;
) return FALSE;
В этой программе основное меню окна предлагает отобразить диалог. При запуске диалога (при обработке сообщения WMJNITDIALOG) производится заполнение списка фамилиями. При переносе выделения с выбранного элемента окна списка на другой элемент в строке состояния отображается выбранная фамилия. Таким образом, я продемонстрировал заполнение списка и выборку информации из него, а также работу по обработке нотификашюнного сообщения. Вид создаваемого программой окна со списком показан на рис. 10.