Диалоговое окно на ассемблере

Метки:  , ,

Окна, как элемент графического интерфейса Windows, могут быть простыми, информационными, уведомляющими пользователя о каком-либо событии (например, об ошибке), но чаще всего они представляют собой более сложные конструкции, содержащие в собственной клиентской области множество элементов управления. Вне зависимости от внешнего вида графического окна, основное предназначение его заключается в выстраивании взаимодействия с пользователем, поскольку само понятие окна подразумевает некую область, которую приложение отрисовывает с целью предоставить (вывести) информацию или запросить (ввести) её, другими словами обеспечить ввод/вывод данных. Вывод и вывод зачастую комбинируются в пределах одного окна и могут быть организованы нетривиально, например содержать выделенные рамкой области, группирующие однотипные данные, иметь чекбоксы/радиокнопки, обеспечивающие выбор опций, поля для текстового вывода, кнопки, различного рода списки и прочие объекты. Естественно, что создавать а затем и обслуживать в окне большое количество автономных объектов не так просто как хотелось бы: мало того, что для самого окна нужно определять класс, на основании класса уже создавать непосредственно окно, далее описывать оконную процедуру, а в довершение ко всему создавать в пределах окна каждый элемент, а ведь этими элементами придется еще и управлять. Вся эта рутина превращает процесс разработки многоэлементного окна в очень трудоемкую задачу, в которой разработчик сфокусирован более на логике создания окон и элементов, нежели на алгоритме ядра приложения. Решением данной проблемы является рекомендованный Microsoft к использованию особый класс окон, называемый диалоговыми, которые обеспечивают относительную простоту обслуживания окон, содержащих произвольное количество различных (статических и динамических) элементов управления.

Диалоговое окно - предопределенный класс графических окон, спроектированный для организации удобного (с точки зрения разработчика) ввода/вывода информации, характеризующийся упрощенным взаимодействием с дочерними (вложенными) элементами управления.

Диалоговые окна как класс могут использоваться в прикладных программах для реализации ожидания от пользователя какого-либо действия (команды), после произведения которого могут незамедлительно быть уничтожены (закрыты). Так же, диалоговые окна могут применяться для отображения/получения произвольной промежуточной информации, при одновременной работе пользователя с другими окнами приложения, ведь сам термин "диалог" подразумевает, что при помощи данного класса окон выстраивается определенного рода взаимодействие с пользователем. Характерным примером может выступать диалог, реализующий поиск текста в приложении, предназначенном для обработки текстовой информации. В такого рода приложениях часто используется автономное диалоговое окно с несколькими элементами: строкой ввода искомого фрагмента, чекбоксами опций и кнопками подтверждения/отмены. Вот первый попавшийся по руку характерный пример:

пример окна диалога

В процессе поиска диалоговое окно активно, отображается на экране и выводит полученные результаты, тем не менее в любой момент пользователь может переключить фокус на диалог и изучить результаты или же изменить критерии поиска и запустить процесс вновь. Диалоговые окна в приложениях могут создавать в момент активации (произвольной) команды и продолжать отображать в нем необходимую информацию на протяжении заданного промежутка времени, функционирования какого-либо алгоритма, реализующего некую логику, непосредственно до момента закрытия пользователем.
Сами по себе вложенные в диалоговое окно элементы управления являются стандартными и могут применяться во всех без исключения типах окон. Но, для создания элементов (EDIT, STATIC, BUTTON) в классических приложениях нужно описывать каждый из них как отдельное окно, дочернее по отношению к главному окну приложения, с производными отсюда сложностью обслуживания. В случае же использования диалога можно объявить основное окно приложения диалоговым и описать все вложенные элементы в ресурсах. Одним словом, диалоговое окно это фактически обычное окно графического интерфейса, однако создаваемое посредством специализированных функций, обеспечивающих отдельный системный механизм, позволяющий работать с дочерними элементами проще, чем если бы они были созданы с использованием типовых (хорошо знакомых нам) функций и обрабатывались бы соответствующими механизмами. Еще одно неоспоримое достоинство заключается в том, что элементы диалогового окна могут быть определены (описаны) при помощи ресурсов приложения, представляющих собой структуру в удобном для описания и быстром для обхода представлении.
Диалоговое окно может быть использовано несколькими способами:

  • как основное окно: окно создается как единственное, содержащее все необходимые элементы управления и по закрытии завершает все приложение (процесс);
  • как вспомогательное (дочернее) окно: применяется в качестве временного для получения некой промежуточной информации от пользователя, при закрытии уничтожается исключительно данное окно.

