WinAPI - диалоговые окна и элементы управления из файла ресурсов |
25.08.2009 Вадим Исаев |
Эта статья является дополнением к книге Основы программирования для Win32 на Free Pascal Ивана Шихалева.
Иногда возникает необходимость написать небольшие оконные программки, чтобы в них было и минимум кода и минимальный размер после компиляции. Визуальные проектировщики интерфейсов, вроде Delphi или Lazarus не подходят, т.к. после них получаются бинарные файлы чудовищных размеров. Да и не секрет, что объектно-ориентированные интерфейсы слегка замедляют работу программы.
Казалось бы выход очевиден - использовать WinAPI. Но и тут не всё слава Богу - на одно окошко с парой кнопок и парой полей ввода текста приходится ужасающее количество написанного программистом кода. Да и от визуального проектирования отказываться неохота. :-)
Есть два выхода из этой ситуации:
Итак, берём любой визуальный редактор ресурсов который вам нравится (я использовал resed) и разрабатываем интерфейс нашей программы.
У FreePascal, на данном этапе развития, есть одна интересная особенность. В файл программы можно включить текстовый, неоткомпилированный файл ресурсов. При этом при компиляции основной програмы файл ресурсов будет автоматически откомпилирован и прилинкован:
{$R *.rc}
Однако если в файле .rc будет содержаться какая-нибудь ошибка, то создание .res файла и линковка произведены не будут. Внятного сообщения об ошибке, которая содержится в файле ресурсов, тоже не будет. Поэтому если вы хотите поэкспериментировать с файлом .rc, то сначала компилируйте его вручную компилятором ресурсов:
windres -o calendar.res calendar.rcдо тех пор, пока у вас не выйдет то, что вы хотели увидеть. После этого файл .rc можно поместить рядом с основной программой и включить его в текст, чтобы он компилировался автоматически.
В исходнике программы так же можно указать уже готовый, откомпилированный файл ресурсов, тогда он просто будет прилинковываться к основной программе в процессе линковки:
{$R *.res}
К любому элементу управления диалогового окна программа обращается по его числовому идентификатору:
CONTROL "ListBox1",1003,"ListBox" DlgDirList(wnd, 'c:', 1003, 0, DDL_DIRECTORY);
Программа может посылать сообщения улементам управления диалогового окна тоже пользуясь его идентификатором с помощью функции:
SendDlgItemMessage(wnd, IDC_EDT1, WM_SETTEXT, 0, LPARAM(PChar(Traffic.bytes)));
Это довольно удобно, т.к. в противном случае, если пользоваться стандартной функцией SendMessage(), пришлось бы предварительно узнавать дескриптор окна каждого элемента.
Поскольку числовые идентификаторы ничего не говорят ни уму ни сердцу, полезно, вместо них, объявить сонстанты с внятными названиями и обращаться к окнам и элементам управления по этим константам.
Для ресурсного файла (mycontrols.h):
#define IDD_DLG1 1000 #define IDC_MVI1 1001
Мой редактор выбирает название и номер самостоятельно. Полезно выделить идентификаторы в отдельный включаемый файл, чтобы потом автоматически его преобразовать с помощью утилиты h2pas.exe, которая имеется в стандартной поставке FreePascal, в pas-файл, который мы включим в программу.
h2pas mycontrols.h
В результате получим файл mycontrols.pp с константами для элементов управления в интерфейсной части.
К ресурсному файлу файл с объявлением идентификаторов подключается так же как и в программах написаных на Си:
#include "mycontrols.h"
Для задания стилей диалоговых окон и элементов управления:
STYLE WS_VISIBLE|WS_OVERLAPPEDWINDOW и т.п.
Мой редактор ресурсов использует мнемонические идентификаторы, которые он, вне всякого сомнения, взял из заголовочных файлов Си, однако подключить самостоятельно нужные заголовочные файлы не потрудился. Придётся это сделать вручную.
Поскольку у меня стоит GCC, то с подключением заголовочного файла нет никаких проблем, но если у вас нет никаких компиляторов Си, то придётся либо им обзавестись, либо попросить у кого-нибудь заголовочный файл winuser.h, в котором находятся почти все нужные для диалоговых окон и элементов управления мнемонические идентификаторы и подключать его к файлу ресурсов:
#include "winuser.h"
Обращаю ваше внимание, что все включаемые заголовочные файлы, которые взяты в двойные кавычки, должны лежать в том же каталоге, где расположен и основной файл .rc.
Основная программа состоит из четырёх обязательных частей:
Сначала заполняется специальная оконная структура типа WNDCLASS или WNDCLASSEX. Они содержат одни и те же поля, однако WNDCLASSEX имеет дополнительные поля:
Скомпилированая с WNDCLASSEX программа имеет несколько больший размер.
Главные поля класса:
Регистрация класса проводится процедурой RegisterClass() для типа WNDCLASS и RegisterClassEx() для типа WNDCLASSEX.
Диалоговое окно создаётся функцией CreateDialog(hInst, Templ, WndParent, DlgProc).
Здесь:
Перед использованием компонентов, которые не относятся к вкладке "Стандартные" в Lazarus, в программе неоходимо вызвать процедуру инициализации библиотеки дополнительных компонентов перед их использованием:
InitCommonControls;иначе дополнительные компоненты работать не будут.
Для некоторых компонентов так же придётся загружать собственные библиотеки перед их использованием. К примеру, чтобы увидеть в программе компонент RichEdit, необходимо загрузить библиотеку RICHED32.DLL:
LoadLibrary('riched32.dll');
В этом цикле отлавливаются все сообщения направленные окну или системе и посылаются для обработки в процедуру обработки сообщений.
В этой процедуре обрабатываются все оконные сообщения. Каждое диалоговое окно в программе может иметь собственную процедуру обработки сообщений, которые поступают именно из этого окна, а могут обработку сообщений передавать процедуре главного окна или, точнее, процедуре класса главного окна. Для второго случая последний параметр функции CreateDialog() будет равен NIL.
Насколько предпочтительней тот или иной стиль обработки сообщений сказать нельзя, это зависит от функционала программы, однако для маленьких программ, вроде той, что мы пишем, с одним или, возможно, двумя диалоговыми окнами, все сообщения можно обрабатывать одной процедурой, чтобы не разводить зоопарк из оконных процедур-обработчиков.
Если я работаю на компьютере под обычным пользователем (не администратором) и хочу узнать какой сегодня день недели, щёлкаю по часам в правом нижнем углу экрана и получаю весёлое сообщение от Билла Гейтса, что у меня нет прав для этого. :-)
Создадим в редакторе ресурсов окошко с размещённым на нём календарём на текущий месяц. Поскольку программа нужна только для того, чтобы полюбоваться на текущую дату и день недели, то никакие сообщения, кроме закрытия окна, она обрабатывать не будет. Код получается очень компактным и откомпилированный бинарник тоже. Попробуйте, для сравнения, сделать то же самое в Lazarus'е или в Delphi - размер бинарника такой программы повергнет вас в лёгкую панику. :-)
Программа периодически (раз в 3 секунды, поскольку это всего лишь демо-пример) обращается к базе данных, где ранится наш трафик, получает запросом оттуда данные и индицирует их в маленьком окошке.
Здесь кода уже намного больше чем в предыдущем примере. В оконной процедуре добавлены обработчики сообщений таймера (взятие из базы текущего трафика) и события создания окна (открытие базы, создание таймера, получение предварительных данных). Так же введена дополнительная процедура обработки сообщения закрытия окна (удаление таймера, закрытие базы данных). Однако можно заметить, что специфического кода WinAPI, для работы с элементами управления, не много.
Кнопка в этой программе не имеет никакого реального значения и добавлена лишь для того, чтобы продемонстрировать обработку сообщений WM_COMMAND.