Автор: Роман Панышев (irrona)
Win32 API содержит множество функций, сообщений и встроенных элементов управления, помогающих разработчикам создавать и сопровождать диалоговые окна. Данная статья содержит следующие разделы, посвящённые диалоговым окнам в WIndows:
Во многих приложениях диалоговые окна используются в качестве вспомогательных окон, обеспечивающих взаимодействие пользователя с программой. Они служат:
Таким образом, в Windows используются два типа диалоговых окон: модальные и немодальные. Отличие их состоит в том, что модальное окно необходимо закрыть, прежде чем Вы получите возможность работать с основным окном приложения, а немодальное окно можно свернуть или сдвинуть в сторону и оно не будет мешать работе с основным окном. Создавать и обслуживать легче всего модальные диалоговые окна, поскольку для их создания, отслеживания сообщений и уничтожения используется одна оконная функция.
Для создания любого типа диалоговых окон нужен шаблон диалогового окна, в котором указаны стили окна и перечислены элементы управления, находящиеся в окне (для всех элементов управления указываются их названия, подписи, размеры, стили и координаты). Кроме этого нужна оконная процедура диалогового окна, в которой можно будет отлавливать и обрабатывать сообщения, поступающие от системы и элементов управления. Шаблон диалогового окна можно создать вручную (для этого необходимо знать синтаксис - если интересует загляните в файл справки rc.hlp в каталоге bin установленного masm32), либо с помощью редакторов ресурсов (некоторые средства разработки содержат встроенные редакторы ресурсов, например в WinAsm и RadAsm имеются таковые). Созданный текстовой файл сохраняется с расширением RC и затем, с помощью утилиты rc.exe компилируется в бинарный (двоичный) ресурсный файл с расширением RES. Полученный таким образом шаблон диалогового окна можно слинковать с исполняемым файлом приложения, либо загружать в память во время работы приложения. Процедура диалогового окна - это функция обратного вызова, которую операционная система вызывает при взаимодействии с диалоговым окном конечного пользователя. Хотя процедура окна диалога и схожа с процедурой основного окна приложения, некоторые отличия всё же имеются. Но об этом ниже.
Создать диалоговое окно в приложении можно используя либо функцию DialogBox,
invoke DialogBox, hInstance, lpTemplate, hWndParent, lpDialogFunc ; где lpTemplate - указатель на имя ; или идентификатор шаблона диалогового окна, ; hWndParent - идентификатор окна-владельца, ; lpDialogFunc - точка входа в процедуру диалогового окна.
создающую модальное диалогове окно, либо функцию CreateDialog,
invoke CreateDialog, hInstance, lpTemplate, hWndParent, lpDialogFunc ; описание параметров см. выше
создающую немодальное диалогове окно. Обе эти функции загружают шаблон диалогового окна из ресурса, слинкованного с файлом приложения, и создают всплывающее окно, соответствующее спецификации шаблона. Кроме этого имеются и другие функции, позволяющие загрузить шаблон диалога в память.
Операционная система использует встроенный специальный класс окна для создания диалоговых окон. Также, после создания окна диалога, система создает специальную оконную процедуру обработки сообщений для этого окна. Изменить внутренние (системные) стили и процедуру Вы не можете. Единственный путь взаимодействия с диалоговым окном - это использование процедуры диалогового окна, написанной вами. Правда есть одно утешение - этой процедуры вам должно хватить с лихвой, как для управления самим окном, так и для управления элементами управления, расположенными на нем.
Большинство диалоговых окон имеют окно-владельца (проще, родителя). Как вы помните, при создании диалога необходимо указывать идентификатор окна-владельца. Система использует этот идентификатор для установки позиции диалогового окна и размещения его поверх (впереди) родительского окна. Кроме этого, система отсылает родительскому окну сообщения о событиях, происходящих в диалоговом окне.
Операционная система автоматически скрывает или уничтожает диалоговое окно при скрытии или уничтожении его владельца. Для нас это означает одно - нет необходимости отслеживать состояние родительского окна, за этим приглядывает сама система. Как говорится, баба с возу...
Теоретически, конечно, можно создать диалоговое окно без указания родителя. Но фирма Microsoft настоятельно не рекомендует этого делать. Поскольку в таком случае система не несет никакой ответственности за поведение окна диалога, вам придется самим обеспечивать это поведение. А это, мне кажется, довольно большая и нудная работа. Хотя... может быть для каких-то особых целей вам сможет это понадобиться... дерзайте.
Окно сообщения - специальный вид модального диалогового окна, используемый для вывода на экран сообщений для пользователя или для получения от пользователя информации, необходимой для дальнейшей работы программы. Окно сообщения обычно содержит заголовок, само сообщение, рисунок(иконку, пиктограмму) и одну или более кнопок. Для создания окна сообщения используйте функции MessageBox, MessageBoxEx или их юникод-аналоги MessageBoxW или MessageBoxExW.
invoke MessageBox, hWnd, lpText, lpCaption, uType ; где hWnd - идентификатор родителя, ; lpText - текст сообщения, ; lpCaption - текст заголовка, ; uType - иконки+кнопки invoke MessageBoxEx, hWnd, lpText, lpCaption, uType, wLanguageId ; идентификатор языка
Для указания кнопок окна сообщения используют следующие битовые константы:
Константа | Значение (Hex) | Описание |
MB_OK | 0 | Окно сообщения содержит только одну кнопку OK. Это стиль по-умолчанию. |
MB_OKCANCEL | 1 | Кнопки OK и Cancel (Отмена). |
MB_ABORTRETRYIGNORE | 2 | Кнопки Abort (Прервать), Retry (Повторить), Ignore (Игнорировать). |
MB_YESNOCANCEL | 3 | Кнопки Yes (Да), No (Нет), Cancel (Отмена). |
MB_YESNO | 4 | Кнопки Yes (Да) и No (Нет). |
MB_RETRYCANCEL | 5 | Кнопки Retry (Повторить) и Cancel (Отмена). |
MB_CANCELTRYCONTINUE | 6 | Кнопки Cancel (Отмена), Try (Повторить), Continue (Продолжить). Используйте вместо MB_ABORTRETRYIGNORE. |
MB_HELP | 4000 | Добавляет к существующим кнопкам кнопку Help (Помощь). При нажатии кнопки Help (Помощь) система посылает родительскому окну сообщение WM_HELP. |
Для указания иконок окна сообщения используют следующие битовые константы:
Константа | Значение (Hex) | Описание |
MB_ICONSTOP MB_ICONERROR MB_ICONHAND |
10 | Позволяет вставить в окно соощения иконку знака "Стоп". |
MB_ICONQUESTION | 20 | Иконка вопросительного знака. |
MB_ICONEXCLAMATION MB_ICONWARNING |
30 | Иконка восклицательного знака. |
MB_ICONINFORMATION MB_ICONASTERISK |
40 | Иконка с буквой i в круге. |
Для указания выделенной по-умолчанию кнопки используют следующие битовые константы:
Константа | Значение (Hex) | Описание |
MB_DEFBUTTON1 | 0 | Выделена первая кнопка. |
MB_DEFBUTTON2 | 100 | Выделена вторая кнопка. |
MB_DEFBUTTON3 | 200 | Выделена третья кнопка. |
MB_DEFBUTTON4 | 300 | Выделена четвертая кнопка. |
Для указания модальности окна сообщения используют следующие битовые константы:
Константа | Значение (Hex) | Описание |
MB_APPLMODAL | 0 | Устанавливается по-умолчанию. Пользователь должен закрыть окно сообщения, чтобы продолжить работу с родительским окном. Пользователь может взаимодействовать с окнами других процессов. Если это позволяет иерархия окон приложения, в некоторых случаях пользователь может взаимодействовать с другими окнами текущего приложения. Все дочерние окна родительского окна неактивны, но всплывающие окна - остаются активными. |
MB_SYSTEMMODAL | 1000 | Поведение такое же, как у MB_APPLMODAL. Отличие состоит в том, что при создании окна применяется стиль WS_EX_TOPMOST, что позволяет выводить окно поверх всех открытых окон системы. Используйте этот стиль в тех случаях, когда необходимо сообщить пользователю о серьёзных ошибках, требующих его срочного вмешательства. |
MB_TASKMODAL | 2000 | Поведение такое же, как у MB_APPLMODAL. отличие состоит в том, что в случае отсутствия идентификатора родителя, деактивируются все окна верхнего уровня, принадлежащие данному процессу. Удобно использовать, например при написании библиотек. Поскольку в библиотеке родительского окна нет, применение такого флага позволяет деактивировать окна приложения, использующего данную библиотеку. |
Дополнительные стили окна сообщения устанавливаются следующими битовыми константами:
Константа | Значение (Hex) | Описание |
MB_SETFOREGROUND | 10000 | Окно сообщения выводится на передний план. На самом деле ОС вызывает функцию SetForegroundWindow. |
MB_DEFAULT_DESKTOP_ONLY | 20000 | В Windows 95/98 этот флаг эффекта не имеет. В Windows NT 4.0 и более поздних этот флаг работает наподобие флага MB_SERVICE_NOTIFICATION, с оговоркой, что система выводит окно сообщения на рабочий стол, принятый по-умолчанию. |
MB_SERVICE_NOTIFICATION_NT3X | 40000 | Работает только в Windows NT 4.0 и более поздних. Данный флаг связан с флагом MB_SERVICE_NOTIFICATION, используемым в Windows NT 3.51. |
MB_TOPMOST | 40000 | Окно сообщения создается с использованием стиля WS_EX_TOPMOST. |
MB_RIGHT | 80000 | Текст в окне сообщения выравнивается по правому краю. |
MB_RTLREADING | 100000 | Выводит текст заголовка и сообщения в формате чтения справа-навлево (RTL) на системах с арабским языком и ивритом. |
MB_SERVICE_NOTIFICATION | 200000 |
в Windows NT 4.0 и более поздних данный флаг используется в окнах сообщений, вызываемых из сервисов Windows. Окно сообщения выводится на активный рабочий стол системы, даже если пользователь не входил в систему. При создании окна сообщения установите параметр hWnd в NULL, поскольку диалог может выводится на рабочем столе, заданном иным идентификатором, отличным от активного. |
Несмотря на то, что окно сообщения является диалоговым окном, операционная система берет на себя заботу о создании и отображении этого окна. Т.е. при создании окна сообщения не нужно создавать шаблон и процедуру диалогового окна. Система создает свой собственный шаблон, основываясь на битовых значениях, перечисленных выше.
В то же время, поскольку окно сообщения все-таки является диалоговым окном, система (если указан параметр hWnd) посылает родительскому окну сообщения WM_CANCELMODE (1Fh) и WM_ENABLE (0Ah). Соответственно Вы получаете возможность обрабатывать эти сообщения.
Модальное окно - это всплывающее окно с системным меню, заголовком и тонкой границей. Это значит, что шаблон окна должен содержать стили WS_POPUP (80000000h), WS_SYSMENU (80000h), WS_CAPTION (0C00000h) и DS_MODALFRAME (0080h). Стиль WS_VISIBLE (10000000h) можно не указывать, поскольку система сделает диалог видимым независимо от того, указан данный стиль или нет. Модальный диалог не может иметь стиль WS_CHILD (40000000h), иначе после создания диалог будет заблокирован и получить доступ к нему будет невозможно.
Модальное диалоговое окно можно создать функциями DialogBox или DialogBoxIndirect.
invoke DialogBox, hInstance, lpTemplate, hWndParent, lpDialogFunc invoke DialogBoxIndirect, hInstance, lpTemplate, hWndParent, lpDialogFunc
Отличие последней состоит в том, что параметр lpTemplate должен быть не указателем на шаблон диалогового окна, а указателем на глобальную переменную в памяти компьютера, которая содержит загруженный шаблон диалогового окна. Шаблон диалога для этой функции должен содержать заголовок, описывающий окно диалога, за которым следуют дополнительные блоки данных, описывающие элементы управления, располагаемые в диалоговом окне. Можно использовать стандартный формат шаблона или расширенный.
Стандартный формат включает две основных структуры DLGTEMPLATE для заголовка и DLGITEMTEMPLATE для элементов управления.
Расширенный формат также включает две основных структуры DLGTEMPLATEEX для заголовка и DLGITEMTEMPLATEEX для элементов управления.
Рассмотрением этих структур займемся ниже. Функции DialogBoxParam и DialogBoxIndirectParam также создают модальные диалоговые окна. Они идентичны функциям, указанным выше, но имеют один дополнительный параметр, в котором можно указать какое-нибудь значение, которое система при создании окна диалога перешлет в конную процедуру диалогового окна. Получить это значение можно в параметре lParam сообщения WM_INITDIALOG (110h). Разница между этими функциями также состоит во втором параметре, касающемся шаблона диалогового окна.
invoke DialogBoxParam, hInstance, lpTemplateName, hWndParent, lpDialogFunc, dwInitParam invoke DialogBoxIndirectParam, hInstance, hDialogTemplate, hWndParent, lpDialogFunc, dwInitParam
Как Вы думаете, что возвращают эти четыре функции по окончании своей работы по созданию модального диалогового окна? Кроме идентификатора диалогового окна, как сообщает MSDN, они возвращают некое 32-битное значение nResult, которое затем становится возвращаемым значением функции EndDialog. При неверном hWndParent, они возвращают 0. А при возникновении любой другой ошибки возвращают -1.
После создания модальное диалоговое окно становится активным окном и остается таковым пока не будет уничтожено функцией EndDialog.
invoke EndDialog, hDlg, nResult
Вот он и вылез, наш пресловутый nResult. Благодаря ему родительское окно может определить насколько удачно прошло создание окна диалога.
При создании модального диалога, система посылает родительскому окну (если таковое имеется) сообщение WM_CANCELMODE (1Fh). Получив это сообщение, приложение должно перевести фокус ввода в созданное диалоговое окно. Обработка сообщений модального диалогового окна происходит в собственном цикле обработки сообщений в процедуре модального окна диалога. Если сообщение, полученное этой процедурой не имеет отношения к диалоговому окну, например сообщение WM_QUIT (12h), оно пересылается родительскому окну на обработку.
Немодальное окно - это всплывающее окно с системным меню, заголовком и тонкой границей. Это значит, что шаблон окна должен содержать стили WS_POPUP (80000000h), WS_SYSMENU (80000h), WS_CAPTION (0C00000h) и WS_BORDER (800000h). Если Вы не укажете стиль WS_VISIBLE (10000000h), система не отобразит диалог на экране монитора. В таком случае, после создания диалога, можно заставить его проявиться с помощью функции ShowWindow.
invoke ShowWindow, hWnd, nCmdShow
Немодальные диалоговые окна, также как и модальные, создаются с помощью четырех функций: DialogBox, DialogBoxIndirect, DialogBoxParam и DialogBoxIndirectParam (см. выше).
После создания немодальное диалоговое окно становится активным окном, но пользователь волен перевести фокус ввода в любое другое окно, расположенное на экране. Немодальное окно не может возвратить значение родительскому окну, как это делает модальное окно с помощью параметра nResult. Но оно может отсылать сообщения родительскому окну, используя функцию SendMessage.
invoke SendMessage, hWnd, Msg, wParam, lParam
Уничтожить диалоговое окно можно вызовом функции DestroyWindow, Которую можно привязать, например, на нажатие кнопки "Отмена" диалога. Если же в процедуре диалогового окна отсутствует вызов этой функции, тогда родительское окно перед закрытием должно вызвать DestroyWindow для диалога. Для закрытия немодальных диалоговых окон нельзя использовать функцию EndDialog.
invoke DestroyWindow, hWnd
Шаблон диалогового окна - это бинарные (двоичные) данные, описывающие координаты, размеры, стиль и элементы управления диалогового окна. При создании диалогового окна система загружает шаблон из ресурса запускаемой программы или использует глобальную переменную памяти, в которую загружен шаблон диалогового окна. В любом случае, независимо от типа загрузки, Вам необходимо научиться создавать и использовать шаблоны диалоговых окон.
Шаблоны диалоговых окон сначала создаются в текстовом виде, согласно требований операционной системы и компилятора ресурсов. В masm32 текстовой файл шаблона ресурсов имеет расширение RC. После чего, используя компилятор ресурсов, текстовой файл ресурсов компилируется в двоичный вид. В masm32 двоичный файл ресурса имеет расширение RES.
Для создания простого диалогового окна, создайте новый файл с расширением RC и вставьте в него следующий шаблон диалогового окна:
#define IDD_DLG 1001 #define IDC_BUTTON 1002 IDD_DLG DIALOGEX 0,0,221,127 CAPTION "Самостоятельное диалоговое окно" FONT 8,"MS Sans Serif" STYLE 0x10cc0800 EXSTYLE 0x00000000 BEGIN CONTROL "Закрыть окно",IDC_BUTTON,"Button",0x50010000,73,80,74,22,0x00000000 END
В этом шаблоне объявлено окно диалога с псевдоидентификатором IDD_DLG равным 1001. Окно создаем с использованием расширенного стиля DIALOGEX. Размеры окна 221x127 dbu. Заголовок окна будет содержать текст "Самостоятельное диалоговое окно". Шрифт, используемый для текста диалогового окна и элементов управления, "MS Sans Serif" размером 8 кеглей. Стиль окна 10cc0800h, что означает WS_VISIBLE or WS_CAPTION or WS_SYSMENU or WS_THICKFRAME or DS_CENTER. Расширенных стилей нет. В окне будет присутствовать один элемент управления "Кнопка (Button)" с псевдоидентификатором IDC_BUTTON равным 1002 и текстом "Закрыть окно". Кнопка будет располагаться по координатам 73 dbu от верха окна и 80 dbu слева. Размеры кнопки 74x22 dbu. Стиль 50010000h. Кстати, заметьте, что константы стилей заданы в нотации языка С - это требование компилятора ресурсов. Вместо малопонятных цифр можно использовать текстовые описания констант, но для этого необходимо подключить файл resource.h, в котором определены основные константы стилей и несколько изменить шаблон.
#include "resource.h" ; включаемый файл должен находиться в каталоге с файлом шаблона #define IDD_DLG 1001 #define IDC_BUTTON 1002 IDD_DLG DIALOGEX 0,0,221,127 CAPTION "Самостоятельное диалоговое окно" FONT 8,"MS Sans Serif" STYLE WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | DS_CENTER BEGIN CONTROL "Закрыть окно",IDC_BUTTON,"Button",WS_CHILD | WS_VISIBLE | WS_TABSTOP,73,80,74,22 END
Думаю во всем этом единственную трудность вызывает размерность dbu. Это я сократил сочетание слов "dialog base unit" (базовая единица диалога). Дело в том, что, в отличие от пикселов, принятых при задании координат и размеров основных окон, при создании диалоговых окон используется специальная единица, которую Microsoft называет единицей диалога. Эта контекстно-независимая единица принята в качестве стандарта для того, чтобы окно диалога смотрелось одинаково при любом разрешении экрана монитора. Пересчитать эту размерность в экранные координаты (пикселы) несложно, если знать как. Эта величина зависит от размера шрифта, указанного в шаблоне диалогового окна. Вертикальная dbu равна 1/4 средней ширины символа системного шрифта. Горизонтальная dbu равна 1/8 средней высоты символа системного шрифта. Псевдокод для вычисления количества пикселей для вертикальной dbu и горизонтальной dbu соответственно приведен ниже:
количество пикселей по горизонтали = 2 * горизонтальная dbu * (средняя ширина символа диалогового шрифта / средняя ширина символа системного шрифта) количество пикселей по вертикали = 2 * вертикальная dbu * (средняя высота символа диалогового шрифта / средняя высота символа системного шрифта)
Средние ширину и высоту системного шрифта можно получить примерно так:
.data avgWidth dd 0 avgHeight dd 0 alphabet db "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 0 .data? hdc HDC ? hFont HFONT ? hFontOld dd ? tm TEXTMETRIC <> size SIZE <> .code invoke SelectObject, hdc, hFont mov hFontOld, eax invoke GetTextMetrics, hdc, addr tm invoke GetTextExtentPoint32, hdc, addr alphabet, 52, addr size ; Вычисляем среднюю ширину символа ; avgWidth = (size.cx/26+1)/2 xor edx, edx mov eax, size.cx mov ecx, 26 div ecx inc eax shr eax, 1 mov avgWidth, eax ; Средняя высота символа mov eax, tm.tmHeight mov avgHeight, eax
Но проще всего сконвертировать dbu в пикселы можно с помощью функции MapDialogRect.
invoke MapDialogRect, hDlg, lpRect ; где lpRect - структура RECT ; на входе заполняете её координатами диалога; ; на выходе получаете в ней экранные координаты (пикселы)
Ну, так, мы сильно отвлеклись. После написания шаблона диалогового окна, его необходимо перевести в двоичный вид. Используем для этого утилиту rc.exe. В командной строке необходимо написать следующее,:
rc.exe путь_к_файлу_шаблона_диалога.rc
и нажать Ввод. В результате в том же каталоге Вы получите скомпилированный ресурс в файле с расширением RES.
Итак, у нас есть шаблон диалога. Время написать саму программу.
.386 .model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib DialogProc proto :DWORD, :DWORD, :DWORD, :DWORD .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke GetModuleHandle, NULL mov hInstance, eax invoke GetCommandLine mov CommandLine, eax invoke DialogBoxParam, hInstance, 1001, NULL, addr DialogProc, 0 invoke ExitProcess, eax DialogProc proc hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg == WM_INITDIALOG .elseif uMsg == WM_COMMAND invoke SendMessage, hDlg, WM_CLOSE, 0, 0 .elseif uMsg == WM_CLOSE invoke EndDialog, hDlg, 0 ret .endif xor eax, eax ret DialogProc endp end start
Ассемблируем программу и линкуем с шаблоном диалога:
ML.EXE /c /coff путь_к_файлу_программы.asm LINK.EXE /SUBSYSTEM:WINDOWS /OPT:NOREF путь_к_файлу_программы.obj путь_к_шаблону_диалога.res
Всё. Исходный код с примером создания диалогового окна находится в файле sample_5_1.rar.
На примере Вы могли увидеть, что при создании шаблона диалогового окна необходимо указать один или более стилей окна. Стили диалоговых окон могут включать как обычные оконные стили (WS_POPUP (80000000h) или WS_SYSMENU (80000h)), так и специализированные стили диалоговых окон (DS_MODALFRAME (0080h) или DS_CENTER (800h)). При создании диалогового окна система посылает указанные в параметре STYLE шаблона стили в параметр dwStyle, а стили, указанные в параметре EXSTYLE шаблона - в параметр dwExStyle функции CreateWindowEx.
Зачастую, диалоговые окна являются всплывающими окнами с меню и заголовком. Шаблон диалога должен содержать стиль WS_BORDER (800000h) для создания немодального и стиль DS_MODALFRAME (0080h) для создания модального диалогового окна. Если шаблон будет содержать какой-либо стиль, отличный от WS_POPUP (80000000h), например WS_OVERLAPPED (0), то в результате Вы получите не диалоговое, а пользовательское окно с указанными стилями.
В таблице ниже приведены стили, которые можно указывать в шаблоне диалогового окна. Обратите внимание, что некоторые из этих стилей подходят только для определенной операционной системы, а некоторые уже устарели и применение их не дает никакого эффекта.
Константа | Значение (Hex) | Описание |
DS_ABSALIGN | 1 | Указывает на то, что координаты диалогового окна относительны экрану, иначе они относительны клиентской области родительского окна. |
DS_SYSMODAL | 2 | Стиль устаревший. Применялся на 16-битных системах. |
DS_3DLOOK | 4 | Использовался только на Windows NT 3.51 для создания 3-мерной границы диалогового окна. На остальных системах такая граница создается автоматически. |
DS_FIXEDSYS | 8 | Заставляет диалоговое окно использовать SYSTEM_FIXED_FONT вместо SYSTEM_FONT. Это моноширный шрифт с системным шрифтом 16-битных систем Windows более ранних, чем Windows 3.0. |
DS_NOFAILCREATE | 10 | Стиль применялся в Windows 95/98. Позволял системе создать диалоговое окно даже при наличии ошибок при создании окна. |
DS_LOCALEDIT | 20 | Стиль устаревший. Применялся на 16-битных системах. |
DS_SETFONT | 40 |
Если указан - это значит, что шаблон диалогового окна содержит параметр FONT, в котором разработчик указывает имя и размер шрифта для диалогового окна и его элементов управления. Если не указан ни DS_SETFONT, ни DS_SHELLFONT, значит шаблон не содержит параметра FONT. |
DS_SHELLFONT | 48 | Является комбинацией стилей DS_SHELLFONT и FIXEDSYS. Позволяет создать одно диалоговое окно, которое будет иметь современный вид в Windows 2000 и более новых, и "классический" вид на Windows 95/98/NT4. |
DS_MODALFRAME | 80 | Позволяет создать модальное диалоговое окно. |
DS_NOIDLEMSG | 100 | Запрещает отправку родительскому окну сообщения WM_ENTERIDLE. |
DS_SETFOREGROUND | 200 |
Позволяет системе использовать функцию SetForegroundWindow для вывода окна диалога на передний план. Служит для привлечения особого внимания пользователя к диалогу. В Windows 98/2000 система сама решает какой процесс имеет право выводить окна на передний план. |
DS_CONTROL | 400 | Создает диалоговое окно, которое является дочерним по отношению к другому диалоговому окну. Например, приложение-мастер является диалоговым окном, вкладками которого являются другие (дочерние) диалоговые окна. Это позволяет перемещаться между дочерними диалогами как между элементами управления, используя назначенные комбинации клавиш. |
DS_CENTER | 800 | Центрирует диалоговое окно относительно клиентской области родительского окна или, если "родитель" не задан, относительно экрана. |
DS_CENTERMOUSE | 1000 | Центрирует диалоговое окно относительно курсора. |
DS_CONTEXTHELP | 2000 | Создает в заголовке окна диалога кнопку с изображением вопросительного знака. При нажатии её, курсор принимает вид вопросительного знака, пока не будет выбран какой-либо из элементов управления диалогового окна. После выбора элемента управления, он получает сообщение WM_HELP, которое должен передать процедуре диалогового окна на обработку. Для обработки Вы можете вызвать функцию WinHelp с командой HELP_WM_HELP и указанием файла справки для элемента управления. |
О последнем стиле можно и подробнее. Функция WinHelp позволяет отображать контекстную справку по элементу управления или действию, либо работать с отдельными опциями справочной системы. Правда работает она только со старым форматом справочных файлов с расширением hlp. Пример использования функции приведен ниже.
.data hfile db "c:\masm32\help\ASMINTRO.HLP", 0 .code .if uMsg == WM_HELP invoke WinHelp, hDlg, addr hfile, HELP_FORCEFILE, 0
Кроме указания начальной позиции и размеров диалогового окна, его шаблон может содержать описание элементов управления, располагаемых в окне. Для каждого элемента управления необходимо указать его псевдоидентификатор (неповторяющийся в пределах приложения номер), тип (класс, на основе которого он создается), размеры, позицию (относительно клиентской области диалогового окна) и стили. Операционная система использует эту информацию при вызове функции CreateWindowEx, используемую для создания элементов управления. Функция вызывается для каждого элемента управления отдельно и в порядке их указания в шаблоне. Каждый элемент управления должен содержать стиль WS_CHILD (40000000h), поскольку все они являются дочерними окнами по отношению к окну диалога. Чтобы элемент управления стал видимым после создания диалога, не забудьте выставить стиль WS_VISIBLE (10000000h). Другие стили, такие как WS_BORDER (800000h), WS_DISABLED (8000000h), WS_TABSTOP (10000h) или WS_GROUP (20000h) указываются по мере надобности. Шаблон может также содержать стили, специфичные для данного класса элементов управления. Перед использованием специальных стилей различных элементов управления, ознакомьтесь со следующими статьями из MSDN:
Псевдоидентификатор необходим элементу управления, чтобы процедура диалогового окна могла понять от которого из них поступило сообщение WM_COMMAND (111h). Единственный элемент управления, которому не обязателен идентификатор, элемент управления STATIC, - просто он не посылает сообщений WM_COMMAND (111h).
Обычно при создании диалогового окна на нем располагают кнопки OK и CANCEL. Этим кнопкам назначают соответственно идентификаторы IDOK (1) и IDCANCEL (2), которые при получении диалоговым окном сообщения WM_COMMAND (111h), можно извлечь из параметра wParam оконной процедуры. Кстати системное меню (у диалогового окна оно содержит только два-три активных пункта: переместить и закрыть, а при использовании стиля WS_THICKFRAME еще и пункт размер), при выборе пункта "Закрыть", посылает сообщение WM_COMMAND (111h) с wParam, установленным в IDCANCEL (2).
Как уже упоминалось выше, для вычисления размеров и позиции диалогового окна, операционная система использует среднюю ширину символа. По-умолчанию, система отрисовывает весь текст на диалоговом окне, используя шрифт SYSTEM_FONT (13). Если желаете отказаться от шрифта по-умолчанию, используйте оператор FONT шаблона диалогового окна,
FONT размер_шрифта, "название шрифта"
в котором укажите размер шрифта и его название. Имейте в виду, что Windows 2000 использует по-умолчанию шрифт отличный от шрифта, принятого по-умолчанию в Windows 9x и Windows NT 4.0. Чтобы шрифты диалогового окна, отрисовывались одинаково адекватно во всех этих системах, используйте шаблон DIALOGEX вместо DIALOG, в стили диалогового окна добавьте DS_SHELLFONT, а в операторе FONT укажите шрифт MS Shell Dlg. При таком раскладе система будет рисовать текст в диалоговом окне шрифтом Tahoma в Windows 2000 и шрифтом MS Sans Serif в Windows 9x и Windows NT 4.0.
А вот теперь мы, похоже, добрались до самого интересного места данной статьи. Сейчас рассмотрим каким же всё-таки образом можно использовать шаблоны диалоговых окон, расположенные в памяти.
На основании собственной практики могу отверждать, что существует несколько способов формирования шаблонов диалоговых окон в памяти и последующего их использования. В качестве функции, использующей шаблон в памяти я буду использовать DialogBoxIndirectParam. Самый, на мой взгляд, простой способ создания диалогового окна из шаблона, расположенного в памяти, следующий. Создаем шаблон привычным нам способом. Для примера, создадим такой вот шаблон:
#include "resource.h" #define IDD_DLG 1001 #define IDSTATIC 1002 #define IDBUTTON 1003 IDD_DLG DIALOG 0,0,221,127 CAPTION "Простое использование шаблона" FONT 8,"MS Sans Serif" STYLE WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER | DS_SETFONT BEGIN CONTROL "Элемент управления STATIC",IDSTATIC,"Static",WS_CHILD | WS_VISIBLE | WS_TABSTOP,50,40,130,22 CONTROL "Кнопка",IDBUTTON,"Button",WS_CHILD | WS_VISIBLE | WS_TABSTOP,73,80,74,22 END
диалогового окна, на котором расположены элементами управления Static с идентификатором 1002 и Button (кнопка) - идентификатор 1003. Компилируем данный шаблон с помощью компилятора ресурсов. После этого открываем получившийся RES-файл любым hex-редактором и со смещения 64 (40h) байтов от начала файла копируем весь 16-ричный код, который присваиваем переменной в коде программы, показанном ниже, разбивая строку на отдельные байты. А переменную передаем функции DialogBoxIndirectParam. Название переменной я специально выделил, чтобы было более понятно.
.data dlgTemplate db 040h,008h,0C8h,010h,000h,000h,000h,000h,002h,000h,000h,000h,000h,000h,0DDh,000h db 07Fh,000h,000h,000h,000h,000h,01Fh,004h,040h,004h,03Eh,004h,041h,004h,042h,004h db 03Eh,004h,035h,004h,020h,000h,038h,004h,041h,004h,03Fh,004h,03Eh,004h,03Bh,004h db 04Ch,004h,037h,004h,03Eh,004h,032h,004h,030h,004h,03Dh,004h,038h,004h,035h,004h db 020h,000h,048h,004h,030h,004h,031h,004h,03Bh,004h,03Eh,004h,03Dh,004h,030h,004h db 000h,000h,008h,000h,04Dh,000h,053h,000h,020h,000h,053h,000h,061h,000h,06Eh,000h db 073h,000h,020h,000h,053h,000h,065h,000h,072h,000h,069h,000h,066h,000h,000h,000h db 000h,000h,001h,050h,000h,000h,000h,000h,032h,000h,028h,000h,082h,000h,016h,000h db 0EAh,003h,0FFh,0FFh,082h,000h,02Dh,004h,03Bh,004h,035h,004h,03Ch,004h,035h,004h db 03Dh,004h,042h,004h,020h,000h,043h,004h,03Fh,004h,040h,004h,030h,004h,032h,004h db 03Bh,004h,035h,004h,03Dh,004h,038h,004h,04Fh,004h,020h,000h,053h,000h,054h,000h db 041h,000h,054h,000h,049h,000h,043h,000h,000h,000h,000h,000h,000h,000h,001h,050h db 000h,000h,000h,000h,049h,000h,050h,000h,04Ah,000h,016h,000h,0EBh,003h,0FFh,0FFh db 080h,000h,01Ah,004h,03Dh,004h,03Eh,004h,03Fh,004h,03Ah,004h,030h,004h,000h,000h db 000h,000h,000h,000h .code start: invoke DialogBoxIndirectParam, hInstance, addr dlgTemplate, NULL, addr DialogProc, 0
invoke ExitProcess, eax DialogProc proc hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg == WM_INITDIALOG .elseif uMsg == WM_COMMAND .if wParam == 1003 ; реакция на нажатие кнопки invoke SendMessage, hDlg, WM_CLOSE, 0, 0 .endif .elseif uMsg == WM_CLOSE invoke EndDialog, hDlg, 0 ret .endif xor eax, eax ret DialogProc endp end start
Исходный код с примером создания диалогового окна находится в файле sample_5_2.rar. Данный способ хорош тем, что нет надобности разбираться в формате шаблона и составляющих его структурах заголовка и элементов управления. Минусом же его является негибкость. Изменить данный шаблон так, чтобы он отображал диалоговое окно с другим набором стилей или элементов управления, довольно сложно. И уж тем более, для этого нужно разбираться в правилах построения шаблона в памяти. Вот тут то мы и приходим ко второму варианту создания шаблона диалога в памяти.
Но для начала вам нужно знать следующее. Шаблон диалогового окна должен состоять из заголовка, описывающего само диалоговое окно (его стили, положение и размеры), и может содержать одну или несколько структур, описывающих элементы управления (для каждого элемента также указываются его стили, размеры и положение в границах клиентской области диалогового окна). Для описания шаблона можно использовать обычные структуры или расширенные.
DLGTEMPLATE | DLGITEMTEMPLATE |
DLGTEMPLATE struct style DWORD ? dwExtendedStyle DWORD ? cdit WORD ? x WORD ? y WORD ? cx WORD ? cy WORD ? DLGTEMPLATE ends |
DLGITEMTEMPLATE struct style DWORD ? dwExtendedStyle DWORD ? x WORD ? y WORD ? cx WORD ? cy WORD ? id WORD ? DLGITEMTEMPLATE ends |
DLGTEMPLATEEX | DLGITEMTEMPLATEEX |
DLGTEMPLATEEX struct dlgVer WORD ? signature WORD ? helpID DWORD ? exStyle DWORD ? style DWORD ? cDlgItems WORD ? x WORD ? y WORD ? cx WORD ? cy WORD ? menu WORD ? windowClass WORD ? title BYTE titleLen dup(?) ; следующие члены структуры указываются, ; если член структуры style ; имеет стиль DS_SETFONT или DS_SHELLFONT pointsize WORD ? weight WORD ? italic BYTE ? charset BYTE ? typeface WORD stringLen dup(?) DLGTEMPLATEEX ends |
DLGITEMTEMPLATEEX struct helpID DWORD ? exStyle DWORD ? style DWORD ? x WORD ? y WORD ? cx WORD ? cy WORD ? id WORD ? windowClass WORD ? title BYTE titleLen dup(?) extraCount WORD ? DLGITEMTEMPLATEEX ends |
Как и в предыдущем примере за основу возьмем шаблон диалогового окна с двумя элементами управления.
Правила построения шаблона в памяти требуют, чтобы текст заголовка окна, название класса, меню и имя шрифта диалога, указывались в юникод-формате. Для перевода ASCII-строки в юникод-строку используем функцию MultiByteToWideChar.
Теперь смотрим как нам следует заполнить структуру DLGTEMPLATE. Первые 4 байта - стиль диалогового окна (двойное слово - DWORD). Устанавливаем его в значение WS_VISIBLE or WS_CAPTION or WS_SYSMENU or DS_CENTER или 10CC0840h. Расширенный стиль не используется, но место, предусмотренное под него, заполнить нулями нужно всё-равно (тоже DWORD). Далее указываем сколько элементов управления находится в диалоговом окне (2 байта или WORD). После этого вставляем по 2 байта (WORD) значения положение по-горизонтали (x), по вертикали (y), и размеры окна. На этом структура DLGTEMPLATE заполнена и после неё можно указать дополнительные байтовые массивы, описывающие меню, класс окна, заголовок и шрифт. Итак, следующие 2 байта (WORD), показывают имеется ли меню. Если 0, значит меню нет, если FFFF - массив имеет дополнительный эелемент, в котором указывается идентификатор ресурса меню в исполняемом файле, иначе - это название юникод-название ресурса меню. Следующие 2 байта (WORD) - класс окна диалога. Если 0, значит система использует класс по-умолчанию, если FFFF - массив имеет дополнительный элемент с идентификатором класса окна, иначе - это юникод-название зарегистрированного класса окна. За классом идет заголовок окна в юникод-формате или 2 нулевых байта, если заголовок не нужен. В случае использования оператора FONT, далее идет описание шрифта диалогового окна: 2 байта размер шрифта и юникод-строка название шрифта.
За структурой, описывающей окно и дополнительными строковыми массивами, идут описания элементов управления, выравненные по границе двойного слова (DWORD) DLGITEMTEMPLATE. Стиль элемента управления - 4 байта. Расширенный стиль - 4 байта. Далее по 2 байта координаты и размеры элемента управления. После его псевдоидентификатор 2 байта. На этом структура DLGITEMTEMPLATE заканчивается и начинаются дополнительные байтовые массивы. Далее байтовый массив, описывающий класс окна элемента управления. Если первые 2 байта равны FFFF, значит далее указывается идентификатор предопределённого в операционной системе элемента управления, иначе система воспринимает байтовый массив, как юникод-строку с названием зарегистрированного класса окна.
hex-значение класса | Описание |
0080 | Button (класс кнопки) |
0081 | Edit (класс текстового поля ввода) |
0082 | Static (класс нередактируемого текстового поля) |
0083 | List box (класс списка) |
0084 | Scroll bar (класс полосы прокрутки) |
0085 | Combo box (класс выпадающего списка) |
Далее юникод-строка текста, расположенного на элементе управления, выравненная по границе двойного слова. И в конце 2 байта - размер дополнительных данных и сами дополнительные данные (могут быть любого размера), которые система затем пересылает вместе с сообщением WM_CREATE в параметре lParam в процедуру окна.
;********* Заполнение шаблона заголовка диалога ***********; DLGTEMPLATE стиль окна = 10CC0840h (DWORD) расширенный стиль = 0h (DWORD) кол-во контролов = 0h (WORD) x координата = 0h (WORD) y координата = 0h (WORD) ширина окна = 0DDh (WORD) высота окна = 07Fh (WORD) Дополнительные массивы диалога меню = 0h (WORD) класс = 0h (WORD) заголовок = "Самостоятельное диалоговое окно" (в юникод-формате) размер шрифта = 08h (WORD) имя шрифта = "MS Sans Serif" (в юникод-формате) ;******** Заполнение шаблона элемента управления ************; DLGITEMTEMPLATE стиль кнопки = 50010000h (DWORD) расширенный стиль = 0h (DWORD) x координата = 049h (WORD) y координата = 050h (WORD) ширина элемента = 04Ah (WORD) высота элемента = 016h (WORD) id элемента = 03EAh (WORD (1002 в десятичном виде)) Дополнительные массивы элемента управления есть описание = 0FFFFh (WORD) класс элемента = 080h (WORD) текст = "Закрыть окно" (в юникод-формате) размер дополнит. данных = 0h (WORD) дополнит. данные = 0h (DWORD) ;***********************************************************; ...и так далее для каждого последующего элемента управления
Исходный код с примером создания диалогового окна из шаблона, подготовленного в памяти, находится в файле sample_5_3.rar. Думаю, обилие комментариев поможет Вам разобраться в примере.
Процедура диалогового окна похожа на процедуру основного (главного) окна тем, что операционная система посылает в неё сообщения о различных событиях. Отличие её от процедуры основного окна в том, что в ней не используется вызов функции DefWindowProc.
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
Вместо этого, процедура диалогового окна возвращает TRUE, если происходит обработка сообщения, или FALSE - в ином случае. Каждая диалоговая процедура имеет следующий вид:
DlgProc proc hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM ; выбор и обработка сообщения ; стандартный выход xor eax, eax ret DialogProc endp
Основными сообщениями, которые обрабатываются в процедуре диалогового окна, конечно же являются WM_INITDIALOG (110h), отправляемое системой при инициализации диалогового окна, и WM_COMMAND (111h), отправляемое эементами управления (кроме, как было сказано выше, элемента STATIC).
Операционная система предоставляет специальный интерфейс взаимодействия диалогового окна с клавиатурой, связывая определенные клавиши (сочетания клавиш) клавиатуры с сообщениями, посылаемыми процедуре диалогового окна. Эти сообщения могут быть связаны напрямую с определенными кнопками диалогового окна или служить для перемещения между элементами управления. Ниже даны описания кнопок клавиатуры и их воздействие на диалоговое окно и элементы управления, расположенные на нём.
Кнопка клавиатуры | Действие |
ALT+мнемоника или мнемоника |
Перемещает фокус ввода на первый элемент управления, имеющий стиль WS_TABSTOP и расположенный после элемента управления STATIC с указанной мнемоникой. |
DOWN или RIGHT |
Перемещает фокус ввода на следующий элемент управления в группе элементов. |
ENTER | Посылает сообщение WM_COMMAND с параметром wParam, установленным в значение IDOK (1) или в значение идентификатора кнопки по-умолчанию (DEFPUSHBUTTON). |
ESC | Посылает сообщение WM_COMMAND с параметром wParam, установленным в значение IDCANCEL (2). |
LEFT или UP |
Перемещает фокус ввода на предыдущий элемент управления в группе элементов. |
TAB | Перемещает фокус ввода на следующий элемент управления, имеющий стиль WS_TABSTOP. |
SHIFT+TAB | Перемещает фокус ввода на предыдущий элемент управления, имеющий стиль WS_TABSTOP. |
Операционная система автоматически заботится об обеспечении реакции на нажатия указанных клавиш в модальных диалоговых окнах. В немодальных диалогах необходимо, при получении ожидаемого сообщения, сразу же послать его функции IsDialogMessage, которая позволяет это сообщение обработать именно впроцедуре диалогового окна. Иначе сообщение попадет в процедуру основного окна, где и будет обработано или потеряно. Если функция IsDialogMessage возвратит значение, отличное от 0, значит сообщение было успешно обработано.
invoke IsDialogMessage, hDlg, uMsg
И ещё на заметку. Поскольку кнопки клавиатуры LEFT, RIGHT, UP и DOWN используются для перемещения между элементами управления диалогового окна, их нельзя использовать для прокрутки содержимого окна. Поэтому, если в вашем диалоговом окне имеются полосы прокрутки, пользоваться ими придется при помощи альтернативных комбинаций клавиш клавиатуры. Назначение и обработка событий от нахначенных клавиш целиком и полностью ложится на разработчика программы.
Иногда может возникнуть желание сделать своё диалоговое окно, отличное от того, что предлагает по-умолчанию операционная система Windows. В этом случае можно воспользоваться парой вариантов, представленных ниже.
Во-первых, можно создать диалоговое окно, наследовав класс основного (главного) окна приложения. Для начала подготовьте шаблон диалогового окна. В заголовке шаблона должен быть указан оператор CLASS.
CLASS "имя_класса"
В коде приложения вызовите либо функцию GetClassInfo, передав ей в качастве параметра пустую структуру WNDCLASS, либо GetClassInfoEx с указателем на структуру WNDCLASSEX.
WNDCLASS struct style DWORD ? lpfnWndProc DWORD ? cbClsExtra DWORD ? cbWndExtra DWORD ? hInstance DWORD ? hIcon DWORD ? hCursor DWORD ? hbrBackground DWORD ? lpszMenuName DWORD ? lpszClassName DWORD ? WNDCLASS ends
invoke GetClassInfo, hInstance, lpClassName, lpWndClass ; где lpClassName - имя класса окна, параметры которого запрашиваются, ; lpWndClass - указатель на структуру WNDCLASS
WNDCLASSEX struct cbSize DWORD ? style DWORD ? lpfnWndProc DWORD ? cbClsExtra DWORD ? cbWndExtra DWORD ? hInstance DWORD ? hIcon DWORD ? hCursor DWORD ? hbrBackground DWORD ? lpszMenuName DWORD ? lpszClassName DWORD ? hIconSm DWORD ? WNDCLASSEX ends
invoke GetClassInfoEx, hInstance, lpClassName, lpWndClass ; где lpClassName - имя класса окна, параметры которого запрашиваются, ; где lpWndClass - указатель на структуру WNDCLASSEX
В результате соответствующая структура будет заполнена значениями, присущими запрашиваему классу окна. Члены этой структуры вы вправе изменить по-своему вкусу. Для члена структуры cbWndExtra укажите значение DLGWINDOWEXTRA, для lpfnWndProc - указатель на процедуру диалогового окна для приема и обработки сообщений, и для lpszClassName - имя класса диалогового окна (такое же как в шаблоне). После этого регистрируете наследованный класс с помощью функции RegisterClass или RegisterClassEx, и затем создаете диалоговое окно. Процедура диалогового окна примерно такая же, как процедура основного окна. Единственное отличие состоит в том, что в процедуре диалогового окна вместо функции DefWindowProc нужно вызывать функцию DefDlgProc.
Второй способ лучше всего применять при создании приложений, главным окном в которых выступает диалоговое окно. Последовательность действий примерно следующая. В функции WinMain Вы заполняете структуру WNDCLASSEX, в которой члену cbWndExtra задаете значение DLGWINDOWEXTRA, а члену lpszClassName структуры присваиваете указатель на имя класса диалогового окна, такое как в шаблоне. После этого регистрируете класс диалогового окна с помощью функции RegisterClass или, соответственно, RegisterClassEx, и затем создаете диалоговое окно. В этом случае процедура диалогового окна точно такая же, как и процедура основного окна.
Исходный код с примером создания пользовательского диалогового окна находится в файле sample_5_4.rar.
Всем известны модальные окна сообщений (MessageBox), о которых уже было рассказано выше и довольно подробно. Основной недостаток таких диалоговых окон состоит в том, что пока пользователь не отреагирует и не нажмёт кнопку в окне этого диалогового окна, он не сможет продолжать работу с приложением. Соответственно, возникает вопрос: каким образом расширить функционал такого окна, добавив в него завершение работы по истечении заданного интервала времени. В MSDN я нашёл пример создания такого окна. Слегка его переработал и выкладываю всем Вам для ознакомления. Сразу предупреждаю, код местами не оптимизирован (в принципе и не особо стремился); основной задачей считаю указать направление разработки таких окон.
Исходный код с примером создания окна сообщения с таймаутом находится в файле sample_5_5.rar.