Существуют несколько видов диалоговых окон:

  • Модальное диалоговое окно (modal dialog box) -- Создается при помощи подмножества функций DialogBox*. В случае использования данного вида, диалоговое окно создается без родительского окна. Этот подход делает цикл сообщений потока ненужным, а оконная процедура фактически становится процедурой диалогового окна (с некоторыми изменениями), и все сообщения, адресованные окну (и дочерним элементам) посылаются напрямую этой процедуре диалогового окна. Класс окна в этом случае регистрировать не нужно. Отличительной особенностью некоторых подтипов модального диалогового окна является:
    • ограничение в переключении фокуса ввода на другие окна родительского приложения, либо на окна сторонних приложений.
    • прерывание вызывающего диалог процесса/блокировка родительского окна до момента, пока не будет закончена работа с диалоговым окном и оно не будет закрыто.

    Однако, не смотря на подобные явные ограничения, намного чаще применяются модальные диалоговые окна, поскольку зачастую диалог на ассемблере представляет из себя небольшую утилитку, открывающую единственное окно и с закрытием его завершающее своё функционирование, поэтому ограничения никого не пугают :)

    Модальные диалоговые окна более простые в обслуживании, поскольку создаются, используются и уничтожаются несколько проще.
  • Немодальное диалоговое окно (modeless dialog box) -- Создается при помощи подмножества функций CreateDialog*. Отличается от модального диалогового окна тем, что возвращает управление сразу после своего создания, не дожидаясь закрытия собственного окна, никаким образом не воздействуют (не блокируют) родительское окно, типом возвращаемого функцией значения и методами закрытия получившегося диалогового окна. Всё остальное остается в силе: случае использования данного подхода, мы получаем ту же диалоговую процедуру, функционирующую стандартным образом. Этот тип диалоговых окон позволяет предоставлять/получать информацию и переключаться между окнами, возвращаясь к предыдущей задаче, без закрытия окна диалога.

По основному циклу обработки сообщений во всех типах диалоговых окон присутствуют отличия от цикла обычных окон: отсутствуют вовсе такие привычные функции как TranslateMessage и DispatchMessage, в некоторых типах диалоговых окон присутствует функция IsDialogMessage.

Во всех типах модальных диалоговых окон мы сталкиваемся не с привычной нам по обычным приложениям оконной процедурой, а имеем дело уже с так называемой процедурой диалогового окна. Последняя очень похожа на классическую процедуру окна, однако имеет тип возвращаемого значения TRUE/FALSE (вместо штатного LRESULT) и может содержать в себе функции, предназначенные для закрытия диалоговых окон и выполнения некоторых специфичных действий.

Отличия классического окна от диалогового следующие:

  • обслуживается отдельной частью ядра - менеджером диалоговых окон. Именно указанный диспетчер производит обратный вызов процедуры диалогового окна в нашем коде, передавая ей необходимые сообщения;
  • если процедура диалогового окна обрабатывает какое-либо сообщение, она должна вернуть TRUE в регистре eax, если же она не обрабатывает сообщение, тогда она должна вернуть в регистре eax значение FALSE;
  • процедура диалогового окна не передает никаких сообщений функции DefWindowProc, попросту вообще её не использует, так как это не полноценная процедура окна, а лишь отчасти схожая;
  • меньше сообщений, приходящих в процедуру диалогового окна. частично сообщения совпадают с сообщениями типового окна, вместо WM_CREATE приходит WM_INITDIALOG;

В данной статье мы будем рассматривать диалоговые окна в совокупности с различными дочерними (статическими и динамическими) элементами, поскольку само по себе диалоговое окно это всего-лишь один из видов окон графического интерфейса, являющееся, по сути, пустым "каркасом". Согласитесь, что рассматривать диалоговое окно без каких либо элементов не имеет смысла, ведь много ли толка от пустого окна-каркаса, если в нем нет никакой функциональной нагрузки, попросту не с чем взаимодействовать? Поэтому, диалоговое окно создается с целью размещения внутри него неких элементов управления.

Статические/динамические элементы управления (static/dynamic controls, контролы) - объекты (с точки зрения системы те же окна) управления, предназначающиеся для ввода и вывода текста, отрисовки рамок (фреймов), иконок или разделителей в пределах окна.

Динамические элементы управления предназначены для обеспечения ввода информации от пользователя. Статические элементы идеально подходят для целей организации простого, легко обрабатываемого вывода, они не предназначены для приема ввода от пользователя и (таким образом) не отсылают никаких сообщений, уведомляющих о собственных событиях родительскому окну. Поэтому, если даже по элементу щелкнули мышью, никакой информации передаваться в приложение не будет. Тем не менее, сама по себе информация, располагающаяся в статических элементах, может быть изменена в любой момент времени существования элемента управления.

