Азбука программирования в Win32 API

         

МЕНЮ И АКСЕЛЕРАТОРЫ



ПОДКЛЮЧЕНИЕ МЕНЮ К ОКНУ

В предыдущей главе мы написали и исследовали программу, создаю­щую окно. В этой главе мы должны научиться подключать меню к наше­му окну и, естественно, научиться реагировать на сообщения, исходящие от меню. Мы узнаем о том, какие типы меню и элементов меню бывают, изучим различные способы построения меню.



Любой, кто хоть немного работал в 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

 

 

 

102

Кнопки

Перед тем, как начать рассказ о кнопках, хочу предостеречь читателя. Дело в том, что можно использовать кнопки и в обычных окнах. Но они, как и большинство элементов управления, проектировались для исполь­зования именно в диалоговых окнах. Использование кнопок в обычных окнах не рекомендуется, ибо это увеличивает риск того, что программа будет работать неправильно.



Кнопка - это имитация на экране обычной кнопки или переключателя. В этом разделе под кнопками я также подразумеваю не только 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

 

106

#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?

 

Связать изображение с кнопкой

 

Т а б л и ц а 22. Состояния кнопок и их описание

Идентификаюр

 

Значение

 

Описание

 

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»

 

Т а б л и ц а 24. Идентификаторы иконки, появляющиеся в окне сообщений

Флаг

 

Значение

 

Эффект

 

MB ICONHAND

 

0x000000 10L

 

Иконка с изображением знака «Stop»

 

MBJCONQUESTION

 

Ox00000020L

 

Иконка с изображением вопроситель-

 

 

 

 

 

ного знака

 

MBJCONEXCLAMATION

 

0x0000003 OL

 

Иконка с изображением восклицатель-

 

 

 

 

 

ного знака

 

MB ICONASTERISK

 

Ox00000040L

 

Иконка с изображением буквы i

 

MB ICONINFORMATION

 

Ox00000040L

 

Иконка с изображением буквы i

 

MB ICONSTOP

 

0x000000 10L

 

Иконка с изображением знака «Stop»

 

Таблица 25. Идентификаторы кнопки по умолчанию

Флаг

 

Значение

 

Эффект

 

MB DF.FBUTTON1 MB DEFBUTTON2 MB DEFBUTTON3

 

OxOOOOOOOOL 0x00000 100L Ox00000200L

 

Первая кнопка работает но умолчанию Вторая кнопка работает по умолчанию Третья кнопка работает по умолчанию

 

<


Т а б л и ц а 26. Идентификаторы, определяющие модальность окна сообщений

Флаг

 

Значение

 

Эффект

 

MB

 

_APPLMODAL

 

OxOOOOOOOOL

 

Разрешаются переключения на другие

 

MB

 

_SYSTEMMODAL

 

0x00001000

 

приложения Не разрешаются переключения на другие

 

MB

 

TASKMODAL

 

0x00002000

 

приложения Применяется в случае отсутствия роди-

 

 

 

 

 

 

 

тельского окна для запрещения ввода в

 

 

 

 

 

 

 

другие окна

 

Т а б л и ц а 27. Значения, возвращаемые фунунией iVIessageBoxf)

Нажатая клавиши

Числовое -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

 

Кроме этого набора кнопок, uType определяет также и иконку (одну из предопределенных в Win32), которая будет отображаться в окне сообщений. Таблица 24 содержит флаги, определяющие иконку, появ­ляющуюся в окне сообщений.

Следующие флаги определяют, какая из кнопок будет считаться кноп­кой по умолчанию (табл. 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.