Публикации FreePascal

WinAPI - диалоговые окна и элементы управления из файла ресурсов

25.08.2009
Вадим Исаев

Эта статья является дополнением к книге Основы программирования для Win32 на Free Pascal Ивана Шихалева.

Иногда возникает необходимость написать небольшие оконные программки, чтобы в них было и минимум кода и минимальный размер после компиляции. Визуальные проектировщики интерфейсов, вроде Delphi или Lazarus не подходят, т.к. после них получаются бинарные файлы чудовищных размеров. Да и не секрет, что объектно-ориентированные интерфейсы слегка замедляют работу программы.

Казалось бы выход очевиден - использовать WinAPI. Но и тут не всё слава Богу - на одно окошко с парой кнопок и парой полей ввода текста приходится ужасающее количество написанного программистом кода. Да и от визуального проектирования отказываться неохота. :-)

Есть два выхода из этой ситуации:

  1. Использовать готовые сторонние библиотеки, которые разрабатывались специально для уменьшения исходного кода при создании элементов управления. Для Delphi это библиотеки acl (к сожалению давно заброшенная автором) и kol, которая была создана на основе acl.
    Преимущества: При очень маленьком размере бинарника элементы управления создаются буквально парой строчек кода.
    Недостатки: библиотека kol, которая разрабатывалась специально для работы с Delphi, иногда неправильно работает с FreePascal. acl же вообще не удасться откомпилировать с помощью FreePascal без значительной переработки библиотеки.
  2. Всё-таки использовать WinAPI, но разрабатывать интерфейс программы с помощью какого-либо визуального редактора ресурсов.
    Преимуществa: при визуальной разработке можно сразу видеть то, что наваял. Подключение файла ресурса к файлу программы прямо в тексте программы с помощью директивы компилятору {$R *.rc} или {$R *.res} (почему вознили два варианта будет сказано далее).
    Недостатки: Всё-таки кода для обработки событий элементов управления требуется несколько больше, чем при написании тех же элементов с помощью классов (VCL в Delphi или LCL в Lazarus). Однако если элементов управления не много, то количество кода увеличивается не сильно. ;-)

Создание ресурсного файла

Итак, берём любой визуальный редактор ресурсов который вам нравится (я использовал 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.

Создание файла программы

Основная программа состоит из четырёх обязательных частей:

  1. Создание и регистрация класса главного окна.

    Сначала заполняется специальная оконная структура типа WNDCLASS или WNDCLASSEX. Они содержат одни и те же поля, однако WNDCLASSEX имеет дополнительные поля:

    • cbSize - сюда заносится размер WNDCLASSEX
    • hIconSm - сюда заносится "маленькая иконка" приложения. Если маленькую иконку использовать не предполагается, то присваивается ноль.

    Скомпилированая с WNDCLASSEX программа имеет несколько больший размер.

    Главные поля класса:

    • lpfnWndProc - сюда заносится адрес процедуры обработки сообщений главного окна программы.
    • cbWndExtra - здесь, если диалоговое окно создаётся из ресурсов, должно присутствовать волшебное слово DLGWINDOWEXTRA.
    • lpszClassName - здесь должно стоять название класса основного окна. Обращаю ваше внимание, что название должно быть точно таким же, как и название CLASS у того диалогового окна в ресурсе, которое будет главным.

    Регистрация класса проводится процедурой RegisterClass() для типа WNDCLASS и RegisterClassEx() для типа WNDCLASSEX.

  2. Создание диалоговых окон, информация о которых берётся из ресурсов.

    Диалоговое окно создаётся функцией CreateDialog(hInst, Templ, WndParent, DlgProc).

    Здесь:

    • hInst - хэндл приложения,
    • Templ - идентификатор окна приведённый к типу PChar,
    • WndParent - окно-владелец, если это будет главное окно, то указать 0,
    • DlgProc - адрес функции-обработчика сообщений от этого окна
      если указать NIL, то обработка будет проводится функцией оконного класса.

    Перед использованием компонентов, которые не относятся к вкладке "Стандартные" в Lazarus, в программе неоходимо вызвать процедуру инициализации библиотеки дополнительных компонентов перед их использованием:

    InitCommonControls;
    иначе дополнительные компоненты работать не будут.

    Для некоторых компонентов так же придётся загружать собственные библиотеки перед их использованием. К примеру, чтобы увидеть в программе компонент RichEdit, необходимо загрузить библиотеку RICHED32.DLL:

    LoadLibrary('riched32.dll');

  3. Запуска цикла обработки сообщений главного окна программы.

    В этом цикле отлавливаются все сообщения направленные окну или системе и посылаются для обработки в процедуру обработки сообщений.

  4. Процедуры обработки сообщений главного окна программы.

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

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

От теории к практике

  • Пример1. Программа-календарь.

    Если я работаю на компьютере под обычным пользователем (не администратором) и хочу узнать какой сегодня день недели, щёлкаю по часам в правом нижнем углу экрана и получаю весёлое сообщение от Билла Гейтса, что у меня нет прав для этого. :-)

    Создадим в редакторе ресурсов окошко с размещённым на нём календарём на текущий месяц. Поскольку программа нужна только для того, чтобы полюбоваться на текущую дату и день недели, то никакие сообщения, кроме закрытия окна, она обрабатывать не будет. Код получается очень компактным и откомпилированный бинарник тоже. Попробуйте, для сравнения, сделать то же самое в Lazarus'е или в Delphi - размер бинарника такой программы повергнет вас в лёгкую панику. :-)

  • Пример2. Программа следящая за расходуемым трафиком.

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

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

    Кнопка в этой программе не имеет никакого реального значения и добавлена лишь для того, чтобы продемонстрировать обработку сообщений WM_COMMAND.

Скачать примеры.

Актуальные версии
FPC3.2.2release
Lazarus3.2release
MSE5.10.0release
fpGUI1.4.1release
links