Метод 1: использование DialogBoxParam

В качестве первого практического задания для данного материала мы рассмотрим пример довольно простенькой программы, выводящей при старте одно-единственное диалоговое окно, размещающее в себе некоторые часто используемые на практике элементы управления (комбобоксы, кнопки, поля вывода и редактирования). Можно сказать, что наше приложение будет (с большим натягом) попадать в категорию программ-модификаторов, которые по-английски именуются "Patch", или заплатка для бинарного файла. Однако, полноценный патчер достаточно громоздок в качестве учебного материала начального уровня, что для детального описания в одной статье будет явно избыточным, поэтому мы реализуем алгоритм упрощенного прототипа (обертки, каркаса), который будет обеспечивать лишь вывод информации в окно редактирования, исключив работу с файлами (которая когда-нибудь будет представлена отдельной статьей).
В качестве базового шаблона для нашей будущей программы мы используем распространяемый в составе дистрибутива исходный текст модуля под названием \EXAMPLES\DIALOG\dialog.asm, с внесением соответствующих модификаций, характерных для поставленной нами задачи. Для удобства дальнейшего изложения, сперва традиционно приведем получившийся исходный код:

Ну и раз уж речь зашла об исходном коде, сразу давайте представим тут и результат запуска исполняемого файла приложения, познакомимся так сказать с визуальным представлением интерфейса:

Окно патчера

Для тех, кто ознакомился с предшествующими моими материалами либо имеет уже некоторый опыт программирования приложений на ассемблере, становится очевидным, что по структуре наша программа на типичные приложения не очень то и похожа, имеется несколько глобальных отличий. Причиной подобной ситуации является использование в коде функции создания модального диалогового окна DialogBoxParam, которая накладывает некоторые до селе нам не известные обязательства на структуру разрабатываемого приложения:

  • Нет необходимости регистрировать класс окна;
  • Нет необходимости создавать окно (например, функцией CreateWindowEx) на основе ранее зарегистрированного класса;
  • Процедура сообщений окна преобразуется в так называемую процедуру диалога, которая представляет собой упрощенный обработчик сообщений;
  • Отсутствует функция обработки не обрабатываемых сообщений DefWindowProc;
  • Меняются некоторые типы кодов возврата из обработчиков сообщений;
  • Меняются общие правила возврата из процедуры.

Как можно заметить, нововведения многочисленны, но на самом деле к ним можно адаптироваться и они не столь существенно меняют логику работы приложения. Я так понимаю, судя по замыслу специалистов из Microsoft, нововведения призваны упростить использование диалогов, привлекая несколько упрощенной моделью обработки сообщений.

Функция для создания диалогового окна DialogBoxParam упрощает структуру приложения, поскольку сразу создает окно диалога со всеми необходимыми дочерними элементами на основе шаблона диалогового окна. Но "под капотом" всё довольно прозаично - происходит циклический вызов функции CreateWindowEx для каждого создаваемого окна/элемента управления.

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

Шаблон диалогового окна (dialog box template) - описание непосредственно диалога и вложенных элементов управления. Можно создавать этот шаблон в виде ресурса, загружаемого непосредственно из исполняемого файла приложения, или же построить его "на лету" в памяти процесса.

Сам по себе шаблон представляет определенным образом сконструированное описание (разметку) всех элементов создаваемого диалогового окна. Наиболее часто шаблон создается в секции ресурсов либо вручную, либо импортируется из внешнего файла на этапе компиляции. В ресурсах элементы диалога описываются при помощи специализированных макродиректив: директории ресурсов и вложенных в неё непосредственно самих ресурсов. Шаблонный метод довольно удобен, поскольку существенно упрощает структуру приложения, исключая необходимость создавать дочерние элементы управления при помощи функций. С другой стороны, все эти особенности разных типов окон самым беспощадным образом воздействуют на неокрепший ум начинающего программиста, что, в свою очередь, серьёзно влияет на мировоззрение последнего :)

Начальная инициализация

В "шапке" исходного кода у нас представлены описания констант, используемых в приложении, это идентификаторы 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. Традиционно, подобная процедура во всех диалоговых приложениях является ключевой, основополагающей логикой.

Процедура диалога (диалоговая процедура, dialog box procedure) - созданная программистом функция обратного вызова, обрабатывающая сообщения, посылаемые системой диалоговому окну.

