Подклассыокон
Сейчас нам надо рассмотреть одининтересный прием — порождение подкласса окон. Часто бывает так, чтовозможностей, предоставляемых окном того или иного стандартного класса Вам нехватает, а создавать эквивалентный стандартному класс с небольшими отличиямислишком сложно. В этом случае было бы удобно научиться создавать дополнительныеклассы окон, основанные на уже известных классах.
Именно это и называется порождением подкласса окон. Основнаяидея заключается в использовании собственной функции обработки сообщений,которая выполняла бы требуемую обработку, отличную от стандартной. При этом вкачестве процедуры обработки сообщений по умолчанию должна выступать процедура,определенная в уже существующем классе.
Для реализации этого метода нам надо сделать три вещи:
· узнать адрес процедуры обработкисообщений заданного окна (или заданного класса).
· научиться вызывать нужную процедурувместо процедуры обработки сообщений по умолчанию.
· сделать так, что бы сообщенияобрабатывала написанная нами процедура, а не определенная в классе.
Первую и третью задачи удобно решать с помощью функции
LONG SetWindowLong(hWnd, GWL_WNDPROC, lpfnNewProc );
эта функция одновременно устанавливает новый адрес процедурыобработки сообщений и возвращает адрес прежней функции. Конечно, когда мыпередаем адрес новой процедуры обработки сообщений он должен быть адресомсвязанной с нашим приложением функции, то есть он должен быть возвращенпроцедурой MakeProcInstance.
Теперь нам надо только организовать обращение к старойпроцедуре обработки сообщений вместо процедуры по умолчанию (DefWindowProc).Сделать это непосредственно мы не можем, так как при вызове оконной процедурымы должны связать ее с приложением, зарегистрировавшем этот класс. Вместо этогонам надо воспользоваться функцией:
LONG CallWindowProc(lpfnProc, hWnd, wMsg, wPar, lPar );
Итак, приведем небольшой пример:
static HANDLE hInstance;
static FARPROC lpfnNewProc;
static FARPROC lpfnOldProc;
LONG WINAPIChildProc( HWND, UINT, UINT, LONG );
//функция обработки сообщений главного окна
LONG WINAPI _export WinProc(
HWND hWnd, UINT wMsg, UINT wPar,LONG lPar
) {
static HWND hChild;
switch ( wMsg ){
case WM_CREATE:
lpfnNewProc= MakeProcInstance((FARPROC)ChildProc, hInstance );
hChild= CreateWindow(
“BUTTON”, “Btn A”,
BS_PUSHBUTTON | WS_CHILD |WS_VISIBLE,
10,10, 50,50,
hWnd, 0, hInstance, NULL
);
//заменяем процедуру обработки сообщений дочернего окна
lpfnOldProc=(FARPROC)SetWindowLong(
hChild,GWL_WNDPROC,(LONG)lpfnNewProc
);
break;
case WM_DESTROY:
DestroyWindow( hChild );
FreeProcInstance( lpfnNewProc );
break;
...
}
return DefWindowProc( hWnd, wMsg,wPar, lPar );
}
LONG WINAPI_export ChildProc(
HWND hWnd, UINT wMsg, UINT wPar,LONG lPar
) {
// специфичнаяобработка сообщений
// и вызов прежнейфункции, а не функции DefWindowProc
return CallWindowProc( lpfnOldProc,hWnd, wMsg, wPar, lPar );
}
Конечно, рассмотренный нами вариант не единственный. Так,например, мы можем заменять функцию обработки сообщений не окна, а класса.Тогда все вновь создаваемые окна этого класса будут применять нашу процедуру.Для этого мы должны использовать функцию
LONG SetClassLong(hWnd, GCW_WNDPROC, lpfnNewProc );
Что неудобно, так это то, что мы должны сначала создать окно,а только затем заменять процедуру обработки сообщений. Мы можем поступить ииначе — сначала узнать адрес процедуры обработки сообщений, используя функцию
GetClassInfo( hInstance,lpszClassName, lpWndClass );
которая заполняет структуру WNDCLASS информацией о данномклассе, а затем создать свой класс, который будет применять вместо процедурыобработки сообщений по умолчанию процедуру этого класса.
Связываниеданных с окном
При работе с окнами очень часто возникает необходимостьхранения данных, связанных с окном. Часто это приходится делать при работе сдочерними окнами, особенно при порождении подклассов окон, когда в такихсвязанных данных удобно сохранять адреса процедур обработки сообщений и пр. Приэтом не должно возникать вопросов с разделением данных между двумя окнамиодного класса.
Для такого связывания в Windows предусмотрено два разныхметода. Первый метод основан на выделении дополнительного пространства для данныхпользователя в структуре, описывающей окно или класс окон. Этот способдостаточно эффективен, но ограниченно применим, так как выделенное пространствостатично и не может изменить свой размер.
Для использования данных окна (или класса) мы должны, прирегистрации класса окон указать размеры дополнительного пространства,выделяемого в струткуре окна (поле .cbWndExtra структуры WNDCLASS) и вструктуре класса (поле .cbClsExra).При выделении пространства оно автоматически обнуляется. Подробнее об этом смотрилекцию 2.
Для доступа к элементам описаний класса и окна можноприменять функции:
UINT GetWindowWord(hWnd, nOffset );
LONG GetWindowLong( hWnd, nOffset);
UINT SetWindowWord( hWnd,nOffset, wNewValue );
LONG SetWindowLong( hWnd, nOffset,dwNewValue );
UINT GetClassWord( hWnd, nOffset);
LONG GetClassLong( hWnd, nOffset);
UINT SetClassWord( hWnd, nOffset,wNewValue );
LONG SetClassLong( hWnd, nOffset,dwNewValue );
Параметр nOffsetзадает смещение требуемого слова (двойного слова) относительно начала дополнительныхданных в байтах. Эти же функции могут применяться для доступа к некоторым полямсамих структур, а не дополнительных данных, но при этом они имеют отрицательныезначения.
Второй метод основан на применении специального списка свойств (property) окна. Этот список может динамическиизменяться, но работа с ним медленее, чем с данными окна. Кроме того онразмещается в локальной памяти модуля USER, поэтому ограничен размерамисвободной памяти модуля USER.
Так как списки свойств размещаются не в нашем приложении, топеред уничтожением окна, мы должны удалить все внесенные нами свойства.Свойства состоят из имени (строка символов) и словаданных. Часто это слово рассматривается как хендл блока данных. Мы можемзаписывать читать, удалять и перебирать свойства, связанные с окном. Для этогопредназначены следующие функции:
BOOL SetProp( hWnd,lpszName, hData );
HANDLE GetProp( hWnd, lpszName );
HANDLE RemoveProp( hWnd, lpszName);
int EnumProp( hWnd, lpfnEnumProc);
Хотя в документации данные, связанные с конктретнымсвойством, рассматриваются всегда как хендлы, но это не обязательно так.Конечно применение хендлов может несколько сократить размеры списка свойств засчет того, что одному элементу списка может соответствововать больше данных.
Так как Windows не знает, хендл какого блока данных(глобального или локального), объекта GDI или просто данные связан с конкретнымэлементом списка свойств, то при удалении записи эти данные не удаляются, апередаются Вам для их удаления.Ресурсыприложения
На предыдущих лекциях мы довольно часто ссылались на такиересурсы приложения, как курсоры и иконки, ранее мы познакомились более подробнос еще одной разновидностью ресурсов — битмапом. На примере битмапа мырассмотрели основные правила применения ресурсов.
Сейчас несколько обобщим эти правила и рассмотрим нескольковидов ресурсов более подробно. Для описания ресурсов мы должны сформироватьфайл описания ресурсов, имеющий расширение .RC. В этом файлеперечисляются ресурсы, которые могут быть использованы приложением.
Описание ресурсов имеет следующий вид:
ResNameId TypeNameId [load-opt] [mem-opt] ResSource
Каждый ресурс должен иметь собственное уникальное имя илиномер ResNameIdи имя или номер типа ресурса TypeNameId.
Эти имена задаются либо текстом (имя),либо числом (номер), либо символическим именем (номер).
Примеры:
MYBITMAP BITMAP my_bmp.bmp
100 ICON my_ico.ico
MYDATA 500 my_data.dat
#define IconID 101
IconID ICON second.ico
(компилятор ресурсов может использовать директивыпрепроцессора C и включать заголовочные файлы .H, для заданиясимволических имен).
Далее, при работе с ресурсами они будут загружаться из файлаприложения в память. При этом для ресурса обычно выделяется блок глобальнойпамяти мы можем задать некоторые характеристики ресурса как блока памяти mem-optи определять некоторые правила его загрузки load-opt.
load-opt, описывая правила загрузки, можетбыть:
PRELOAD ресурс должен загружаться в памятипри запуске приложения
LOADONCALL ресурс загружается только потребованию (используется по умолчанию)
mem-opt задает характеристики выделяемогоблока и может быть:
FIXED ресурс должен размещаться вфиксированном блоке памяти
MOVEABLE ресурс размещается в перемещаемомблоке памяти (используется по умолчанию)
DISCARDABLE перемещаемый ресурс может бытьудален из памяти (практически любой перемещаемый ресурс может быть удален, таккак его содержимое не меняется)
Наконец, нам требуются данные этого ресурса ResSource.Некоторые виды ресурсов должны быть размещены в отдельном файле, в этом случаев качестве ResSourceиспользуется имя файла, другие виды ресурсов требуют непосредственного описаниянепосредственно в файле описания ресурсов, в этом случае ResSource можетбыть:
BEGIN
данные ресурса
END
или
{
данные ресурса
}
Иногда допускается либо использования файлов, либонепосредственное описание ресурсов. Некоторый “разнобой” может быть связан сприменением компиляторов (и редакторов) ресурсов разных фирм — так как многиеиз них используют расширенные возможности.
Так, например, редактор ресурсов Borland WorkShopможет описывать практически все ресуры непосредственно в файле описанияресурсов, включая их в виде дампа ресурса, а стандартные компилятор ресурсов Microsoft RC не допускает этого, например, для курсоров илибитмапов. Компилятор ресурсов Symantec позволяетприменять кавычки при задании имен ресурса или типа, что позволяет составлятьимена из нескольких слов, что невозможно для Borland и Microsoft и т.д.
Windows предусматривает несколько стандартных типов ресурсов,а Вы можете легко описать ресурсы собственного типа, указав собственный тип(или номер, больший 255- номера типов от 0до 255зарезервированы Windows). При задании данных собственного ресурса Вы можетеуказать имя файла, содержащего этот ресурс — тогда этот файл включается вресурс как он есть, либо описав в виде текста непосредственно в файле описанияресурсов:
MyResource MyType
BEGIN
“This is a 0-terminated string\0”,1, 2, 3, 4, 5,
100, 0x1000
END
В качестве типа ресурса можно указать стандартный тип RCDATA,который соответствует включаемым в файл описания ресурсов данным пользователя вэтом-же формате. Если Вы хотите получить доступ к Вашим ресурсам, то надовоспользоваться парой функций:
HRSRC FindResource(hInstance, lpszName, lpszType );
HGLOBAL LoadResource( hInstance,hrSrc );
Первая функция позволяет получить промежуточный хендлописания ресурса, а вторая — загрузить ресурс память и получить хендлглобального блока памяти, содержащего ресурс. Если Вы декларировали ресурс как LOADONCALL,то физическое размещение ресурса в памяти произойдет не при вызове функции LoadResource,а при непосредственном обращении к ресурсу.
Если для задания имен ресурса или типа вы использовали текст,то параметры lpszNameи lpszTypeявляются указателями на соответствующие строки; если же используются номера, товы можете их указывать двумя способами — передав строку, начинающуюся на #,например, “#123”,либо разместив в младшем слове адреса нужный номер, а старшем 0. Последниймеханизм реализуется с помощью макроса:
LPSTR MAKEINTRESOURCE(nId );
Этот способ считается самым эффективным, тем более, что Выможете применять символические имена для задания номеров. При необходимостичтения данных ресурса Вы должны его зафиксировать в памяти с помощью процедуры
LPVOID LockResource(hGlobResource );
и после доступа к данным разрешить его перемещение:
BOOL UnlockResource(hGlobResource );
(Это не отдельная процедура, а обычный GlobalUnlock). Послеиспользования ресурса его можно удалить с помощью процедуры:
BOOL FreeResource(hGlobResource );
Когда Вы применяете ресурсы какого-либо типа,предусмотренного Windows, то приходится применять несколько другие способыдоступа к данным, связанные с необходимостью специальной обработки такихресурсов. Можно выделить следующие основные типы ресурсов:
ACCELERATORS – таблица акселераторовклавиатуры; для загрузки применяется функция
HACCEL LoadAccelerators( hInstance,lpszAccName );
BITMAP – битмап, включенный вприложение для загрузки применяется функция
HBITMAP LoadBitmap( hInstance,lpszBitmapName );
CURSOR – ресурс, представляющийкурсор мыши для загрузки применяется функция
HCURSOR LoadCursor( hInstance,lpszCursorName );
DIALOG – диалогс ресурсами типа DIALOGи с самими диалогами мы разберемся позже.
FONT – включениешрифтового ресурса о применении шрифтов мы говорили ранее. Включать ресурсэтого типа в Ваше приложение следует специфическим способом, существенноотличающимся от остальных ресурсов.
ICON – иконка длязагрузки применяется функция
HICON LoadIcon( hInstance,lpszIconName );
MENU – меню, котороеможет быть назначено к окну для загрузки применяется функция
HMENU LoadMenu( hInstance,lpszMenuName );
STRINGTABLE – таблица строк ресурс этоготипа вообще не загружается целиком. Он представляет собой таблицу строк,имеющих идентификаторы, и обеспечивает доступ к конкретной строке по ееидентификатору. Реально ресурсу этого типа соответствует не блок данных впамяти, а, может быть, несколько — для каждых 16 строк (попорядку номеров) выделяется отдельный блок. Приложение может содержать толькоодин ресурс этого типа, поэтому при описании таблицы строк в файле описанияресурсов ее имя не указывается — указывается только тип этого ресурса. Длядоступа к строкам применяется функция
int LoadString(hInstance, idString, lpszBuff, nmaxCount );
Мы достаточно близко познакомились с ресурсами типа BITMAPи FONT;практически можно считать что мы знакомы и с ресурсами типа CURSOR и ICON,так как они описываются так‑же, как и BITMAP. Сейчас намнадо лучше разобраться с тремя новыми типами – ACCELERARTORS, MENUи DIALOG.
Акселераторы
Акселераторы представляют собой простейшее средство длясвязывания определенных комбинаций клавиш с конкретными действиями. Можносчитать, что акселераторы представляют собой таблицу, в которой записываютсянажимаемые клавиши и генерируемые ими сообщения.
Точнее, акселераторы генерируют только сообщение WM_COMMAND,указывая более подробную информацию в параметрах этого сообщения. Параметр wParсообщения содержит идентификатор, назначенный клавише, а параметр lParвсегда равен 0x00010000L.
Для создания таблицы акселераторов Вы должны поместить вфайле описания ресурсов соответствующий ресурс:
AccName ACCELERATORS[load-opt] [mem-opt]
BEGIN
key, id [, type] [, options]
...
END
параметры могут быть следующими:
key определяет назначаемую клавишу
id посылаемый код извещения
type тип клавиши ASCII, VIRTKEYили опущен
options указывает состояние специальныхклавиш и некоторые действия: NOINVERT,ALT,SHIFT,CONTROLили опущен.
подробнее рассмотрим назначение акселераторов на примере:
“A”, 100 //послать извещение 100 при нажатии А
65, 100, ASCII // тоже самое, ASCII код 65 соответствует А
“^A”, 101 //послать 101 при нажатии Ctrl-A
“A”, 101, CONTROL // то жесамое
VK_SPACE, 102, VIRTKEY //послать 102 при нажатии Space
VK_SPACE, 103, VIRTKEY, SHIFT //послать103 принажатииShift-Space
Несколько слов следует сказать о применении акселераторов вприложении. Сам факт загрузки акселератора в память еще не обозначает егоприменения. Для того, что бы сообщения от клавиатуры начали обрабатыватьсяакселератором надо в главном цикле обработки сообщений включить специальлныесредства для их трансляции.
int TranslateAccelerator(hWnd, hAccel, lpMSG );
Параметр hWndуказывает окно, которое будет получать извещения, hAccel задает хендлтаблицы акселераторов, lpMSG- адрес структуры MSG,содержащей сообщение.
При обычном применении акселератора извлеченное из очередисообщение передается в эту функцию. Если это сообщение клавиатуры и данноенажатие на клавишу транслируется акселератором, то указанное окно получаетсообщение WM_COMMANDи процедура возвращает TRUE;во всех остальных случаях возвращается FALSE, говоря о том, что сообщение небыло трансировано в другое. Считается, что если сообщение было обработаноакселератором, то дальнейшая его обработка не требуется — то есть обычныйпроцесс трансляции и диспетчеризации этого сообщения исключается. При этомглавный цикл обработки сообщений приобретает следующий вид:
MSG msg;
HACCEL hAccel;
HWND hWnd;
...
hAccel= LoadAccelerators(hInstance, “AccName” );
...
while ( GetMessage( &msg, NULL,NULL, NULL ) ) {
if ( !hAccel ||!TranslateAccelerator( hWnd, hAccel, &msg ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
...
Загруженные акселераторы автоматически удаляются призавершении приложения.Меню
Еще одна разновидность ресурсов, которую мы должны сейчасрассмотреть – меню. Меню, предоставляемое Windows имеет иерархическуюорганизацию. Основное меню всегда представлено строкой в верхней части окна, изнего могут “выпадать” вертикальные меню, связанные с конкретным пунктом, и такдалее — причем все уровни меню, кроме верхнего, представлены вертикальнымименю.
Меню может быть описано как с помощью ресурсов, так ипрограммно, причем существующее меню всегда может быть изменено. При работе сменю наше приложение будет получать следующие сообщения:
WM_ENTERMENUIDLE когда меню было активизировано инаходится в состоянии ожидания.
WM_MENUSELECT посылается окну, использующемуменю, как извещение о выборе пункта меню.
WM_MENUCHAR информирует окно о том, что приработе с меню была нажата кнопка не соответствующая никакому пункту меню.Обрабатывая это сообщение Вы можете вернуть номер пункта меню который надовыбрать, указать Windows о том, что работа с меню закончена или потребоватькороткий звуковой сигнал.
WM_COMMAND посылается окну, извещая его овыборе требуемого пункта меню. Параметр wPar содержит идентификатор пунктаменю, а lParравен 0L.Обычно при работе с меню обрабатывается только сообщение WM_COMMAND,остальные применяются в специальных случаях.
Сейчас мы можем подвести некоторый итог под применениемсообщения WM_COMMAND,которое может быть получено от дочернего окна, акселератора или меню:
wPar LOWORD(lPar) HIWORD(lPar)
окно Id hWndChild wCode
акселератор Id 1
меню Id
При работе с системным меню вместо сообщения WM_COMMAND мы будемполучать сообщения WM_SYSCOMMAND.При описании меню в ресурсе текст описания меню должен размещаться в файлеописания ресурсов в следующей форме:
MenuName MENU [load-opt] [mem-opt]
BEGIN
определения пунктовменю 0-го уровня:
MENUITEM дляопределения пункта
POPUP дляопределения пункта, связанного с меню следующего уровня
BEGIN
определение пунктовменю 1-го уровня
...
END
END
Для определения пункта меню, связанного с меню следующегоуровня мы должны использовать следующую форму записи:
POPUP text [, options]
BEGIN
...
END
Для задания обычного пункта меню, то есть посылающего WM_COMMANDпри его выборе, мы должны использовать следующую форму записи:
MENUITEM text, id [,options]
В этих случаях приняты следующие обозначения:
text задает текст пункта меню в видестроки взятой в двойные кавычки, один из символов строки может предварятьсясимволом &, который приводит к подчеркиванию этогосимвола и его автоматической интерпретации как акселератора.
id определяет идентификатор пунктаменю
options указывает на некоторые возможныехарактеристики данного пункта меню:
CHECKED пункт меню отмечен галочкой.(невозможно для меню 0-го уровня)
INACTIVE пункт меню неактивен (его нельзявыбрать), но рисуется обычным способом
GRAYED пункт меню неактивен и нарисовансерым цветом
MENUBREAK пункт меню размещен либо в новойстроке (для уровня 0), либо в новом столбце.
MENUBARBREAK то же, что и MENUBREAK, ностолбец (строка) отделяется сплошной чертой
HELP обозначает пункт меню, связанный сподсказкой.
Для использования меню совместно с окном мы можемвоспользоваться любым удобным способом:
1) при регистрациикласса окна мы можем указать имя требуемого ресурса. Тогда все окна этогокласса при создании получат указанное меню.
2) мы можем указатьхендл меню при создании окна, тогда это окно будет создано с указанным меню.
3) в любой момент мыможем вызвать функцию
BOOL SetMenu( hWnd,hMenu );
с помощью которой мы можем установить новое меню, заменитьодно на другое или удалить имеющееся (указав hMenu=NULL).
Во время работы мы можем легко получить хендл меню,используемого данным окном:
HMENU GetMenu( hWnd);
и, зная хендл меню, столь же легко можем узнать хендл менюследующего уровня:
HMENU GetSubMenu(hMenu, nPos );
здесь параметр ‘nPos’ указывает номер пункта меню, связанногос меню следующего уровня.
Вы можете создать новое пустое меню, которое можноиспользовать как в качестве меню верхнего уровня, так и в качестве меню другихуровней. Это определяется только его применением. Если Вы его добавите кдругому меню, то оно будет POPUPменю, а если Вы его назначите окну, то оно будет меню верхнего уровня. Длясоздания нового меню предназначена функция
HMENU CreateMenu(void );
Кроме того мы можем узнать хендл системного меню данногоокна:
HMENU GetSystemMenu(hWnd, FALSE );
с помощью которого мы можем добавить новые пункты ксистемному меню, изменить или удалить прежние. При коррекции меню можноиспользовать два разных способа задания пункта меню:
1) по егоидентификатору (пункт, связанный с меню следующего уровня не имеетидентификатора, поэтому не может быть указан этим способом).
При выборе этого способа Вы должныуказать флаг MF_BYCOMMANDпри указании пункта меню.
2) по его номеру вменю. В этом случае Вы должны указать флаг MF_BYPOSITION призадании пункта.
При необходимости корректировать меню мы можемвоспользоваться следующими функциями:
BOOL AppendMenu(hMenu, nFlags, idNew, lpszNewName );
BOOL InsertMenu( hMenu, idItem,nFlags, idNew, lpszNewName );
BOOL ModifyMenu( hMenu, idItem,nFlags, idNew, lpszNewName );
BOOL DeleteMenu( hMenu, idItem,nFlags );
Эти четыре функции позволяют добавлять в конец меню,вставлять, изменять или удалять пункты меню. С их же помощью можноманипулировать с меню следующего уровня — добавлять, изменять или удалять.
BOOL CheckMenuItem(hMenu, idItem, nFlags );
BOOL EnableMenuItem( hMenu,idItem, nFlags );
BOOL HiliteMenuItem( hWnd, hMenu,idItem, nFlags );
С помощью этих функций можно отметить отдельный пункт менюгалочкой, запретить его или пометить выделенным. Меню может не перерисовываетсяпосле изменений, поэтому Вы должны, окончив все изменения, вызвать процедуру
void DrawMenuBar(hWnd );
которая перерисует меню. Конечно, если изменения делались вневидимом пункте меню, то его можно не перерисовывать.
Диалоги
Последний, и самый сложный вид ресурсов, рассматриваемый нами- диалог. Диалог представляет собой отдельное окно, называемое панелью диалога,с размещенными на его поверхностями управляющими элементами — кнопками, списками,статическими элементами и пр. Каждый управляющий элемент диалога являетсядочерним окном.
При работе с диалогами надо придерживаться основных правил ихописания. Все управляющие элементы составлены в список, по порядку их описания.При работе с диалогом перемещение по списку элементов осуществляется с помощьюкнопок стрелка вверх и стрелка влеводля перехода к ранее описанным, стрелка вниз и стрелка вправо — к позже описанным.
Обычно в диалоге выделяют несколько групп управляющихэлементов и в каждой группе описывают хотя бы один элемент со стилем WS_TABSTOP(стиль дочернего окна). С помощью клавиш Tab и Shift-Tab осуществляется перемещение от одного элемента с этимстилем к другому. То есть с помощью Tab можно осуществитьбыстрый переход от одной группе к другой.
Для переключения состояния элементов используется клавиша Space (не Enter!). Обычно в диалогевыделяют одну из кнопок, которая описывается как DEFPUSHBUTTON иименно эта кнопка “нажимается” клавишей Enter.
Помимо этого, если диалог использует AUTORADIOBUTTON,надо выделять отдельные группы кнопок, из которых только одна может бытьотмечена. Это делается с помощью стиля WS_GROUP. Управляющий элемент состилем WS_GROUPначинает новую группу элементов, которая заканчивается на следующем элементе сэтим стилем.
Для описания диалогов используется специальный ресурс — DIALOG,описывающий набор управляющих элементов, их стилей, размеров, положение напанели диалога и т.д. При описании диалога применяется совершенно специфичнаясистема координат, которая больше нигде не используется — она основана не нафизических единицах величина, а на долях величиные символа системного шрифта.Считается, что средний символ системного шрифта содержит 4 единицы диалогапо оси Xи 8единиц по оси Y.Если Вам надо самим определять реальные значений координат, то Вы можетевоспользоваться функциями
DWORD GetDialogBaseUnits(void );
void MapDialogRect( hWndDlg,lpRect );
Первая функция возвращает двойное слово, младшее словокоторого содержит размер символа системного шрифта по оси X, а старшее — пооси Y.Разделив эти числа на 4и 8Вы можете узнать цену единиц диалога.
Вторая функция преобразует координаты точек прямоугольника изсистемы координат диалога в систему координат экрана.Функции для создания диалогов
Для создания диалога существует 8 функций:
int DialogBox(hInstance, lpszDlgTemplate, hWndOwner, lpfnDlgProc );
intDialogBoxParam(
hInstance, lpszDlgTemplate,hWndOwner, lpfnDlgProc, lParamInit
);
intDialogBoxIndirect( hInstance, hglbDlgTemplate, hWndOwner, lpfnDlgProc );
intDialogBoxIndirectParam(
hInstance, hglbDlgTemplate,hWndOwner, lpfnDlgProc, lParamInit
);
HWNDCreateDialog( hInstance, lpszDlgTemplate, hWndOwner, lpfnDlgProc );
HWND CreateDialogParam(
hInstance, lpszDlgTemplate,hWndOwner, lpfnDlgProc, lParamInit
);
HWNDCreateDialogIndirect( hInstance, hglbDlgTemplate, hWndOwner, lpfnDlgProc );
HWNDCreateDialogIndirectParam(
hInstance, hglbDlgTemplate,hWndOwner, lpfnDlgProc, lParamInit
);
Для создания диалога необходимо передать соответствующейфункции структуру данных, описывающую этот диалог (то есть указывающую стили,размеры, положение и идентификаторы управляющих элементов и самого диалога).
Половина из перечисленных функций, содержащих слово ...Indirect…в названии, использует хендл глобального блока памяти hglbDlgTemplate вкотором должна размещаться такая структура. Вы должны сами позаботиться осоздании и заполнении этого блока данными.
Другая половина функций, не содержащих слова ...Indirect… вназвании, использует имя (номер) ресурса, описывающего эту структуру данных.При этом приложение должно содержать ресурс типа DIALOG, длякоторого компилатор ресурсов создает нужный блок данных. Вы можете самизагружать ресурс в блок глобальной памяти, что-либо корректировать в нем, еслиэто необходимо, и использовать функцию ...Indirect… для создания диалога.
Кроме информации о самом диалоге Вы должны указать хендлкопии приложения, с которой будет связано окно диалога, и которое содержиттребуемые ресурсы; хендл окна — пользователя диалога (о различии Owner и Parent мы уже говорили). Помимоэтого Вы должны указать адрес процедуры, обрабатывающей сообщения диалога lpfnDlgProc(об этой функции чуть позже). Это должен быть адрес функции, связанной с копиейприложения с помощью функции MakeProcInstance.
Когда окно диалога создается, оно дополнительно получитсообщение WM_INITDAILOG,которое используется для инициализации управляющих элементов. Вы можетепередать вместе с этим сообщением параметр lParam, содержащийнужные Вам данные. Для этого предназначены функции, содержащие слово ...Paramв названии.Модальныеи немодальные диалоги
Диалоги разделяются на два общих класса — модальные(modal) и немодальные (modeless) диалоги. Модальные диалогитребуют обязательного завершения для продолжения работы всего приложения.Пример – диалог для выбора файла в редакторе. До тех пор, пока файл не выбранпродолжение работы редактора бессмыслено.
Немодальные диалоги работают паралелльно с остальным приложением.Пример – диалог поиск/замена в большинстве редакторов. Вы можете перейти в окноредактора и поработать там, не завершая работу с диалогом. При этом Вы можетеоперировать как с диалогом, так и с остальными окнами Вашего приложения.
Разница между модальными и немодальными диалогами напрограммном уровне заключается в правилах обработки сообщений, поступающих кэтим диалогам.
Модальный диалог, работающий монопольно, должен исключитьпередачу сообщений к остальным окнам приложения. Для этого организуется новыйцикл обработки сообщений, обрабатывающий все сообщения, нужные диалогу, иисключающие обработку сообщений, направленных другим окнам приложения(исключаются, в основном, сообщения от клавиатуры и мыши).
Для создания модальных диалогов предназначены функции DialogBox...,которые создают требуемый диалог и организуют цикл обработки сообщений дляэтого диалога.
При этом надо быть достаточно аккуратным — Ваш главный циклобработки сообщений будет бездействовать, и какая-либо дополнительная обработкасообщений в нем будет игнорирована. Так, например, акселераторы, транслируемыев главном цикле обработки сообщений, не окажут никакого эффекта в модальномдиалоге.
Немодальный диалог, работающий параллельно с остальнымприложением, получает сообщения через главный цикл обработки сообщений. То естьокно диалога, вместе со всеми управляющими элементами, выступает в качествесамого обычного окна приложения.
Для создания немодального диалога Вам надо создать окнодиалога с помощью функции CreateDialog...,и предусмотреть специальную обработку сообщений для диалога в главном циклеобработки сообщений. Модификация главного цикла обработки сообщенийпроизводится следующим образом:
MSG msg;
HWND hWndModeless= NULL;
...
while (GetMessage( &msg, NULL, NULL, NULL ) ) {
if ( !hWndModeless ||!IsDialogMessage( hWndModeless, &msg ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
Порядокприменения функцийIsDialogMessage и TranslateAccelerator определяется желаемым эффектом: если Вам надо, что бы акселераторы использовались диалогом, то трансляцию акселератора надо производить до вызовафункции IsDialogMessage. Обычноэто не требуется и трансляция производится позже.
В некоторых случаях Вы можете с помощью CreateDialogиммитировать модальный диалог, организовав после создания окна дилогадополнительный цикл обработки сообщений. При этом Вы можете предусмотретьспециальную трансляцию некоторых сообщений.
Вы можете применять окна диалога в качестве главных оконприложения. Базовыеклассы окон
Для того, что бы упростить работу с диалогами Windowsсодержит специальную процедуру, определяющую новый базовыйкласс окон – класс диалогов.
Основное отличие базового класса от обычного классазаключается в том, что для базового класса не существует никаких структурданных, описывающих этот класс и не существует окон, принадлежащих этомуклассу. Базовый класс полностью определяется процедурой обработки сообщений,которой должны пользоваться все окна, построенные на этом базовом классе.
До сих пор мы сталкивались только с одним базовым классом — обычное перекрывающееся окно. Процедура обработки сообщений этого базовогокласса — DefWindowProc.
Для диалогов существует специальная функция DefDlgProc,определяющая базовый класс диалогов. При желании Вы можете создаватьсобственные классы диалогов, основанные на этом базовом классе так-же, как иобычные окна, применяя функцию RegisterClass.При этом Вы должны в структуре данных окна зарезервировать дополнительноепространство, размером .cbWndExtra=DLGWINDOWEXTRA (30байт). В этом случае Вы сможете создавать окна немодального диалога с помощьюфункции CreateWindow,либо указав имя зарегистрированного класса в шаблоне диалога.
Функция DefDlgProcвыполняет обработку нескольких дополнительных сообщений, которые необрабатываются (или редко используются) обычным окном:
WM_INITDIALOG инициализация диалога (этосообщение не посылается оконной функции диалога, оно передается только впроцедуру диалога)
WM_GETDLGCODE посылается управляющему элементудля выяснения ожидаемых управляющих сообщений
WM_NEXTDLGCTL установка фокуса на требуемыйуправляющий элемент, сообщение можно только посылать.
WM_PARENTNOTIFY извещение осоздании/удалении/”щелчке” мышкой
WM_ENTERIDLE модальный диалог или меню ожидаетввода данных. посылается диалогом или меню главному окну приложения.
DM_GETDEFID узнать идентификатор DEFPUSHBUTTON
DM_SETDEFID выбрать новую DEFPUSHBUTTON
Внимание! сообщения DM_GETDEFIDи DM_SETDEFIDимеют номера WM_USERи WM_USER+1,поэтому не используйте собственных сообщений WM_USER и WM_USER+1для посылки окну диалога!Функциядиалога
Для диалогов принят необычный способ, предусматривающийнестандартную обработку сообщений. Если для обычных окон мы обрабатываемсообщения сами, обращаясь к функции, выполняющей обработку по умолчанию, толькопри необходимости, то для диалогов предусмотрена специальная функция диалога,которая вызывается стандартной процедурой DefDlgProc.
Эта функция диалога, не являясь обычнойоконной процедурой, возвращает результат не в виде двойного слова, а ввиде логической величины:
/>
BOOL CALLBACK_export DlgProc( hWnd, wMsg, wPar, lPar ) {
return FALSE;
}
Функция DlgProcвозвращает FALSE,если сообщение надо обрабатывать стандартным образом и TRUE, еслисообщение обработано. Единственное исключение — сообщение WM_INITDIALOG, гдезначение TRUEуказывает на необходимость установить фокус на требуемый управляющий элемент, аFALSEговорит о том, что Вы уже установили фокус.
Обычно Вы пишете только DlgProc. Однако этафункция возвращает логическую величину, используемую процедурой DefDlgProc. Внекоторых случаях требуется возвращать конкретный конечный результат обработкисообщения (например сообщение WM_QUERYOPEN).
Вы можете сделать это с помощью функций GetWindowLong и SetWindowLong,указывая смещение DWL_MSGRESULTдля чтения/изменения возвращаемого по умолчанию значения.
Кроме того Вы можете изменить при желании адрес функциидиалога на новый, используя смещение DWL_DLGPROC для чтения/записи адресапроцедуры обработки сообщений.
Эти данные размещены в пространстве, добавляемом к структуре,описывающей окно, при его создании (DLGWINDOWEXTRA). Соответственно DWL_MSGRESULTи DWL_DLGPROCимеют положительные значения.
При разработки необходимых функций диалога надо учитыватьнекоторую разницу между модальным и немодальным диалогами. Все отличия можносвести к нескольким пунктам:
· модальный диалог завершается спомощью процедуры
void EndDialog(hWnd, wPar );
которая возвращает указанныйрезультат и прерывает цикл обработки сообщений, организованный процедурой DialogBox.
· немодальный диалог заканчивается приуничтожении окна диалога с помощью обычной функции DestroyWindow. Приэтом Вы должны принять меры, что бы в главном цикле обработки сообщений большене вызывалась процедура IsDialogMessageдля этого диалога.
· так как функция создания немодальногодиалога возвращает хендл окна диалога, то мы можем обойтись без функциидиалога, поступая обычным способом — написав оконную процедуру, использующую в качествефункции обработки сообщений по умолчанию процедуру DefDlgProc. Приэтом мы указываем NULLв качестве адреса функции диалога и, после создания окна, заменяем адреспроцедуры обработки сообщений на собственный (по–сути применяя прием порожденияподкласса окон).
При этом обработка сообщений диалога может быть изображенаследующей схемой:
/>
Если мы указали адрес функции диалога NULL, то DlgProc,изображенная на этой схеме, вызываться не будет. Рассмотрим небольшой пример:
FARPROC lpfnOwnProc;
//новая оконная процедура
LONG CALLBACK _export OwnDlgProc(
HWND hWnd, UINT wMsg, UINT wPar,LONG lPar
) {
switch ( wMsg ) // нестандартная обработка сообщений
case WM_CTLCOLOR:
return ...;
default:
break;
}
return DefDlgProc( hWnd, wMsg,wPar, lPar );
}
// вкакой–либо иной процедуре:
// созданиенемодального диалога
HWND hModeless;
lpfnOwnProc= MakeProcInstance((FARPROC)OwnDlgProc, hInstance );
hModeless=CreateDialog( hInstance, “my_res”, hWndOwner, NULL );
SetWindowLong( hModeless,GWL_WNDPROC, (LONG)lpfnOwnProc );
// Внимание! Так какподстановка процедуры осуществляется после
// создания окна, топервые сообщения, включая WM_INITDIALOG
// уже обработаныстандартной функцией
...
// после закрытияокна диалога
FreeProcInstance( lpfnOwnDlgProc );
Вообще нам может понадобиться порождать подкласс и отмодального диалога. В этом случае подмену процедуры обработки сообщений лучшепроизводить в функции диалога при обработке сообщения WM_INITDIALOG:
FARPROC lpfnOwnProc;
LONG PASCAL FAR_export OwnDlgProc(
HWND hWnd, UINT wMsg, UINT wPar,LONG lPar
) {
// см. выше
}
FARPROC lpfnDlgProc;
BOOL PASCAL FAR _export DlgProc(
HWND hWnd, UINT wMsg, UINT wPar,LONG lPar
) {
switch ( wMsg ) {
case WM_INITDIALOG:
/*
установитьновую оконную процедуру и запретить вызов
даннойфункции диалога:
*/
SetWindowLong( hWnd, GWL_WNDPROC,(LONG)lpfnOwnProc );
SetWindowLong( hWnd, DWL_DLGPROC,(LONG)NULL );
/*
еслимы устанавливаем новую функцию при обработке сообщения
WM_INITDIALOG,то наша новая функция его уже не получит. Поэтому нам
надопослать какое-либо специальное сообщение или вызвать отдельную
функциюдля первоначальной инициализации диалога.
*/
return TRUE;
default:
break;
}
return FALSE;
}
// вкакой–либо иной процедуре:
// вызов модальногодиалога
...
lpfnDlgProc= MakeProcInstance((FARPROC)DlgProc, hInstance );
lpfnOwnProc= MakeProcInstance((FARPROC)OwnDlgProc, hInstance );
int answer;
answer=DialogBox( hInstance, “my_res”, hWndOwner, lpfnDlgProc );
FreeProcInstance(lpfnDlgProc );
FreeProcInstance( lpfnOwnDlgProc );Функции для управления диалогом
Windows содержит достаточно большое количество функций,применяемых при работе с диалогами, что бы их здесь не рассматривать подробно.Мы попробуем только лишь выделить некоторые из них и дать короткуюхарактеристику.
int GetDlgCtrlID(hwndControl );
HWND GetDlgItem( hwndDlg, nCtrlId);
Эти функции позволяют определить идентификатор управояющегоэлемента диалога по его хендлу или хендл управляющего элемента по егоидентификатору и хендлу диалога.
LONG SendDlgItemMessage(hwndDlg, nCtrlId, wMsg, wPar, lPar );
Эта функция используется для посылки сообщения конкретномууправляющему элементу диалога.
Следующая группа функций может задавать текст управляющегоэлемента в виде числа или строки.
void SetDlgItemInt(hwndDlg, nCtrlId, nValue, bSigned );
UINT GetDlgItemInt( hwndDlg,nCtrlId, lpbOk, bSigned );
void SetDlgItemText( hwndDlg,nCtrlId, lpszString );
int GetDlgItemText(hwndDlg, nCtrlId, lpsBuffer, nMaxCount );
Еще несколько функций предназначены для работы с кнопкамиразных видов:
void CheckDlgButton(hwndDlg, nCtrlId, nCheck );
void CheckRadioButton(hwndDlg, nCtrlFirst, nCtrlLast, nCheck );
UINT IsDlgButtonChecked( hwndDlg,nCtrlId );
Для выбора файлов с помощью списков разного вида. Эти функцииодновременно обслуживают список с именами файлов и статический текст, в которомпредставлен текущий путь:
int DlgDirList(hwndDlg, lpszPath, idListBox, idText, nFileType );
int DlgDirListComboBox(hwndDlg, lpszPath, idComboBox, idText, nFileType );
BOOL DlgDirSelect( hwndDlg,lpszPath, idListBox );
BOOL DlgDirSelectEx( hwndDlg,lpszPath, nMaxCount, idListBox );
BOOL DlgDirSelectComboBox(hwndDlg, lpszPath, idComboBox );
BOOL DlgDirSelectComboBoxEx(hwndDlg, lpszPath, nMaxCount, idComboBox );
При необходимости программной передачи управления могутпригодиться следующие функции:
HWND GetNextDlgGroupItem(hwndDlg, hwndCtrl, bPreviouse );
HWND GetNextDlgTabItem( hwndDlg,hwndCtrl, bPreviouse );
Напоследок несколько особенностей диалога.
Во‑первых, надо очень аккуратно применять элемент типа DEFPUSHBUTTON,так как он может “перехватывать” клавишу Enter у другихэлементов диалога, даже если эта клавиша необходима для их нормальной работы.
Так, если при работе в COMBOBOX вы нажмете Enterдля выбора текущего элемента из списка, DEFPUSHBUTTON может перехватить этосообщение, соответственно возьмет на себя фокус ввода, а COMBOBOXотреагирует на это как на отмену выбора элемента.
Во‑вторых, могут возникнуть сложности с раскраской управляющихэлементов диалога и самой панели далога. Если вы будете обрабатывать сообщение WM_CTLCOLORфункцией диалога, то для возвращения хендла кисти Вам надо устанавливать поле DWL_MSGRESULTструктуры окна диалога.
Однако, диалог выполняет некоторую обработку этого сообщенияпосле того, как Вы вернули ответ. При этом он руководствуется своими правиламидля назначения кисти и может вовсе заменить назначенную Вами на желаемую им.
При необходимости управлять цветом элементов диалогаэффективно может использоваться прием порождения подкласса от диалога – когдаВы можете обрабатывать WM_CTLCOLORсамостоятельно и не использовать стандартной обработки этого сообщения.