Окна, как элемент графического интерфейса Windows, могут быть простыми, информационными, уведомляющими пользователя о каком-либо событии (например, об ошибке), но чаще всего они представляют собой более сложные конструкции, содержащие в собственной клиентской области множество элементов управления. Вне зависимости от внешнего вида графического окна, основное предназначение его заключается в выстраивании взаимодействия с пользователем, поскольку само понятие окна подразумевает некую область, которую приложение отрисовывает с целью предоставить (вывести) информацию или запросить (ввести) её, другими словами обеспечить ввод/вывод данных. Вывод и вывод зачастую комбинируются в пределах одного окна и могут быть организованы нетривиально, например содержать выделенные рамкой области, группирующие однотипные данные, иметь чекбоксы/радиокнопки, обеспечивающие выбор опций, поля для текстового вывода, кнопки, различного рода списки и прочие объекты. Естественно, что создавать а затем и обслуживать в окне большое количество автономных объектов не так просто как хотелось бы: мало того, что для самого окна нужно определять класс, на основании класса уже создавать непосредственно окно, далее описывать оконную процедуру, а в довершение ко всему создавать в пределах окна каждый элемент, а ведь этими элементами придется еще и управлять. Вся эта рутина превращает процесс разработки многоэлементного окна в очень трудоемкую задачу, в которой разработчик сфокусирован более на логике создания окон и элементов, нежели на алгоритме ядра приложения. Решением данной проблемы является рекомендованный Microsoft к использованию особый класс окон, называемый диалоговыми, которые обеспечивают относительную простоту обслуживания окон, содержащих произвольное количество различных (статических и динамических) элементов управления.
Диалоговые окна как класс могут использоваться в прикладных программах для реализации ожидания от пользователя какого-либо действия (команды), после произведения которого могут незамедлительно быть уничтожены (закрыты). Так же, диалоговые окна могут применяться для отображения/получения произвольной промежуточной информации, при одновременной работе пользователя с другими окнами приложения, ведь сам термин "диалог" подразумевает, что при помощи данного класса окон выстраивается определенного рода взаимодействие с пользователем. Характерным примером может выступать диалог, реализующий поиск текста в приложении, предназначенном для обработки текстовой информации. В такого рода приложениях часто используется автономное диалоговое окно с несколькими элементами: строкой ввода искомого фрагмента, чекбоксами опций и кнопками подтверждения/отмены. Вот первый попавшийся по руку характерный пример:
В процессе поиска диалоговое окно активно, отображается на экране и выводит полученные результаты, тем не менее в любой момент пользователь может переключить фокус на диалог и изучить результаты или же изменить критерии поиска и запустить процесс вновь. Диалоговые окна в приложениях могут создавать в момент активации (произвольной) команды и продолжать отображать в нем необходимую информацию на протяжении заданного промежутка времени, функционирования какого-либо алгоритма, реализующего некую логику, непосредственно до момента закрытия пользователем.
Сами по себе вложенные в диалоговое окно элементы управления являются стандартными и могут применяться во всех без исключения типах окон. Но, для создания элементов (EDIT, STATIC, BUTTON) в классических приложениях нужно описывать каждый из них как отдельное окно, дочернее по отношению к главному окну приложения, с производными отсюда сложностью обслуживания. В случае же использования диалога можно объявить основное окно приложения диалоговым и описать все вложенные элементы в ресурсах. Одним словом, диалоговое окно это фактически обычное окно графического интерфейса, однако создаваемое посредством специализированных функций, обеспечивающих отдельный системный механизм, позволяющий работать с дочерними элементами проще, чем если бы они были созданы с использованием типовых (хорошо знакомых нам) функций и обрабатывались бы соответствующими механизмами. Еще одно неоспоримое достоинство заключается в том, что элементы диалогового окна могут быть определены (описаны) при помощи ресурсов приложения, представляющих собой структуру в удобном для описания и быстром для обхода представлении.
Диалоговое окно может быть использовано несколькими способами:
- как основное окно: окно создается как единственное, содержащее все необходимые элементы управления и по закрытии завершает все приложение (процесс);
- как вспомогательное (дочернее) окно: применяется в качестве временного для получения некой промежуточной информации от пользователя, при закрытии уничтожается исключительно данное окно.
Существуют несколько видов диалоговых окон:
- Модальное диалоговое окно (modal dialog box) -- Создается при помощи подмножества функций DialogBox*. В случае использования данного вида, диалоговое окно создается без родительского окна. Этот подход делает цикл сообщений потока ненужным, а оконная процедура фактически становится процедурой диалогового окна (с некоторыми изменениями), и все сообщения, адресованные окну (и дочерним элементам) посылаются напрямую этой процедуре диалогового окна. Класс окна в этом случае регистрировать не нужно. Отличительной особенностью некоторых подтипов модального диалогового окна является:
- ограничение в переключении фокуса ввода на другие окна родительского приложения, либо на окна сторонних приложений.
- прерывание вызывающего диалог процесса/блокировка родительского окна до момента, пока не будет закончена работа с диалоговым окном и оно не будет закрыто.
Однако, не смотря на подобные явные ограничения, намного чаще применяются модальные диалоговые окна, поскольку зачастую диалог на ассемблере представляет из себя небольшую утилитку, открывающую единственное окно и с закрытием его завершающее своё функционирование, поэтому ограничения никого не пугают :)
Модальные диалоговые окна более простые в обслуживании, поскольку создаются, используются и уничтожаются несколько проще. - Немодальное диалоговое окно (modeless dialog box) -- Создается при помощи подмножества функций CreateDialog*. Отличается от модального диалогового окна тем, что возвращает управление сразу после своего создания, не дожидаясь закрытия собственного окна, никаким образом не воздействуют (не блокируют) родительское окно, типом возвращаемого функцией значения и методами закрытия получившегося диалогового окна. Всё остальное остается в силе: случае использования данного подхода, мы получаем ту же диалоговую процедуру, функционирующую стандартным образом. Этот тип диалоговых окон позволяет предоставлять/получать информацию и переключаться между окнами, возвращаясь к предыдущей задаче, без закрытия окна диалога.
По основному циклу обработки сообщений во всех типах диалоговых окон присутствуют отличия от цикла обычных окон: отсутствуют вовсе такие привычные функции как TranslateMessage и DispatchMessage, в некоторых типах диалоговых окон присутствует функция IsDialogMessage.
Отличия классического окна от диалогового следующие:
- обслуживается отдельной частью ядра - менеджером диалоговых окон. Именно указанный диспетчер производит обратный вызов процедуры диалогового окна в нашем коде, передавая ей необходимые сообщения;
- если процедура диалогового окна обрабатывает какое-либо сообщение, она должна вернуть TRUE в регистре eax, если же она не обрабатывает сообщение, тогда она должна вернуть в регистре eax значение FALSE;
- процедура диалогового окна не передает никаких сообщений функции DefWindowProc, попросту вообще её не использует, так как это не полноценная процедура окна, а лишь отчасти схожая;
- меньше сообщений, приходящих в процедуру диалогового окна. частично сообщения совпадают с сообщениями типового окна, вместо
WM_CREATE
приходитWM_INITDIALOG
;
В данной статье мы будем рассматривать диалоговые окна в совокупности с различными дочерними (статическими и динамическими) элементами, поскольку само по себе диалоговое окно это всего-лишь один из видов окон графического интерфейса, являющееся, по сути, пустым "каркасом". Согласитесь, что рассматривать диалоговое окно без каких либо элементов не имеет смысла, ведь много ли толка от пустого окна-каркаса, если в нем нет никакой функциональной нагрузки, попросту не с чем взаимодействовать? Поэтому, диалоговое окно создается с целью размещения внутри него неких элементов управления.
Динамические элементы управления предназначены для обеспечения ввода информации от пользователя. Статические элементы идеально подходят для целей организации простого, легко обрабатываемого вывода, они не предназначены для приема ввода от пользователя и (таким образом) не отсылают никаких сообщений, уведомляющих о собственных событиях родительскому окну. Поэтому, если даже по элементу щелкнули мышью, никакой информации передаваться в приложение не будет. Тем не менее, сама по себе информация, располагающаяся в статических элементах, может быть изменена в любой момент времени существования элемента управления.
Метод 1: использование DialogBoxParam
В качестве первого практического задания для данного материала мы рассмотрим пример довольно простенькой программы, выводящей при старте одно-единственное диалоговое окно, размещающее в себе некоторые часто используемые на практике элементы управления (комбобоксы, кнопки, поля вывода и редактирования). Можно сказать, что наше приложение будет (с большим натягом) попадать в категорию программ-модификаторов, которые по-английски именуются "Patch", или заплатка для бинарного файла. Однако, полноценный патчер достаточно громоздок в качестве учебного материала начального уровня, что для детального описания в одной статье будет явно избыточным, поэтому мы реализуем алгоритм упрощенного прототипа (обертки, каркаса), который будет обеспечивать лишь вывод информации в окно редактирования, исключив работу с файлами (которая когда-нибудь будет представлена отдельной статьей).
В качестве базового шаблона для нашей будущей программы мы используем распространяемый в составе дистрибутива исходный текст модуля под названием \EXAMPLES\DIALOG\dialog.asm, с внесением соответствующих модификаций, характерных для поставленной нами задачи. Для удобства дальнейшего изложения, сперва традиционно приведем получившийся исходный код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
format PE GUI 4.0 ; Формат PE. Версия GUI 4.0. entry start ; Описание точки входа include '%include%\win32a.inc' ; Делаем стандартное включение описателей. IDD_MAIN = 44 ; Идентификатор диалогового окна IDD_EDIT = 100 ; Идентификатор элемента управления: поле редактирования IDD_BUTTONPATCH = 100 ; Идентификатор элемента управления: кнопка IDD_BUTTONEXIT = 101 ; Идентификатор элемента управления: кнопка ;=== сегмент кода ============================================================ section '.text' code readable executable start: invoke InitCommonControlsEx ; Подключим общие элементы управления invoke GetModuleHandle,0 ; Получим дескриптор приложения invoke DialogBoxParam,eax,IDD_MAIN,HWND_DESKTOP,DialogProc,0 ; Создадим диалоговое окно на основе ресурса or eax,eax ; Проверим на код удачного завершения jz exit ; Если функция завершилась удачно, то нормально выходим invoke MessageBox,HWND_DESKTOP,message,caption,[flags] ; Иначе выдаем окно с ошибкой exit: invoke ExitProcess,0 ; Выход из программы. ;--- процедура обработки диалога (функция диалога, диалоговая процедура, процедура обработки сообщений диалогового окна) proc DialogProc hWnd,wMsg,wParam,lParam push ebx esi edi ; сохраним регистры cmp [wMsg],WM_INITDIALOG ; Проверим на WM_INITDIALOG je .wminitdialog ; на обработчик wminitdialog cmp [wMsg],WM_COMMAND ; Проверим на WM_INITDIALOG je .wmcommand ; на обработчик wminitdialog cmp [wMsg],WM_CLOSE ; Проверим на WM_CLOSE je .wmclose ; на обработчик wmclose xor eax,eax ; если мы не обработали ни одного сообщения, необходимо вернуть 0 jmp .finish ; на выход .wminitdialog: invoke GetDlgItem,[hWnd],IDD_EDIT ; Получим дескриптор поля редактирования mov [hEdit],eax ; Сохраним его stdcall GetTimeText,editOutput ; Сформируем префикс времени invoke lstrcat,editOutput,editPad ; Добавим к нему разделитель invoke lstrcat,editOutput,_pinitial ; Добавим к нему сообщение invoke SetDlgItemText,[hWnd],IDD_EDIT,editOutput ; Передадим текст в поле редактирования jmp .done .wmcommand: cmp [wParam],BN_CLICKED shl 16 + IDD_BUTTONEXIT ; Нажата кнопка Exit? je .wmclose cmp [wParam],BN_CLICKED shl 16 + IDD_BUTTONPATCH ; Нажата кнопка Patch? je .dopatch jmp .done .dopatch: ; Тут может располагаться код работы с изменяемым файлом stdcall GetTimeText,editOutput ; Сформируем префикс времени invoke lstrcat,editOutput,editPad ; Добавим к нему разделитель invoke lstrcat,editOutput,_psuccess ; Добавим к нему сообщение invoke GetWindowTextLength,[hEdit] ; Получим длину текста в поле редактирования invoke SendMessage,[hEdit],EM_SETSEL,eax,eax ; Установим каретку поля в позицию последнего символа invoke SendMessage,[hEdit],EM_REPLACESEL,FALSE,editOutput ; Заменим (выведем) текст в поле редактирования jmp .done .wmclose: ; Обработчик сообщения WM_CLOSE. Обязателен. invoke EndDialog,[hWnd],0 ; Закрытие диалога .done: mov eax,1 ; При обработке хотя бы одного сообщения возвращаем 1 .finish: pop edi esi ebx ; восстановим регистры ret endp proc GetTimeText pTimeStr:dword push eax ebx ecx invoke GetLocalTime,systime movzx eax,[systime.wHour] movzx ebx,[systime.wMinute] movzx ecx,[systime.wSecond] invoke wsprintf,[pTimeStr],tBufferFormat,eax,ebx,ecx pop ecx ebx eax ret endp ;=== сегмент данных ========================================================== section '.data' data readable writeable _pinitial db 'Engine started',13,10,0 _psuccess db 'Patched successfully',13,10,0 editPad db ' : ',0 flags dd ? caption rb 40h message rb 100h hEdit dd ? systime SYSTEMTIME editOutput rb 256 db 0 tBufferFormat db '%02u:%02u:%02u',0 ;=== таблица импорта ========================================================= section '.idata' import data readable writeable library kernel32,'KERNEL32.DLL',user32,'USER32.DLL',comctl32,'COMCTL32.DLL' include 'api\kernel32.inc' include 'api\user32.inc' include 'api\comctl32.inc' ;=== секция ресурсов ========================================================= section '.rsrc' resource data readable directory RT_DIALOG,dialogs, RT_MANIFEST,manifests resource dialogs,\ IDD_MAIN,LANG_ENGLISH+SUBLANG_DEFAULT,rsrc_dialog dialog rsrc_dialog,'Patcher',70,70,240,152,WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+DS_SYSMODAL+DS_MODALFRAME+DS_CENTER,0,0,'Arial',10 dialogitem 'STATIC','Put AppName Here', -1,2,2,236,11,WS_VISIBLE+SS_CENTER+SS_CENTERIMAGE+SS_SUNKEN dialogitem 'BUTTON','', -1,2,11,236,63,WS_VISIBLE+BS_GROUPBOX dialogitem 'STATIC','[Filename]', -1,5,18,50,11,WS_VISIBLE+SS_LEFT+SS_CENTERIMAGE dialogitem 'STATIC','targetapp.exe', -1,60,18,175,11,WS_VISIBLE+SS_SUNKEN+SS_LEFT+SS_CENTERIMAGE dialogitem 'STATIC','[URL]', -1,5,32,50,11,WS_VISIBLE+SS_LEFT+SS_CENTERIMAGE dialogitem 'STATIC','http://datadump.ru',-1,60,32,175,11,WS_VISIBLE+SS_SUNKEN+SS_LEFT+SS_CENTERIMAGE dialogitem 'STATIC','[Author]', -1,5,46,50,11,WS_VISIBLE+SS_LEFT+SS_CENTERIMAGE dialogitem 'STATIC','Einaare', -1,60,46,175,11,WS_VISIBLE+SS_SUNKEN+SS_LEFT+SS_CENTERIMAGE dialogitem 'STATIC','[Release Date]', -1,5,60,50,11,WS_VISIBLE+SS_LEFT+SS_CENTERIMAGE dialogitem 'STATIC','25.12.2017', -1,60,60,175,11,WS_VISIBLE+SS_SUNKEN+SS_LEFT+SS_CENTERIMAGE dialogitem 'BUTTON','Log window', -1,2,75,236,58,WS_VISIBLE+BS_GROUPBOX dialogitem 'EDIT', '', IDD_EDIT,5,83,230,46,WS_VISIBLE+WS_BORDER+ES_MULTILINE+ES_AUTOHSCROLL+ES_AUTOVSCROLL+ES_READONLY+WS_VSCROLL dialogitem 'BUTTON','&Patch', IDD_BUTTONPATCH,132,135,52,14,WS_VISIBLE+BS_DEFPUSHBUTTON dialogitem 'BUTTON','&Exit', IDD_BUTTONEXIT,185,135,52,14,WS_VISIBLE+BS_FLAT enddialog resource manifests,\ 1,LANG_ENGLISH+SUBLANG_DEFAULT,manifest resdata manifest db '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',13,10 db '<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">',13,10 db '<assemblyIdentity name="patch.exe" processorArchitecture="x86" version="5.1.0.0" type="win32"/> ',13,10 db '<description>Patcher</description>',13,10 db '<dependency>',13,10 db '<dependentAssembly>',13,10 db '<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*" />',13,10 db '</dependentAssembly>',13,10 db '</dependency>',13,10 db '</assembly>',13,10 endres |
Ну и раз уж речь зашла об исходном коде, сразу давайте представим тут и результат запуска исполняемого файла приложения, познакомимся так сказать с визуальным представлением интерфейса:
Для тех, кто ознакомился с предшествующими моими материалами либо имеет уже некоторый опыт программирования приложений на ассемблере, становится очевидным, что по структуре наша программа на типичные приложения не очень то и похожа, имеется несколько глобальных отличий. Причиной подобной ситуации является использование в коде функции создания модального диалогового окна DialogBoxParam, которая накладывает некоторые до селе нам не известные обязательства на структуру разрабатываемого приложения:
- Нет необходимости регистрировать класс окна;
- Нет необходимости создавать окно (например, функцией CreateWindowEx) на основе ранее зарегистрированного класса;
- Процедура сообщений окна преобразуется в так называемую процедуру диалога, которая представляет собой упрощенный обработчик сообщений;
- Отсутствует функция обработки не обрабатываемых сообщений DefWindowProc;
- Меняются некоторые типы кодов возврата из обработчиков сообщений;
- Меняются общие правила возврата из процедуры.
Как можно заметить, нововведения многочисленны, но на самом деле к ним можно адаптироваться и они не столь существенно меняют логику работы приложения. Я так понимаю, судя по замыслу специалистов из Microsoft, нововведения призваны упростить использование диалогов, привлекая несколько упрощенной моделью обработки сообщений.
При использовании данного подхода исключается необходимость создавать отдельно огромное количество дочерних элементов, тем самым загромождая структуру кода. Как Вы видите, мы тут упомянули о так называемом шаблоне диалогового окна.
Сам по себе шаблон представляет определенным образом сконструированное описание (разметку) всех элементов создаваемого диалогового окна. Наиболее часто шаблон создается в секции ресурсов либо вручную, либо импортируется из внешнего файла на этапе компиляции. В ресурсах элементы диалога описываются при помощи специализированных макродиректив: директории ресурсов и вложенных в неё непосредственно самих ресурсов. Шаблонный метод довольно удобен, поскольку существенно упрощает структуру приложения, исключая необходимость создавать дочерние элементы управления при помощи функций. С другой стороны, все эти особенности разных типов окон самым беспощадным образом воздействуют на неокрепший ум начинающего программиста, что, в свою очередь, серьёзно влияет на мировоззрение последнего :)
Начальная инициализация
В "шапке" исходного кода у нас представлены описания констант, используемых в приложении, это идентификаторы IDD_MAIN, IDD_EDIT, IDD_BUTTONPATCH, IDD_BUTTONEXIT, задающие числовые эквиваленты элементов управления, к которым мы будет обращаться в коде. Секция кода начинается со строки 15, в которой инициируется вызов функции InitCommonControlsEx, производящей настройку внешнего представления (вида) общих элементов управления. Подробнее о этом будет рассказано далее, при описании манифеста в секции ресурсов. Функция GetModuleHandle применяется для получения дескриптора приложения, который возвращается в регистре eax. Затем, стартуем сразу с места в карьер и в строке 17 производим вызов функции DialogBoxParam с целью создания диалогового окна, фактически основного и единственного окна нашего приложения. В общем то, этой единственной функцией мы и создаем диалоговое окно, ничего более нам не требуется и ничего сверхъестественного не происходит, однако тайна диалогового окна будет раскрыта далее, когда мы начнем изучать код и ресурсы, на основании которых функция создает окно диалога и обеспечивает логику его обработки. Ну а тут сперва нам неплохо бы разобраться с входными параметрами функции:
Название | Описание |
---|---|
hInstance |
Дескриптор (описатель) исполняемого модуля (процесса), который является владельцем шаблона диалога. Если параметр содержит значение NULL, это означает, что использован текущий процесс, в контексте которого функция вызвана. |
lpTemplateName |
Шаблон диалога. Имя или (в нашем случае) идентификатор ресурса шаблона диалога, который ссылается на блок данных, описывающий все особенности управляющих элементов, размещаемых в пределах основного окна. Более сложно: это номер записи ресурса диалога в секции ресурсов. |
hWndParent |
Дескриптор родительского окна (окна-владельца) по отношению к создаваемому диалоговому окну. В случае, если приложение имеет единственное окно, то может использоваться значение HWND_DESKTOP (0), фактически объявляющее владельцем рабочий стол. |
lpDialogFunc |
Указатель на процедуру диалога (процедуру обработки сообщений диалогового окна). Данная процедура содержит логику работы с элементами в окне. |
dwInitParam |
Значение, передаваемое в диалоговую процедуру в параметре lParam сообщения WM_INITDIALOG . Это значение может использоваться программистом для передачи каких-либо данных диалоговому окну в момент его инициализации. Данная особенность может использоваться программистом в сценариях, в которых надо передать диалоговому окну какие-либо параметры в момент инициализации, непосредственно перед отображением на экране. |
Как же эта функция работает? Во внутренних алгоритмах функция DialogBoxParam использует функцию CreateWindowEx для создания диалогового окна. Далее, внутренний код DialogBoxParam посылает сообщение WM_INITDIALOG в процедуру только что созданного диалогового окна, которое предписывается во что бы то ни стало обработать, вернув на выходе в регистре eax значение 1. Если в шаблоне указаны стили DS_SETFONT | DS_SHELLFONT, то системой так же генерируется сообщение WM_SETFONT. Происходит отображение диалогового окна (в зависимости от наличия стиля WS_VISIBLE в шаблоне диалога), отключается (гасится) родительское окно (если присутствует), и стартует собственный цикл сообщений, обеспечивающий получение и диспетчеризацию сообщений, поступающих в диалоговое окно. Функция DialogBoxParam не возвращает управления до тех пор, пока диалоговое окно, созданной функцией, не будет закрыто (уничтожено) каким-либо образом. На деле, уничтожение (закрытие) диалогового окна должно выполняться с использованием функции EndDialog, которая завершает цикл сообщений, включает родительское окно (если существовало), и возвращает значение входного параметра nResult на выход процедуры DialogBoxParam. Таким образом функция EndDialog инициирует завершение DialogBoxParam.
Двигаемся далее по коду. После завершения DialogBoxParam, в строках с 18 по 22, у нас реализована логика проверки кода возврата из процедуры, которая опирается на следующие требования:
- Если функция завершилась без ошибки, код возврата это значение параметра nResult, указанного при вызове функции EndDialog для завершения диалога;
- Если функция завершилась с ошибкой из-за некорректного значения параметра hWndParent, то возвращаемое значение = 0. В этом случае функция возвращает 0 из-за необходимости обеспечить совместимость с предыдущими версиями Windows. В этом месте у нас получается какая-то не совсем прозрачная логика, потому что если мы указали 0 в параметре функции EndDialog как код возврата без ошибки, и в то же время 0 у нас возвращается в случае ошибки? Но таким образом реализован шаблон, взятый нами за основу, поэтому мы следует его логике и пока опустим данный нюанс.
- Если функция завершилась с ошибкой по любой другой причине, код возврата должен быть равен –1;
На основании этих условий, выдвинутых разработчиками, после завершения диалога происходит проверка выходного параметр на значение 0, и в случае равенства происходит "тихое" штатное завершение процесса вызовом функции ExitProcess. В противном случае происходит вывод окна сообщения с ошибкой (строка 20).
Процедура диалога
Вот тут то у нас начинается самое интересное. В строках 26-71 располагается код процедуры обработки сообщений диалогового окна, называемой иначе диалоговой процедурой, которая (в нашем случае) носит имя DialogProc
. Традиционно, подобная процедура во всех диалоговых приложениях является ключевой, основополагающей логикой.
Отдельно хотелось бы отметить характерные для процедуры диалогового окна отличия в обработке сообщений:
- Если хотя бы одно из сообщений было обработано, то на выходе процедуры диалога, в регистре eax должен быть выставлено значение 1.
- Если входящее сообщений не принадлежит ни к одну из обрабатываемых нашей процедурой диалогового окна, иными словами если наша процедура диалога не обработала ни одного сообщения, то на выходе, в регистре eax, мы должны возвратить 0. В этом случае операционная система сама выполнит необходимые действия по обработке подобных сообщений. По сути, поведение аналогично обработке необрабатываемых сообщений в типовой оконной процедуре, однако не производится непосредственного вызова функции DefWindowProc, можно предположить что она вызывается где-то в недрах кода DialogBoxParam.
- На этапе создания диалога, в процедуру диалога не поступает сообщение
WM_CREATE
(как в традиционной процедуре), заместо него система посылаетWM_INITDIALOG
.
В строках 28-33 расположен блок проверки входящих сообщений. В зависимости от поступившего сообщения, код маршрутизируется по соответствующим процедурам обработчиков. Для начала разберемся с сообщением WM_INITDIALOG
, которое (как уже упоминалось) поступает на этапе создания диалога, до отображения окна на рабочем столе. Обычно данное сообщение не обрабатывается, но в нашем примере мы его задействует, поскольку именно в нем мы проведем получение/сохранение дескриптора поля редактирования и произведем в это поле вывод самой первой строки статуса. Как вы уже наверное поняли, поле редактирования у нас будет использоваться как импровизированное пространство логирования операций, производимой приложением. В строке 37, при помощи функции GetDlgItem, получаем дескриптор поля редактирования нашего окна, сохраняем его в переменную hEdit для дальнейшего использования. Сразу после следует вызов внутренней самописной процедуры GetTimeText, которая предназначена для получения строки, содержащей текущее время в текстовом формате.
Вернемся к коду. Выводимая в поле редактирования строка всегда формируется у нас в буфере editOutput. Затем к буферу добавляется текстовый статус операции, например в строке 41 к получившемуся буферу (пока содержит только время) добавляем при помощи функции lstrcat текст "Engine Started". Полученную таким образом результирующую строку выводим в поле редактирования с использованием одной из функций работы с текстом в диалоге SetDlgItemText.
WM_SETTEXT
требуемому элементу управления, содержащее в собственном lParam указатель на строку выводимого текста. В параметрах функции указываются дескриптор диалогового окна и идентификатор элемента-получателя.На этом код обработки сообщения WM_INITDIALOG
у нас заканчивается.
Напомним, что процедура диалога обеспечивает обработку сообщений не только самого диалогового окна, но и всех дочерних элементов управления (контролов) и осуществляет это посредством уведомлений (кодов производимых над элементами управления простых действий). В момент, когда над элементом управления производится какое-либо действие (например, нажатие клавиши), система формирует уведомление и передает его в процедуру диалога через параметры сообщения WM_COMMAND
, то есть как бы "внутри" сообщения. Именно поэтому сообщение WM_COMMAND
представляет для нас особый интерес, ведь именно через него поступает уведомления о некоторых ключевых событиях, например через параметры данного сообщения передаются идентификаторы нажатых в нашем диалоговом окне клавиш.
WM_COMMAND
, которое состоит из двух двойных слов-параметров (DWORD, 32 бита): wParam и lParam. Сообщение WM_COMMAND
в параметре lParam содержит дескриптор контрола, а параметр wParam разбивается на код уведомления (старшее слово) и идентификатор контрола (младшее слово).Когда пользователь нажимает клавишу в нашем окне, система передает в процедуру диалога сообщение WM_COMMAND
, соответственно для определения нажата ли в окне интересующая нас клавиша, мы проверяем наличие кода уведомления BN_CLICKED в старшем слове и идентификатора клавиши в младшем слове параметра wParam. Подобная проверка реализована в строках 45 и 47, но выглядит она для начинающих не совсем типично:
1 2 3 |
. . . cmp [wParam],BN_CLICKED shl 16 + IDD_BUTTONEXIT . . . |
Что за интересная форма записи? Если присмотреться, то начинаешь понимать, что в правой части вычисляется следующее: берется константа BN_CLICKED (значение 0, разрядность 16 бит), над которой выполняется битовый сдвиг на 16 разрядов влево, тем самым перемещая её значение из нижнего слова в верхнее и образуя 32-битное значение, затем операцией "логическое ИЛИ" (+) добавляется идентификатор интересующей нас клавиши (в нашем коде IDD_BUTTONPATCH, IDD_BUTTONEXIT). Получающееся в результате 32-битное значение в точности соответствует тому, которое поступает в wParam сообщения WM_COMMAND
при нажатии в окне клавиши Exit.
Затем получившуюся константу сравнивают командой cmp с содержимым параметра wParam сообщения WM_COMMAND
и таким образом определяют, была ли нажата (та или иная) клавиша или нет. Думается, что это хороший алгоритм проверки, поэтому определенно стоит использовать его в своем коде.
Стоит обратить внимание так же и на код обработки клавиши Patch, который начинается у нас с метки .dopatch
. Тут опять же мы вызываем процедуру GetTimeText для формирования начальной части статусной строки, добавляем к получившейся строке уже сообщение "Patched successfully" и делаем вывод в наше поле редактирования.
Завершает процедуру диалога у нас обработчик сообщения WM_CLOSE
, который является обязательным и без которого очевидно никак обойтись нельзя, поскольку у окна должна иметься возможность закрываться по нажатию иконки крестика или кнопки Exit. Данный обработчик в диалоговой процедуре имеет одну отличительную особенность: по требованиям, предъявляемым к диалоговым окнам, для закрытия диалогового окна и уничтожения всех связанных структур, непосредственно в диалоговой процедуре должна вызываться специализированная функция EndDialog, входной параметр которой поступает затем на выход функции DialogBoxParam.
Секция ресурсов
Начиная со строки 114
исходного кода нашего приложения, располагается секция ресурсов, которая играет немаловажную роль в описании диалога. Дело в том, что так называемый шаблон диалога, который используется в качестве массива параметров для настройки как самого диалогового окна, так и вложенных в него элементов, определяется именно в секции ресурсов. Сразу же хотелось бы оговориться, что полноценное описание форматов директив (макросов) приведено в специализированной статье, а тут мы ограничимся лишь описанием моментов, важных для понимания нашего примера.
Традиционно, в самом начале секции ресурсов у нас присутствует запись каталога ресурсов directory
с типом RT_DIALOG
, в которой описывается ресурс с именем dialogs
. В свою очередь, в строке 118 макрос resource
описывает поддиректорию диалога с идентификатором IDD_MAIN (44), который используется у нас одним из входных параметров функции создания диалога DialogBoxParam. В строке 121 используется макродиректива dialog
, открывающая область описания шаблона и предназначающаяся для задания параметров самого диалогового окна. Формат следующий:
dialog label,title,x,y,cx,cy,style,exstyle,menu,fontname,fontsize
параметры слева-направо:
Название | Тип | Описание |
---|---|---|
label |
DWORD | Метка диалога. Задает произвольное имя, использующееся для связывания с директорией и ресурсом. |
title |
STRING | Название. Строка, выводимая в заголовке (верхняя строка) окна диалога. |
x |
WORD | Координата X левого верхнего угла диалогового окна. Указывается в единицах диалога? |
y |
WORD | Координата Y левого верхнего угла диалогового окна. Указывается в единицах диалога? |
cx |
WORD | Ширина диалогового окна. |
cy |
WORD | Высота диалогового окна. |
style |
DWORD | Стили диалогового окна. Задают множество свойств окна диалога. |
exstyle |
DWORD | Расширенные стили диалогового окна. Задают дополнительные стили. Были введены в какой версии? |
menu |
DWORD | Указатель на меню. В нашем примере не используется. |
fontname |
DWORD | Строка, описывающая класс шрифта. |
fontsize |
DWORD | Размер шрифта в единицах диалога? |
Описание dialog
должно непременно завершаться парной закрывающей директивой enddialog
, поскольку эти две директивы образуют своеобразный контейнер. Вложенные в dialog
макроопределения dialogitem
представляют собой параметры, описывающие всех расположенные в окне элементы диалога. Эти макроопределения подобно списку следуют друг за другом и описывают каждый атомарный элемент управления, размещенный в пределах окна. Эти описатели представляют для нас непосредственный интерес, поскольку в них содержатся все параметры элементов управления. Формат следующий:
dialogitem class,title,id,x,y,cx,cy,style,exstyle
параметры слева-направо:
Название | Тип | Описание |
---|---|---|
class |
DWORD | Класс элемента управления диалогового окна. Может принимать значения: BUTTON, EDIT, STATIC, LISTBOX, SCROLLBAR, COMBOBOX |
title |
STRING | Строка, выводимая внутри элемента управления. Например, для клавиши это название, печатаемое непосредственно на самой клавише, для поля редактирования это содержимое поля. |
id |
WORD | Дескриптор (идентификатор) элемента управления. Используется для связи с функциями в коде приложения. Каждый статический/динамический элемент управления (контрол) диалогового окна, доступ к которому вы хотите иметь в коде приложения, должен иметь собственный уникальный идентификатор. Значение -1 (0FFFFh) отключает использование идентификатора у элемента управления. |
x |
WORD | Координата X левого верхнего угла элемента управления. Указывается в единицах диалога? |
y |
WORD | Координата Y левого верхнего угла элемента управления. Указывается в единицах диалога? |
cx |
WORD | Ширина элемента управления. |
cy |
WORD | Высота элемента управления. |
style |
DWORD | Стили элемента управления. Задают множество свойств элемента. |
exstyle |
DWORD | Расширенные стили элемента управления. Задают дополнительные стили. Были введены в какой версии? |
К примеру, в строке 122, с использованием класса STATIC у нас описывается статический элемент, представляющий собой поле вывода, в котором размещен текст "Put AppName Here", взаимодействие с которым нам не нужно, поэтому мы задаем идентификатор (id) равный -1 за которым следуют координаты левого верхнего угла, ширина/высота, стили, определяющие свойства функционирования/отображения элемента (WS_VISIBLE+SS_CENTER+SS_CENTERIMAGE+SS_SUNKEN). Следующая строка 123 описывает группирующий фрейм (рамку) которая задается классом BUTTON, однако кнопкой не является, поскольку в стилях указана константа BS_GROUPBOX. Данная рамка имеет пустой заголовок (title) и обрамляет следующие далее в окне статические элементы: поля, в которых указываются данные о патчере, авторе, сайте и прч. В самом низу описываются кнопки, расположенные в нижней части окна.
Отдельно хотелось бы упомянуть поле редактирования с идентификатором IDD_EDIT (строка 133), которое представляет многострочный блок для вывода текста, наделенный вертикальной полосой прокрутки для пролистывания (WS_VSCROLL), горизонтальной и вертикальной прокруткой содержимого (ES_AUTOHSCROLL, ES_AUTOVSCROLL), маркированный только для чтения (ES_READONLY) во избежании редактирования содержимого пользователем.
Отдельно хотелось бы отметить, что начиная со строки 138 и до конца исходного текста у нас описывается ресурс манифеста. Приведенный манифест использован нами с одной лишь целью - связать процесс нашего приложения с библиотекой общих элементов управления 6-ой версии, необходимой для настройки современного, объемного представления диалогового окна и всех элементов в нем. Подробнее с этим механизмом можно ознакомиться в статье, посвященной манифесту приложения.
Размеры диалогового окна
В шаблоне размер диалога задаётся не в пикселях, а в так называемых единицах диалогового окна (dialog box units), которые прозрачно пересчитываются в пиксели в зависимости от используемого в диалоге шрифта. Я задавал в шаблоне разнообразные размеры шрифта, и смотрел на результат, получалось, что размеры окна менялись. Если шрифт в шаблоне не выставляется, пересчёт значений производится исходя из параметров используемого по умолчанию системного шрифта.
Достаточно подробное описание. Нигде такого не встречал.
Спасибо за безвозмездный труд!
статья не окончена. вторая часть с использованием функций CreateDialog* не описана. доделаю как-нибудь, при желании :)
Цитата:
"затем операцией "логическое И" (+) добавляется идентификатор интересующей нас клавиши"
Здесь операция "логическое ИЛИ" фактически сложение. Опечатка?
"опечатка? не думаю!!" :))) спасибо!!
Спасибо огромное за статью..очень подробно, доходчиво и интересно!!
И ещё....Ваша фраза в статье -- " исключив работу с файлами (которая когда-нибудь будет представлена отдельной статьей)" очень заинтриговала...ждём с нетерпением продолжения.
Спасибо заранее !