Отдельно хотелось бы отметить характерные для процедуры диалогового окна отличия в обработке сообщений:

  • Если хотя бы одно из сообщений было обработано, то на выходе процедуры диалога, в регистре eax должен быть выставлено значение 1.
  • Если входящее сообщений не принадлежит ни к одну из обрабатываемых нашей процедурой диалогового окна, иными словами если наша процедура диалога не обработала ни одного сообщения, то на выходе, в регистре eax, мы должны возвратить 0. В этом случае операционная система сама выполнит необходимые действия по обработке подобных сообщений. По сути, поведение аналогично обработке необрабатываемых сообщений в типовой оконной процедуре, однако не производится непосредственного вызова функции DefWindowProc, можно предположить что она вызывается где-то в недрах кода DialogBoxParam.
  • На этапе создания диалога, в процедуру диалога не поступает сообщение WM_CREATE (как в традиционной процедуре), заместо него система посылает WM_INITDIALOG.

В строках 28-33 расположен блок проверки входящих сообщений. В зависимости от поступившего сообщения, код маршрутизируется по соответствующим процедурам обработчиков. Для начала разберемся с сообщением WM_INITDIALOG, которое (как уже упоминалось) поступает на этапе создания диалога, до отображения окна на рабочем столе. Обычно данное сообщение не обрабатывается, но в нашем примере мы его задействует, поскольку именно в нем мы проведем получение/сохранение дескриптора поля редактирования и произведем в это поле вывод самой первой строки статуса. Как вы уже наверное поняли, поле редактирования у нас будет использоваться как импровизированное пространство логирования операций, производимой приложением. В строке 37, при помощи функции GetDlgItem, получаем дескриптор поля редактирования нашего окна, сохраняем его в переменную hEdit для дальнейшего использования. Сразу после следует вызов внутренней самописной процедуры GetTimeText, которая предназначена для получения строки, содержащей текущее время в текстовом формате.

Внутренняя процедура GetTimeText (определена в строках 73-81) предназначается для создания текстовой строки с текущим временем в формате ЧЧ:ММ:СС (пример: 12:31:59), так называемой "временной метки". Единственный входной параметр pTimeStr задает адрес строки для записи результата. Временная метка используется в качестве префикса (начальной части) каждой выводимой строки статуса, поступающей в окно редактирования, с целью отразить время выполнения операции. Время запрашивается у системы через функцию GetLocalTime, которая возвращает значения в структуру systime прототипа SYSTEMTIME (описана в строке 97). После получения выбираем из структуры требующиеся нам значения часов, минут и секунд в соответствующие регистры и затем форматируем вывод при помощи функции wsprintf. Функция wsprintf является мощнейшим системным форматером, который создает результирующую строку из значений входных параметров (у нас заданы регистрами eax, ebx, ecx), преобразуя их тип/длину в соответствии с задаваемым специальными спефицикаторами шаблоном вывода (переменная tBufferFormat). Проще, функция берет входные значения, конвертирует их (в соответствии с задаваемыми требованиями) и помещает в выходной буфер.

Вернемся к коду. Выводимая в поле редактирования строка всегда формируется у нас в буфере editOutput. Затем к буферу добавляется текстовый статус операции, например в строке 41 к получившемуся буферу (пока содержит только время) добавляем при помощи функции lstrcat текст "Engine Started". Полученную таким образом результирующую строку выводим в поле редактирования с использованием одной из функций работы с текстом в диалоге SetDlgItemText.

Общение с элементами диалогового окна производится при помощи ряда специализированных функций и состоит, фактически, в посылке им определенных сообщений. Для того, что бы передать текст в элемент управления, используется функция SendDlgItemMessage, которая отправляет сообщение WM_SETTEXT требуемому элементу управления, содержащее в собственном lParam указатель на строку выводимого текста. В параметрах функции указываются дескриптор диалогового окна и идентификатор элемента-получателя.

На этом код обработки сообщения WM_INITDIALOG у нас заканчивается.
Напомним, что процедура диалога обеспечивает обработку сообщений не только самого диалогового окна, но и всех дочерних элементов управления (контролов) и осуществляет это посредством уведомлений (кодов производимых над элементами управления простых действий). В момент, когда над элементом управления производится какое-либо действие (например, нажатие клавиши), система формирует уведомление и передает его в процедуру диалога через параметры сообщения WM_COMMAND, то есть как бы "внутри" сообщения. Именно поэтому сообщение WM_COMMAND представляет для нас особый интерес, ведь именно через него поступает уведомления о некоторых ключевых событиях, например через параметры данного сообщения передаются идентификаторы нажатых в нашем диалоговом окне клавиш.

Коды уведомлений нажатых в диалоговом окне кнопках (BN_CLICKED) поступают в параметрах входящего сообщения WM_COMMAND, которое состоит из двух двойных слов-параметров (DWORD, 32 бита): wParam и lParam. Сообщение WM_COMMAND в параметре lParam содержит дескриптор контрола, а параметр wParam разбивается на код уведомления (старшее слово) и идентификатор контрола (младшее слово).

Когда пользователь нажимает клавишу в нашем окне, система передает в процедуру диалога сообщение WM_COMMAND, соответственно для определения нажата ли в окне интересующая нас клавиша, мы проверяем наличие кода уведомления BN_CLICKED в старшем слове и идентификатора клавиши в младшем слове параметра wParam. Подобная проверка реализована в строках 45 и 47, но выглядит она для начинающих не совсем типично:

Что за интересная форма записи? Если присмотреться, то начинаешь понимать, что в правой части вычисляется следующее: берется константа BN_CLICKED (значение 0, разрядность 16 бит), над которой выполняется битовый сдвиг на 16 разрядов влево, тем самым перемещая её значение из нижнего слова в верхнее и образуя 32-битное значение, затем операцией "логическое ИЛИ" (+) добавляется идентификатор интересующей нас клавиши (в нашем коде IDD_BUTTONPATCH, IDD_BUTTONEXIT). Получающееся в результате 32-битное значение в точности соответствует тому, которое поступает в wParam сообщения WM_COMMAND при нажатии в окне клавиши Exit.

Идентификаторы часто используемых в приложениях кнопок, таких как OK (1), Cancel (2), Abort (3), Retry (4), Ignore (5), Yes (6), No (7), а так же коды уведомлений (BN_CLICKED и прч.) определены в подключаемых файлах USER32.INC/USER64.INC и доступны для использоваться в коде в качестве констант. У нас эти константы в коде не используются, однако они бесспорно удобны.

Затем получившуюся константу сравнивают командой cmp с содержимым параметра wParam сообщения WM_COMMAND и таким образом определяют, была ли нажата (та или иная) клавиша или нет. Думается, что это хороший алгоритм проверки, поэтому определенно стоит использовать его в своем коде.
Стоит обратить внимание так же и на код обработки клавиши Patch, который начинается у нас с метки .dopatch. Тут опять же мы вызываем процедуру GetTimeText для формирования начальной части статусной строки, добавляем к получившейся строке уже сообщение "Patched successfully" и делаем вывод в наше поле редактирования.

Однако, тут имеется один тонкий момент, дело в том, что в окне лога мы хотим обеспечить добавление поступающих вновь сообщений со сдвигом всего уже имеющегося в поле текста вверх. Для реализации этого алгоритма пришлось долго экспериментировать, в итоге было найдено несколько красивых и не очень решений, однако одним из самых простых (и правильных) оказалось решение, рекомендованное самой Microsoft. Оно представлено в строках 59-61 и заключается в предварительном получении размера уже размешенного в элементе редактирования текста, установке каретки на последнюю позицию в окне (сообщение EM_SETSEL) и выводе строки при помощи передачи сообщения замены EM_REPLACESEL с аргументами.

Завершает процедуру диалога у нас обработчик сообщения 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), которые прозрачно пересчитываются в пиксели в зависимости от используемого в диалоге шрифта. Я задавал в шаблоне разнообразные размеры шрифта, и смотрел на результат, получалось, что размеры окна менялись. Если шрифт в шаблоне не выставляется, пересчёт значений производится исходя из параметров используемого по умолчанию системного шрифта.

Комментарии: 5

  1. Marylin

    Достаточно подробное описание. Нигде такого не встречал.
    Спасибо за безвозмездный труд!

    1. einaare

      статья не окончена. вторая часть с использованием функций CreateDialog* не описана. доделаю как-нибудь, при желании :)

  2. Цитата:
    "затем операцией "логическое И" (+) добавляется идентификатор интересующей нас клавиши"
    Здесь операция "логическое ИЛИ" фактически сложение. Опечатка?

    1. einaare

      "опечатка? не думаю!!" :))) спасибо!!

  3. Владимир

    Спасибо огромное за статью..очень подробно, доходчиво и интересно!!
    И ещё....Ваша фраза в статье -- " исключив работу с файлами (которая когда-нибудь будет представлена отдельной статьей)" очень заинтриговала...ждём с нетерпением продолжения.
    Спасибо заранее !

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *