Вы здесь

Программирование в Delphi. Трюки и эффекты. Глава 2. Уменьшение размера EXE-файла. Использование Windows API (А. А. Чиртик, 2010)

Глава 2

Уменьшение размера EXE-файла. Использование Windows API

Источник лишних килобайт

Создание окна вручную

Окно с элементами управления

Стандартные окна Windows

Установка шрифта элементов управления


Не секрет, что размер скомпилированного EXE-файла Delphi часто значительно превосходит размер программ, написанных с использованием сред разработки от Microsoft (например, Visual C++, Visual Basic).

Примечание

Здесь и далее имеются в виду приложения с оконным интерфейсом (не консольные).

При разработке крупных проектов этот факт абсолютно не смущает. Однако что делать, если программисту на Delphi нужно написать программу, занимающую как можно меньше места (например, инсталлятор) или загружающуюся за минимальное время (например, сервисную программу)? Конечно, такое приложение можно написать на C++, но опять же, что делать, если осваивать новый язык программирования нет времени?

В этой главе будет рассмотрен способ уменьшения размера EXE-файла: отказ от библиотеки Borland за счет прямого использования Windows API. Данный способ позволяет уменьшить размер приложения, однако написание Delphi-приложения (да еще и с оконным интерфейсом) с использованием только API-функций является задачей весьма трудоемкой, хотя и интересной, да к тому же и экзотичной.

Вначале небольшое отступление. Операционная система (в данном случае Windows) предоставляет интерфейс для программирования внутри себя – набор функций, заключенных в нескольких системных библиотеках, называемый Windows API (Windows Application Programming Interface – интерфейс программирования Windows-приложений). Любой проект под Windows на любом языке программирования в конечном счете сводится именно к приложению, использующему функции WindowsAPI. Использование этих функций может быть как явным, так и скрытым за использованием библиотек, поставляемых вместе со средой программирования.

Еще один момент: в тексте постоянно говорится о Windows API, а не просто API. Это потому, что само понятие Application Programming Interface применяется ко многим системам, а не только к операционным системам, и уж тем более не только к Windows. Вот несколько примеров: UNIX API, Linux API, Oracle API (интерфейс для работы с СУБД Oracle) и т. д.

Примечание

В книге описаны только те возможности Window API, которые непосредственно используются в примерах. Полное описание Windows API является слишком большой задачей, для которой не хватит и книги. Если вам захочется изучить или хотя бы узнать больше о Windows API, можно будет обратиться к специализированным изданиям по этой теме. Однако никакое издание не заменит MSDN (огромная справочная система от Microsoft для Visual Studio).

Теперь выясним, за счет чего разрастается EXE-файл приложения при использовании среды программирования Delphi.

Источник лишних килобайт

Для начала создадим новый проект Windows-приложения (Project1.exe). По умолчанию создается и показывается одна пустая форма (объявлена в модуле Unit1.pas). Ничего менять не будем, просто скомпилируем и посмотрим размер EXE-файла. Больше 300 Кбайт – не многовато ли для такого простого приложения?

Кстати, простейшее оконное приложение, написанное на Visual C++ 6.0 (в Release-конфигурации, то есть без отладочной информации в EXE-файле) без использования MFC имеет размер 28 Кбайт, а с использованием библиотеки MFC – 20 Кбайт. Простейшее оконное приложение на Visual Basic 6.0 занимает всего 16 Кбайт.

Из-за чего такая разница? Посмотрим, какие библиотеки используются приложениями, написанными на этих языках программирования. Это можно сделать, например, с помощью программы Dependency Walker, входящей в комплект Microsoft Visual Studio (рис. 2.1).

Рис. 2.1. Библиотеки, используемые приложениями


Как видно, приложение на Delphi (правый верхний угол окна на рис. 2.1) использует приличный набор функций, помещенных в стандартные библиотеки операционной системы Windows. Кроме библиотек операционной системы, приложение на Delphi ничего не использует.

Приложение WinAPI.exe (левое верхнее окно) является примером чистого Windows API-приложения в том смысле, что в нем не задействованы библиотеки-оболочки над API-функциями, каким-либо образом облегчающие программирование. Собственно, именно такой объем и занимает простейшее оконное приложение.

С приложением MFC.exe уже интереснее: размер самого EXE-файла уменьшился за счет того, что часть кода работы с API-функциями переместилась в библиотеки. С приложением на Visual Basic еще интереснее (правое нижнее окно). Приложение, написанное на этом языке, компилируется в исполняемый бинарный код, фактически представляющий собой вызовы функций одной библиотеки, в которой и реализована вся поддержка программирования на Visual Basic (при детальном рассмотрении этой библиотеки в ней можно найти объявления встроенных функций Visual Basic).

К чему это все? Да к тому, что приложения на других языках программирования (в данном случае речь идет о продуктах Microsoft) не менее «тяжеловесны», чем приложения, написанные на Borland Delphi, если при их написании программист пользуется не только API-функциями. Особенно примечателен в этом случае пример исполняемого файла Visual Basic, который, хотя и имеет малый размер, требует наличия библиотеки, размер которой равен около 1,32 Мбайт. Программа на Visual C++ с использованием, например MFC, в которой реализованы классы оболочки над функциями Windows API (правда, не только они), требует наличия нескольких DLL-файлов. Для Microsoft это не проблема, так как операционная система Windows выпускается именно этой компанией, а следовательно, обеспечить переносимость (здесь – работоспособность без установки) приложений, написанных с использованием ее же сред разработки, очень просто – достаточно добавить нужные библиотеки в состав операционной системы.

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

Примечание

Кстати, проект на Visual C++ также можно статически скомпоновать с библиотекой MFC (то есть включить код реализации классов в сам EXE-файл). Таким способом можно добиться независимости приложения от различных библиотек, кроме тех, которые гарантированно поставляются с Windows, но при этом размер EXE-файла рассмотренного выше приложения (в Release-конфигурации) возрастет до 192 Кбайт.

Теперь обратимся к проекту на Delphi. Посмотрим, что записано в файлах Unit1.pas и Project1.dpr. Текст файла Unit1.pas приведен ниже (листинг 2.1).

Листинг 2.1. Содержимое файла Unitl.pas

unit Unit1;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs;

type

TForm1 = class(TForm)

private

{Private declarations}

public

{Public declarations}

end;

var

Form1: TForm1;

implementation

{$R *.dfm}

end.


Обратите внимание на секцию uses. Здесь перечислены девять подключаемых модулей, объявлен собственно класс формы TForm1, а также записана строка, указывающая компилятору на использование файла ресурсов. Все модули, кроме первых двух, – труды компании Borland, облегчающие жизнь простым программистам. Модуль такого же рода используется и в файле Project1.dpr (листинг 2.2).

Листинг 2.2. Содержимое файла Project1.dpr

program Project1;

uses

Forms,

Unit1 in 'Unit1.pas'{Form1};

{$R *.res}

begin

Application.Initialize;

Application.CreateForm(TForm1, Form1);

Application.Run;

end.


Теперь обратите внимание на модули Windows и Messages. В первом определены константы, структуры данных, необходимые для работы с функциями Windows API, и, конечно же, объявлены импортируемые из системных библиотек API-функции. В модуле Messages можно найти определения констант и структур, предназначенных для работы с Windows-сообщениями (подробнее об этом смотрите в подразделе «Реакция на сообщения элементов управления» гл. 2 (стр. 66)).

Собственно, этих двух модулей должно хватить для того, чтобы реализовать оконное приложение, использующее, правда, только стандартные функции Windows API и стандартные элементы управления. В листинге 2.3 приведен пример элементарного Windows-приложения. Главное, на что сейчас стоит обратить внимание, – это размер приложения: всего 15 Кбайт.

Листинг 2.3. Элементарное приложение

program WinAPI;

uses

Windows, Messages;

{$R *.res}

begin

MessageBox(0, 'This is a test', 'Little application', MB_OK);

end.


Зачастую полностью отказываться от классов, реализованных Borland, неоправданно, но для чистоты эксперимента в этой главе будут рассмотрены радикальные примеры, построенные на использовании только Windows API.

Создание окна вручную

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

1. Зарегистрировать класс окна с использованием функции RegisterClass или RegisterClassEx.

2. Создать экземпляр окна зарегистрированного ранее класса.

3. Организовать обработку сообщений, поступающих в очередь сообщений.

Пример того, как можно организовать регистрацию класса окна, приведен в листинге 2.4.

Листинг 2.4. Регистрация класса окна

function RegisterWindow():Boolean;

var

wcx: WNDCLASSEX;

begin

ZeroMemory(Addr(wcx), SizeOf(wcx));

//Формирование информации о классе окна

wcx.cbSize:= SizeOf(wcx);

wcx.hInstance:= GetModuleHandle(nil);

wcx.hIcon:= LoadIcon(0, IDI_ASTERISK); //Стандартный значок

wcx.hIconSm:= wcx.hIcon;

wcx.hCursor:= LoadCursor(0, IDC_ARROW); //Стандартный указатель

wcx.hb rBackground:= GetStockObject(WHITE_BRUSH); //Серый цвет фона

wcx.style:= 0;

//..самые важные параметры

wcx.lpszClassName:= 'MyWindowClass'; //Название класса

wcx.lpfnWndProc:= Addr(WindowFunc); //Адрес функции обработки сообщений

//Регистрация класса окна

RegisterWindow:= RegisterClassEx(wcx) <> 0;

end;


Здесь существенным моментом является обязательное заполнение структуры WNDCLASSEX информацией о классе окна. Наиболее необычной вам должна показаться следующая строка:


wcx.lpfnWndProc:= Addr(WindowFunc); //Адрес функции обработки сообщений


Здесь сохранен адрес функции WindowFunc (листинг 2.5) – обработчика оконных сообщений (называемый также оконной процедурой). После вызова функции RegisterClassEx система запомнит этот адрес и будет вызывать указанную функцию-обработчик каждый раз при необходимости обработать сообщение, пришедшее окну. Код простейшей реализации функции WindowFunc приведен в листинге 2.5.

Листинг 2.5. Функция обработки сообщений

//Функция обработки сообщений

function WindowFunc(hWnd:HWND; msg:UINT; wParam:WPARAM; lParam:LPARAM):LRESULT; stdcall;

var

ps: PAINTSTRUCT;

begin

case msg of

WM_CLOSE:

if (hWnd = hMainWnd) then

PostQuit Message(0); //При закрытии окна – выход

WM_PAINT:

begin

//Перерисовка содержимого окна

BeginPaint(hWnd, ps);

Text Out(ps.hdc, 10, 10, 'Текст в окне', 12);

EndPaint(hWnd, ps);

end;

else

begin

//Обработка по умолчанию

WindowFunc:= DefWindowProc(hWnd, msg, wParam, lParam);

Exit;

end;

end;

WindowFunc:= S_OK; //Сообщение обработано

end;


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

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

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

Листинг 2.6. Регистрация и создание окна. Цикл обработки сообщений

program Window;

uses

Windows, Messages;

{$R *.res}

var

hMainWnd: HWND;

mess: MSG;

...

begin

//Создание окна

if not RegisterWindow() then Exit;

hMainWnd:= CreateWindow(

'MyWindowClass', //Имя класса окна

'Главное окно', //Заголовок окна

WS_VISIBLE or WS_OVERLAPPEDWINDOW,//Стиль окна (перекрывающееся, видимое)

CW_USEDEFAULT, //Координата X по умолчанию

CW_USEDEFAULT, //Координата Y по умолчанию

CW_USEDEFAULT, //Ширина по умолчанию

CW_USEDEFAULT, //Высота по умолчанию

HWND(nil), //Нет родительского окна

HMENU(nil), //Нетменю

GetModuleHandle(nil),

nil);

//Запуск цикла обработки сообщений

while (Longint(GetMessage(mess, HWND(nil), 0, 0)) <> 0)

do begin

TranslateMessage(mess);

DispatchMessage(mess);

end;

end.


В листинге 2.6 на месте многоточия должны находиться коды функций WindowFunc и Regis terWindow. При создании окна использовались только стили WS_VI SIBLE и WS_OVERLAPPEDWINDOWS. Но это далеко не все возможные стили окон. В приложении 2 приведен список всех стилей окон (если другого не сказано, то стили можно комбинировать с помощью оператора Or). Кроме функции CreateWindow, для создания окон можно использовать функцию CreateWindowEx. При этом появится возможность указать дополнительный (расширенный) стиль окна (первый параметр функции CreateWindowEx). Список расширенных стилей также приведен в приложении 2.

В конце листинга 2.6 записан цикл обработки сообщений:


while (Longint(GetMessage(mess, hMainWnd, 0, 0)) > 0)

do begin

TranslateMessage(mess);

DispatchMessage(mess);

end;


Здесь API-функция GetMessage возвращает значения больше нуля, пока в очереди не обнаружится сообщение WM_QUIT. В случае возникновения какой-либо ошибки функция GetMessage возвращает значение -1. Функция TranslateMessage преобразует сообщения типа WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN иWM_ SYSKEYUP в сообщения символьного ввода(WM_CHAR, WM_SYS CHAR, WM_ DEADCHAR, WM_SYSDEADCHAR). Функция DispatchMessage в общем случае (за исключением сообщения WM_TIMER) вызывает функцию обработки сообщений нужного окна.

Внешний вид самого окна, создаваемого в этом примере, показан на рис. 2.2.

Рис. 2.2. Окно, созданное вручную


Кстати, пока размер приложения равен всего 16 Кбайт.

Окно с элементами управления

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

• BUTTON – оконный класс, реализующий работу обычной кнопки, флажка, переключателя и даже рамки для группы элементов управления (GroupBox);

• COMBOBOX – раскрывающийся список;

• EDIT – текстовое поле, может быть как однострочным, так и многострочным, с полосами прокрутки и без;

• LISTBOX – список;

• SCROLLBAR – полоса прокрутки;

• STATIC – статический текст (он же Label, надпись, метка и пр.), кроме текста, может содержать изображение.

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

Создание элементов управления

Целесообразно написать более краткие функции создания элементов управления, чтобы, формируя интерфейс формы «на лету», не приходилось усложнять код громоздкими вызовами функций CreateWindow или CreateWindowEx. Этим мы сейчас и займемся. Сразу необходимо отметить: предполагается, что все функции помещены в модуль (модуль Controls в файле Controls.pas), в котором объявлены глобальные переменные hAppInst и hParentWnd. Эти переменные инициализируются перед вызовом первой из перечисленных ниже процедур или функций создания и работы с элементами управления (инициализацию можно посмотреть в листинге 2.21).


Внимание!

Обратите внимание на параметр id функций создания и манипулирования элементами управления. Это целочисленное значение идентифицирует элементы управления в пределах родительского окна.

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

Листинг 2.7. Создание кнопки

function CreateButton(x, y, width, height, id:Integer;

caption: String):HWND;

begin

CreateButton:=

CreateWindow('BUTTON', PAnsiChar(caption), WS_CHILD or WS_VISIBLE or

BS_PUSHBUTTON or WS_TABSTOP, x, y, width, height,

hParentWnd, HMENU(id), hAppInst, nil);

end;


Приведенная в листинге 2.8 функция создает флажок и устанавливает его.

Листинг 2.8. Создание флажка

function CreateCheck(x, y, width, height, id: Integer; caption: String;

checked: Boolean):HWND;

var

res: HWND;

begin

res:=

CreateWindow('BUTTON', PAnsiChar(caption), WS_CHILD or WS_VISIBLE or

BS_AUTOCHECKBOX or WS_TABSTOP, x, y, width, height,

hParentWnd, HMENU(id), hAppInst, nil);

if ((res <> 0) and checked) then

SendMessage(res, BM_SETCHECK, BST_CHECKED, 0); //Флажок установлен

CreateCheck:= res;

end;


Следующая функция создает переключатель (листинг 2.9). Если нужно, то он устанавливается. Новый переключатель может начинать новую группу переключателей, для чего нужно параметру group присвоить значение True.

Листинг 2.9. Создание переключателя

function CreateOption(x, y, width, height, id: Integer; caption: String;

group: Boolean; checked: Boolean):HWND;

var

res: HWND;

nGroup: Integer;

begin

if (checked) then nGroup:= WS_GROUP else nGroup:= 0;

res:=

CreateWindow('BUTTON', PAnsiChar(caption), WS_CHILD or WS_VISIBLE or

BS_AUTORADIOBUTTON or nGroup or WS_TABSTOP, x, y, width,

height, hParentWnd, HMENU(id), hAppInst, nil);

if ((res <> 0) and checked) then

//Переключатель установлен

SendMessage(res, BM_SETCHECK, BST_CHECKED, 0);

CreateOption:= res;

end;


Для создания подписанной рамки, группирующей элементы управления, можно воспользоваться функцией CreateFrame, приведенной в листинге 2.10.

Листинг 2.10. Создание рамки

function CreateFrame(x, y, width, height, id: Integer;

caption: String):HWND;

begin

CreateFrame:=

CreateWindow('BUTTON', PAnsiChar(caption), WS_CHILD or WS_VISIBLE or

BS_GROUPBOX, x, y, width, height, hParentWnd,

HMENU(id), hAppInst, nil);

end;


Для создания раскрывающегося списка (ComboBox) пригодится функция Create-Combo, приведенная в листинге 2.11.

Листинг 2.11. Создание раскрывающегося списка

function CreateCombo(x, y, width, height, id: Integer):HWND;

begin

CreateCombo:=

CreateWindow('COMBOBOX', nil, WS_CHILD or WS_VISIBLE or CBS_DROPDOWN

or CBS_AUTOHSCROLL or WS_TABSTOP, x, y, width, height,

hParentWnd, HMENU(id), hAppInst, nil);

end;


Для создания простого списка (ListBox) вполне подойдет функция CreateList, описанная в листинге 2.12.

Листинг 2.12. Создание простого списка

function CreateList(x, y, width, height, id: Integer):HWND;

begin

CreateList:=

CreateWindowEx(WS_EX_CLIENTEDGE, 'LISTBOX', nil, WS_CHILD or WS_VISIBLE

or LBS_NOTIFY or WS_BORDER or WS_TABSTOP, x, y, width,

height, hParentWnd, HMENU(id), hAppInst, nil);

end;


Функция CreateLabel, приведенная в листинге 2.13, создает статическую надпись (Label), предназначенную только для вывода текста.

Листинг 2.13. Создание надписи

function CreateLabel(x, y, width, height, id: Integer;

caption: String):HWND;

begin

CreateLabel:=

CreateWindow('STATIC', PAnsiChar(caption), WS_CHILD or WS_VISIBLE, x,

y, width, height, hParentWnd, HMENU(id), hAppInst, nil);

end;


Однострочное текстовое поле с привычной рамкой создается функцией CreateEdit (листинг 2.14).

Листинг 2.14. Создание однострочного текстового поля

function CreateEdit(x, y, width, height, id: Integer; strInitText: String):HWND;

begin

CreateEdit:=

CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT', PAnsiChar(strInitText),

WS_CHILD or WS_VISIBLE or ES_AUTOHSCROLL or WS_TABSTOP,

x, y, width, height, hParentWnd,

HMENU(id), hAppInst, nil);

end;


Код создания многострочного текстового поля (аналог Memo) отличается от кода создания однострочного поля только указанием дополнительного флага ES_MULTILINE (листинг 2.15).

Листинг 2.15. Создание многострочного текстового поля

function CreateMemo(x, y, width, height, id: Integer; strInitText: String):HWND;

begin

CreateMemo:=

CreateWindowEx(WS_EX_CLIENTEDGE, 'EDIT', PAnsiChar(strInitText),

WS_CHILD or WS_VISIBLE or ES_AUTOVSCROLL or ES_MULTILINE

or WS_TABSTOP, x, y, width, height, hParentWnd,

HMENU(id), hAppInst, nil);

end;


Приведенные здесь функции не претендуют на абсолютную универсальность и гибкость. Они введены для того, чтобы упростить создание элементов управления в тех частных случаях, которые будут приведены далее в примерах этой главы.

Использование элементов управления

Элементы управления, как и все окна, управляются путем отсылки им сообщений. Этим же способом они уведомляют родительские окна о некоторых произошедших событиях (например, выделении элемента в списке, нажатии кнопки и т. д.).

Описание наиболее используемых сообщений для рассматриваемых элементов управления приведено в приложении 3. Сейчас же будет показано, как можно упростить работу с элементами управления в некоторых частных случаях с помощью специальных функций.

Итак, в демонстрационном проекте для управления переключателями и флажками предусмотрены следующие функции и процедуры (листинг 2.16).

Листинг 2.16. Управление флажками и переключателями

//Установка/снятие флажка (установка/снятие переключателя)

procedure SetChecked(id: Integer; checked: BOOL);

var state: Integer;

begin

if (checked) then state:= BST_CHECKED

else state:= BST_UNCHECKED;

SendDlgItemMessage(hParentWnd, id, BM_SETCHECK, state, 0);

end;

//Получение информации о том, установлен ли флажок

//(установлен ли переключатель)

function GetChecked(id: Integer):BOOL;

begin

if (SendDlgItemMessage(hParentWnd, id, BM_GETCHECK, 0, 0) = BST_CHECKED)

then GetChecked:= True

else GetChecked:= False;

end;


Функции и процедуры, описанные в листинге 2.17, предназначены для управления раскрывающимся списком (элементом ComboBox).

Листинг 2.17. Управление раскрывающимся списком

//Добавление строки в список

procedure AddToCombo(id: Integer; str: String);

begin

SendDlgItemMessage(hParentWnd, id, CB_ADDSTRING, 0,

Integer(PAnsiChar(str)));

end;

//Удаление строки из списка

procedure DeleteFromCombo(id: Integer; index: Integer);

begin

SendDlgItemMessage(hParentWnd, id, CB_DELETESTRING, index, 0);

end;

//Выделение строки с заданным номером

procedure SetComboSel(id: Integer; index: Integer);

begin

SendDlgItemMessage(hParentWnd, id, CB_SETCURSEL, index, 0);

end;

//Получение номера выделенной строки (CB_ERR, если нет выделения)

function GetComboSel(id: Integer): Integer;

begin

GetComboSel:= SendDlgItemMessage(hParentWnd, id, CB_GETCURSEL, 0, 0);

end;

//Получение количества строк

function GetComboCount(id: Integer): Integer;

begin

GetComboCount:= SendDlgItemMessage(hParentWnd, id, CB_GETCOUNT, 0, 0);

end;

//Получение текста строки по ее индексу

function GetComboItemText(id: Integer; index: Integer):String;

var buffer: String;

begin

SetLength(buffer,

SendDlgItemMessage(hParentWnd, id, CB_GETLBTEXTLEN, index, 0)

);

SendDlgItemMessage(hParentWnd, id, CB_GETLBTEXT, index,

Integer(Addr(buffer)));

GetComboItemText:= buffer;

end;


Сходные функции и процедуры, приведенные в листинге 2.18, предназначены для управления списком (элементом ListBox).

Листинг 2.18. Управление списком

//Добавление строки в список

procedure AddToList(id: Integer; str: String);

begin

SendDlgItemMessage(hParentWnd, id, LB_ADDSTRING, 0, Integer(PAnsiChar(str)));

end;

//Удаление строки из списка

procedure DeleteFromList(id: Integer; index: Integer);

begin

SendDlgItemMessage(hParentWnd, id, LB_DELETESTRING, index, 0);

end;

//Выделение строки с заданным номером

procedure SetListSel(id: Integer; index: Integer);

begin

SendDlgItemMessage(hParentWnd, id, LB_SETCURSEL, index, 0);

end;

//Получение номера выделенной строки (LB_ERR, если нет выделения)

function GetListSel(id: Integer): Integer;

begin

GetListSel:= SendDlgItemMessage(hParentWnd, id, LB_GETCURSEL, 0, 0);

end;

//Получение количества строк

function GetListCount(id: Integer): Integer;

begin

GetListCount:= SendDlgItemMessage(hParentWnd, id, LB_GETCOUNT, 0, 0);

end;

//Получение текста строки по ее индексу

function GetListItemText(id: Integer; index: Integer):String;

var buffer: String;

begin

SetLength(buffer,

SendDlgItemMessage(hParentWnd, id, LB_GETTEXTLEN, index, 0)

);

SendDlgItemMessage(hParentWnd, id, LB_GETTEXT, index,

Integer(Addr(buffer)));

GetListItemText:= buffer;

end;


Функции и процедуры, приведенные в листинге 2.19, дают возможность управлять текстовыми полями (элементы Edit и Memo).

Листинг 2.19. Управление текстовыми полями

//Получение позиции первого выделенного символа (нумерация с нуля)

function GetSelStart(id: Integer): Integer;

var selStart, selEnd: Integer;

begin

SendDlgItemMessage(hParentWnd, id, EM_GETSEL, Integer(Addr(selStart)),

Integer(Addr(selEnd)));

GetSelStart:= selStart;

end;

//Получение длины выделенного фрагмента текста

function GetSelLength(id: Integer): Integer;

var selStart, selEnd: Integer;

begin

SendDlgItemMessage(hParentWnd, id, EM_GETSEL, Integer(Addr(selStart)),

Integer(Addr(selEnd)));

GetSelLength:= selEnd – selStart;

end;

//Выделение фрагмента текста (позиция первого символа с нуля)

procedure SetSel(id: Integer; start, length: Integer);

begin

SendDlgItemMessage(hParentWnd, id, EM_SETSEL, start, start + length);

end;

//Получение выделенного фрагмента текста

function GetSelText(id: Integer): String;

var allText: String;

begin

allText:= GetText(id);

GetSelText:= Copy(allText, GetSelStart(id)+1,GetSelLength(id));

end;

//Замена выделенного текста

procedure ReplaceSelText(id: Integer; newText: String);

begin

SendDlgItemMessage(hParentWnd, id, EM_REPLACESEL,

0, Integer(PAnsiChar(newText)));

end;


В листинге 2.20 приведены функции и процедуры, которые можно применять ко всем элементам управления с одинаковым успехом.

Листинг 2.20. Общие функции и процедуры

//Установка текста окна

procedure SetText(id: Integer; str: String);

begin

SetWindowText(GetDlgItem(hParentWnd, id), PAnsiChar(str));

end;

//Получение текста окна

function GetText(id: Integer): String;

var buffer: String;

begin

SetLength(buffer, GetWindowTextLength(hParentWnd));

GetWindowText(hParentWnd, PAnsiChar(buffer), Length(buffer));

GetText:= buffer;

end;

//Активизация/деактивизация окна

procedure SetEnabled(id: Integer; fEnabled: BOOL);

begin

EnableWindow(GetDlgItem(hParentWnd, id), fEnabled);

end;

Реакция на сообщения элементов управления

При возникновении какого-либо предусмотренного для элемента управления события родительскому окну посылается сообщение WM_COMMAND.

Примечание

Сообщение WM_COMMAND приходит также при перерисовке так называемых «самоперерисовывающихся» (Owner Draw) элементов управления. Однако ввиду специфики данного вопроса и ограниченности объема главы оно рассматриваться не будет.

Таким образом, когда родительское окно получает сообщение WM_COMMAND, то из двух прилагающихся параметров (lParam и wParam) можно извлечь следующие сведения:

• старшие 16 бит wParam представляют собой целочисленный код уведомления, позволяющий определить, что именно произошло с элементом управления;

• младшие 16 бит wParam представляют собой идентификатор элемента управления, состояние которого изменилось (именно этот идентификатор передавался вместо дескриптора меню при создании элементов управления);

• lParam содержит дескриптор (HWND) окна элемента управления, состояние которого изменилось.

Для выделения старших 16 бит из 32-битного значения можно использовать функцию HiWord. Для получения младших 16 бит можно использовать функцию с именем LoWord. Обе функции объявлены в модуле Windows.

В качестве примеров можно привести следующие коды уведомлений:

• BN_CLICKED – нажата кнопка;

• EN CHANGE – изменен текст в текстовом поле;

• LBN_SELCHANGE – изменилось выделение в списке;

• CBN_SELCHANGE – изменилось выделение в раскрывающемся списке.

Эти и все остальные константы уведомлений стандартных элементов управления объявлены в модуле Messages.

Примечание

Коды и описания уведомлений рассматриваемых в этой главе элементов управления приведены в приложении 3.

Пример приложения

Рассмотрим небольшой пример, иллюстрирующий принцип работы с элементами управления, помещенными на форму описанным ранее способом. Проект этого приложения называется ControlsDemo.

Не буду заострять внимание на регистрации класса главного окна приложения, так как она аналогична приведенной в листинге 2.4. Рассмотрим создание окна с элементами управления в нем (листинг 2.21).

Листинг 2.21. Создание главного окна приложения (с элементами управления)

program ControlsDemo;

uses

Windows, Messages,

Controls in 'Controls.pas';

{$R *.res}

var

hMainWnd: HWND;

hInst: Cardinal;

mess: MSG;

//Функция обработки сообщений

...

//Создание окна и цикл обработки сообщений

begin

hInst:= GetModuleHandle(nil);

//Регистрация и создание главного окна

if not RegisterWindow() then Exit;

hMainWnd:= CreateWindow(

'MyWindowClass', //Имя класса окна

'Главное окно', //Заголовок окна

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, //Координата X по умолчанию

CW_USEDEFAULT, //Координата Y по умолчанию

CW_USEDEFAULT, //Ширина по умолчанию

CW_USEDEFAULT, //Высота по умолчанию

HWND(nil), //Нет родит ельского окна

HMENU(nil), //Нетменю

hInst,

nil);

if (hMainWnd = HWND(nil)) then Exit;

//Инициализация модуля Controls для работы с главным окном

Controls.hParentWnd:= hMainWnd;

Controls.hAppInst:= hInst;

//Создание элементов управления

CreateFrame(10, 80, 170, 70, –1, 'Кнопки');

CreateButton(2 0, 100, 70, 30, 1001, 'Кнопка 1');

CreateButton(100, 100, 70, 30, 1002,'Кнопка 2');

CreateFrame(2 00, 10, 200, 180, –1, 'Флажки и переключатели');

CreateCheck(2 10, 30, 180, 20, 2001, 'Флажок 1');

CreateCheck(2 10, 60, 180, 20, 2002, 'Флажок 2', True);

CreateOption(2 10, 100, 180, 20, 3001, 'Переключатель 1', True);

CreateOption(2 10, 130, 180, 20, 3002, 'Переключатель 2', False, True);

CreateOption(2 10, 160, 180, 20, 3003, 'Переключатель 3', True);

CreateFrame(420, 10, 300, 180, –1, 'Списки и статические надписи');

CreateLabel(430, 30, 70, 20, -1, 'Надпись');

CreateCombo(510, 30, 200, 100, 4001);

CreateList(430, 60, 280, 120, 5001);

CreateFrame(2 00, 200, 200, 240, –1, 'Текстовые поля');

CreateEdit(2 10, 22 0, 180, 20, 6001, 'Текст в текстовом поле');

CreateMemo(2 10, 25 0, 180, 180, 6002, 'Текст в многострочном'+ #13

+ #10 + 'текстовом поле');

//Добавление строк в списки

AddToCombo(4001, 'Строка 1');

AddToCombo(4001, 'Строка 2');

AddToCombo(4001, 'Строка 3');

AddToList(5 001, 'Строка 1');

AddToList(5 001, 'Строка 2');

AddToList(5 001, 'Строка 3');

ShowWindow(hMainWnd, SW_NORMAL);

//Запуск цикла обработки сообщений

while (Longint(GetMessage(mess, 0, 0, 0)) <> 0)

do begin

TranslateMessage(mess);

DispatchMessage(mess);

end;

end.


Код листинга 2.21 заодно демонстрирует использование некоторых из приведенных ранее функций работы с элементами управления. Выглядит созданное окно так, как показано на рис. 2.3.

Рис. 2.3. Окно с элементами управления


Принцип построения функции обработки сообщений для этого окна описан в листинге 2.22.

Листинг 2.22. Функция обработки сообщений

//Функция обработки сообщений

function WindowFunc(hWnd:HWND; msg:UINT;

wParam:WPARAM; lParam:LPARAM):LRESULT; stdcall;

var

ps: PAINTSTRUCT;

begin

case msg of

WM_PAINT:

begin

//Перерисовка содержимого окна

BeginPaint (hWnd, ps);

Text Out (ps.hdc, 10, 10, 'Текст в окне', 12);

EndPaint(hWnd, ps);

end;

WM_CLOSE:

if (hWnd = hMainWnd) then

PostQuit Message(0); //При закрытии этого окна завершается

//приложение

WM_COMMAND:

begin

case LOWORD(wParam) of

//нажата "Кнопка 1"

1001: if HIWORD(wParam) = BN_CLICKED then;

//нажата "Кнопка 2"

1002: if HIWORD(wParam) = BN_CLICKED then;

//установлен "Флажок 1"

2001: if HIWORD(wParam) = BN_CLICKED then;

//установлен "Флажок 2"

2002: if HIWORD(wParam) = BN_CLICKED then;

//установлен "Переключатель 1"

3001: if HIWORD(wParam) = BN_CLICKED then;

//установлен "Переключатель 2"

3002: if HIWORD(wParam) = BN_CLICKED then;

//установлен "Переключатель 3"

3003: if HIWORD(wParam) = BN_CLICKED then;

//выделение в ComboBox

4001: if HIWORD(wParam) = CBN_SELCHANGE then;

//выделение в ListBox

5001: if HIWORD(wParam) = LBN_SELCHANGE then;

//изменен текст в Edit

6001: if HIWORD(wParam) = EN_CHANGE then;

//изменен текст в Memo

6002: if HIWORD(wParam) = EN_CHANGE then;

end;

end;

else

begin

//Обработка по умолчанию

WindowFunc:= DefWindowProc(hWnd, msg, wParam, lParam);

Exit;

end;

end;

WindowFunc:= S_OK; //Сообщение обработано

end;


Приведенная в листинге 2.22 функция не претендует на то, чтобы быть эталоном в порядке классификации сообщений от элементов управления. Иногда бывает полезно сразу классифицировать сообщения не по элементам управления, которые их прислали, а по типу самих сообщений. К тому же в ряде случаев можно предусмотреть один обработчик сообщений сразу для нескольких элементов управления, например, для группы переключателей. В таком случае полезным окажется параметр lParam сообщения WM COMMAND.

Кстати, размер исполняемого файла этого приложения равен всего 19 Кбайт.

Стандартные окна Windows

Теперь рассмотрим, как можно с помощью только функций Windows API вызывать некоторые распространенные окна. Чтобы использовать API-функции и структуры с информацией для этих окон, необходимо подключить следующие модули:

• CommDlg – для окон открытия и сохранения файла, выбора цвета и шрифта, поиска и замены текста;

• ShlObj и ActiveX – для окна выбора папки (второй модуль нужен для доступа к интерфейсу IMalloc, зачем – будет рассказано далее);

• Windows – помимо объявления основных структур и API-функций, этот модуль содержит объявления функций для работы с окнами подключения и отключения от сетевого ресурса (сетевого диска);

• ShellAPI – для системного окна О программе.

Вариант использования рассматриваемых в этом разделе окон приведен в подразделе «Демонстрационное приложение» данной главы (стр. 81).

Примечание

В приведенных далее примерах вызова окон можно увидеть не объявленные, но используемые в программах переменные hAppInst и hParentWnd. Подразумевается, что это глобальные переменные, которые инициализируются вне процедур и функций, приведенных в примерах. Для инициализации этих переменных можно также написать специальную процедуру, например, с именем Init, в которую и передавать значения для hParentWnd и hAppInst.

Окно открытия и сохранения файла

Чтобы использовать возможности окна открытия файла, достаточно воспользоваться функцией, представленной в листинге 2.23.

Листинг 2.23. Окно открытия файла

function ShowOpen(strFilter: string; nFilterIndex: Integer= 0;

strInitFileName: string = '';

var

ofn: OPENFILENAME;

begin

ZeroMemory(Addr(ofn), SizeOf(ofn));

//Формирование буфера (260 символов)

SetLength(strInitFileName, MAX_PATH);

PrepareFilterString(strFilter);

//Заполнение структуры для окна

ofn.lStructSize:= SizeOf(ofn);

ofn.hWndOwner:= hParentWnd;

ofn.hInstance:= hAppInst;

ofn.lpstrFilter:= PAnsiChar(strFilter);

ofn.nFilterIndex:= nFilterIndex;

ofn.lpstrFile:= PAnsiChar(strInitFileName);

ofn.nMaxFile:= MAX_PATH;

ofn.lpstrTitle:= pAnsiChar(strTitle);

ofn.Flags:= OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_HIDEREADONLY;

//Отображение окна и обработка результата

if(GetOpenFileName(ofn) = True) then

ShowOpen:= ofn.lpstrFile;

end;


Приведенная в листинге 2.23 функция возвращает непустую строку – полный путь файла в случае, если пользователь выбрал или ввел имя файла. Здесь главной трудностью является заполнение довольно большой структуры OPENFILENAME. В данном примере используются только базовые возможности окна открытия файла и лишь некоторые из поддерживаемых им флагов (поле Flags):

• OFN_FILEMUSTEXIST – при этом установленном флаге, если окно успешно завершило свою работу, можно быть уверенным, что результирующий путь является путем существующего файла;

• OFN_PATHMUSTEXIST – данный флаг не дает ввести имя файла в несуществующей папке (например, при вводе c: \docs\mydoc1.doc, если папки docs не существует, будет выдано соответствующее сообщение);

• OFN_HIDEREADONLY – позволяет скрыть флажок Только для чтения.

Теперь отдельно рассмотрим, зачем в приведенном примере вызывается дополнительная функция PrepareFilterString (листинг 2.24).

Листинг 2.24. Преобразование строки фильтра

procedure PrepareFilterString(var strFilter: string);

var

i: Integer;

begin

for i:= 1 to length(strFilter) do

if (strFilter[i] = '|') then strFilter[i]:= #0;

end;


Дело в том, что при задании фильтров (поле lpstrFile) требуется, чтобы каждое название и обозначение фильтров были отделены символом #0, а за последним фильтром шла последовательность из двух нулевых символов. На практике задавать строку из нескольких фильтров в следующем виде недостаточно удобно:


'Текстовые файлы'+ #0 + '*.txt'+ #0 + 'Все файлы'+ '*.*'+ #0 + #0


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


'Текстовые файлы|*.txt |Все файлы|*.*||'


Согласитесь, что получилось более кратко и понятно.

Теперь обратимся к окну сохранения файла. Для его вызова достаточно переделать код из листинга 2.23 следующим образом (листинг 2.25).

Листинг 2.25. Окно сохранения файла

function ShowSave(strFilter: string; nFilterIndex: Integer = 0;

strInitFileName: string = '';

strTitle: string = 'Сохранение файла'):string;

var

ofn: OPENFILENAME;

begin

ZeroMemory(Addr(ofn), SizeOf(ofn));

//Формирование буфера (260 символов)

SetLength(strInitFileName, MAX_PATH);

PrepareFilterString(strFilter);

//Заполнение структуры для окна

ofn.lStructSize:= SizeOf(ofn);

ofn.hWndOwner:= hParentWnd;

ofn.hInstance:= hAppInst;

ofn.lpstrFilter:= PAnsiChar(strFilter);

ofn.nFilterIndex:= nFilterIndex;

ofn.lpstrFile:= PAnsiChar(strInitFileName);

ofn.nMaxFile:= MAX_PATH;

ofn.lpstrTitle:= pAnsiChar(strTitle);

ofn.Flags:= OFN_PATHMUSTEXIST or OFN_OVERWRITEPROMPT;

//Отображение окна и обработка результата

if (GetSaveFileName(ofn) = True) then

ShowSave:= ofn.lpstrFile;

end;


Здесь дополнительно к упомянутому ранее флагу OFN_PATHMUSTEXIST добавлен флаг OFN_OVE RWRITEPROMPT для того, чтобы при указании имени уже существующего файла был задан вопрос, желает ли пользователь заменить этот файл.

Окно для выбора цвета

Код вызова следующего окна – окна для выбора цвета – приведен в листинге 2.26.

Листинг 2.26. Окно для выбора цвета

function ShowChooseColor(lastColor: COLORREF = 0):COLORREF;

var

choose: TChooseColor;

begin

ZeroMemory(Addr(choose), SizeOf(choose));

//Заполнение структуры для окна

choose.lStructSize:= SizeOf(choose);

choose.hWndOwner:= hParentWnd;

choose.hInstance:= hAppInst;

choose.rgbResult:= lastColor;

choose.lpCustColors:= Addr(colors);

choose.Flags:= CC_RGBINIT or CC_ANYCOLOR or CC_FULLOPEN;

//Отображение окна и обработка результата

if (ChooseColor(choose) = True) then ShowChooseColor:= choose.rgbResult

else ShowChooseColor:= lastColor;

end;


Здесь также заполняется специальная структура. Для этого используются следующие флаги:

• CC_RGBINIT – использовать значение поля rgbResult в качестве предустановленного значения цвета (по умолчанию как ранее выбранного);

• CC_ANYCOLOR – отображать все доступные предопределенные цвета (левая половина, рис. 2.4);

Рис. 2.4. Окно для выбора цвета


• CC_FULLOPEN – раскрывать панель подбора цвета (правая половина окна, рис. 2.4).

Стоит пояснить, что за переменная, а точнее, адрес, сохраняется в поле lpCustColors – это массив из 16 значений типа COLORREF:


colors: array [1..16] of COLORREF;


Обратите внимание на 16 квадратов в левой нижней области окна (рис. 2.4) – это места для определенных пользователем цветов. Для заполнения этой области окна и используются значения из массива colors. Массив может быть как локальным, так и глобальным (что иногда удобнее, так как значения определенных пользователем цветов в этом случае сохраняются между вызовами окна выбора цвета).

Окно для выбора шрифта

Для вывода окна для выбора шрифта подойдет функция, приведенная в листинге 2.27.

Листинг 2.27. Окно для выбора шрифта

function ShowChooseFont(var font: LOGFONT):BOOL;

var

choose: TChooseFont;

begin

ZeroMemory(Addr(choose), SizeOf(choose));

//Заполнение структуры для окна

choose.lStructSize:= SizeOf(choose);

choose.hWndOwner:= hParentWnd;

choose.hInstance:= hAppInst;

choose.lpLogFont:= Addr(font);

choose.Flags:= CF_BOTH or CF_INITTOLOGFONTSTRUCT;

//Отображение окна и обработка результата

if (ChooseFont (choose) = True) then

begin

CopyMemory(Addr(font), choose.lpLogFont, SizeOf(font));

ShowChooseFont:= True;

end

else ShowChooseFont:= False;

end;


Здесь используются флаги окна, имеющие следующие значения:

• CF_BOTH – позволяет отображать экранные и принтерные шрифты (для отображения либо экранных, либо принтерных шрифтов можно использовать флаги CF_SCREENFONTS и CF_PRINTERFONTS соответственно);

• CF_INITTOLOGFONTSTRUCT – позволяют выбрать в окне шрифт, соответствующий (или максимально похожий) шрифту, описываемому структурой LOGFONT, указатель на которую сохраняется в поле lpLogFont.

Окно для выбора папки

Чтобы иметь возможность пользоваться окном Обзор папок, можно использовать функцию, представленную в листинге 2.28.

Листинг 2.28. Окно для выбора папки

function ShowChooseFolder(strTitle: string):string;

var

choose: BROWSEINFO;

buffer: string;

pidl: PItemIDList;

begin

ZeroMemory(Addr(choose), SizeOf(choose));

SetLength(buffer, MAX_PATH);

//Заполнение структуры для окна

choose.hwndOwner:= hParentWnd;

choose.pi dlRoot:= nil; //Корень – папка Рабочего стола

choose.pszDisplayName:= PAnsiChar(buffer);

choose.lpszTitle:= PAnsiChar(strTitle);

choose.ulFlags:= 0;

//Вывод окна и обработка результата

pidl:= SHBrowseForFolder(choose);

if (pidl <> nil) then

begin

//Получение полного пути выбранной папки

SHGetPathFromIDList(pidl, PAnsiChar(buffer));

ShowChooseFolder:= buffer;

DeletePIDL(pidl);

end

else

ShowChooseFolder:= '';

end;


Представленная в листинге 2.28 функция ShowChooseFolder возвращает полный путь указанной папки, если она выбрана, и пустую строку в противном случае. Само окно Обзор папок показано на рис. 2.5.

Рис. 2.5. Окно для выбора папки


Особенностью использованной в данном примере функции SHBrowseForFolder является то, что она возвращает не путь выбранной папки, а указатель на структуру ItemlDList (что-то вроде внутреннего представления путей). Для извлечения построения пути по содержимому этой структуры используется функция SHGetPathFromIDList. После этого структура становится больше не нужна, и ее следует удалить (с использованием специального интерфейса IMalloc). Для этого используется процедура DeletePIDL, реализованная в листинге 2.29.

Листинг 2.29. Удаление структуры ItemlDList

procedure DeletePIDL(pidl: PItemIDList);

var

pMalloc: IMalloc;

begin

SHGetMalloc(pMalloc);

if (pMalloc <> nil) then

begin

pMalloc.Free(pidl);

pMalloc._Release();

end;

end;


Функцию SHBrowseForFolder (листинг 2.28) можно использовать и для указания принтеров или компьютеров. Для этого достаточно установить флаги BIF_ BROWSEFORCOMPUTER и BIF_BROWSEFORPRINTER соответственно:


choose.ulFlags:= BIF_BROWSEFORCOMPUTER;


и


choose.ulFlags:= BIF_BROWSEFORPRINTER;


Чтобы в окне отображались еще и значки файлов, нужно установить флаг BIF_ BROWSEINCLUDEFILES.

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

Часто бывает удобно осуществлять доступ к сетевым папкам так же, как и к локальным дискам компьютера (с использованием того же принципа построения пути). Окна подключения и отключения сетевого ресурса дают пользователю возможность выбрать, какие папки считать сетевыми дисками и какие сетевые диски можно отключить.

Вид окна подключения сетевого ресурса в Windows XP показан на рис. 2.6.

Рис. 2.6. Окно подключения сетевого диска


Для вызова окна подключения сетевого ресурса можно использовать функцию, приведенную в листинге 2.30.

Листинг 2.30. Окно подключения сетевого ресурса

function ShowConnection(): BOOL;

begin

ShowConnection:=

WNetConnectionDialog(hParentWnd, RESOURCETYPE_DISK) = NO_ERROR;

end;


Функция ShowConnection возвращает значение True в случае удачного подключения и False – в противном случае.

Окно отключения сетевого диска показано на рис. 2.7.

Рис. 2.7. Отключение сетевого ресурса


Функция, показывающая окно отключения сетевого диска, приведена в листинге 2.31.

Листинг 2.31. Окно отключения сетевого ресурса

function ShowDisconnect(): BOOL;

begin

ShowDisconnect:=

WNetDisconnectDialog(hParentWnd, RESOURCETYPE_DISK) = NO_ERROR;

end;


Аналогично ShowConnection функция ShowDisconnect возвращает значение True, если отсоединен хотя бы один диск, и False – в противном случае.

Системное окно О программе

Этот последний и довольно специфический пример приведен на тот случай, если возникнет желание или необходимость использовать окно О программе, которое выводится для самой операционной системы Windows и ее компонентов. Процедура, выводящая это окно, приведена в листинге 2.32.

Листинг 2.32. Окно О программе

procedure ShowAbout(strAppName: string; strInfo: string);

begin

ShellAbout(hParentWnd, PAnsiChar(strAppName), PAnsiChar(strInfo),

LoadIcon(0, IDI_ASTERISK));

end;


Правда, в окне О программе Windows XP на информацию о приложении отведено всего две строки (и место для значка). Все остальное место занимают информация о регистрации операционной системы и фирменная эмблема Microsoft Windows XP.

Демонстрационное приложение

Теперь пришла очередь рассмотреть небольшое приложение, использующее описанные выше окна (проект StandartWindows). Окно этого приложения показано на рис. 2.8.

Рис. 2.8. Окно демонстрационного приложения


Размер EXE-файла приложения равен 22 Кбайт.

В листинге 2.33 приведены объявления используемых глобальных переменных, код, реализующий создание окна и элементов управления в нем, а также цикл обработки сообщений (файл StandartWindows.dpr). Функции работы с рассмотренными выше окнами вынесены в отдельный модуль StdWindows (файл StdWindows.pas).

В листингах 2.33-2.34 используются уже знакомые вам функции из модуля Controls.

Листинг 2.33. Глобальные переменные, код создания окна и цикл обработки сообщений

program StandartWindows;

{$R *.res}

uses

Windows, Messages, CommDlg,

Controls in 'Controls.pas',

StdWindows in 'StdWindows.pas';

var

hMainWnd: HWND;

hInst: Cardinal;

mess: MSG;

curColor: COLORREF;

font: LOGFONT;

hCurFont: HFONT;

...

function RegisterWindow():Boolean;

...

begin

hInst:= GetModuleHandle(nil);

//Регистрация и создание главного окна

if not RegisterWindow() then Exit;

hMainWnd:= CreateWindow(

'MyWindowClass', //Имя класса окна

'Стандартные окна Windows', //Заголовок окна

WS_CAPTION or WS_SYSMENU or WS_CLIPCHILDREN or WS_CLIPSIBLINGS,

CW_USEDEFAULT, //Координата X по умолчанию

CW_USEDEFAULT, //Координата Y по умолчанию

470, 420,

HWND(nil), //Нет родительского окна

HMENU(nil), //Нетменю

hInst,

nil);

if (hMainWnd = HWND(nil)) then Exit;

//Инициализация модуля Controls для работы с главным окном приложения

Controls.hParentWnd:= hMainWnd;

Controls.hAppInst:= hInst;

//Инициализация модуля StdWindows для работы с главным окном приложения

StdWindows.hParentWnd:= hMainWnd;

StdWindows.hAppInst:= hInst;

//Создание кнопок для открытия окон

CreateButton(2 0, 20, 200, 30, 1001, 'Открытие файла');

CreateButton(2 0, 60, 200, 30, 1002, 'Сохранение файла');

CreateButton(2 0, 100, 200, 30, 1003, 'Выбор цвета');

CreateButton(2 0, 140, 200, 30, 1004, 'Выбора шрифта');

CreateButton(2 0, 180, 200, 30, 1005, 'Окно поиска текста');

CreateButton(2 0, 22 0, 200, 30, 1006, 'Окно поиска и замены');

CreateButton(23 0, 20, 22 0, 30, 1010, 'Выбор папки');

CreateButton(23 0, 60, 22 0, 30, 1011, 'Подключение сетевого ресурса');

CreateButton(23 0, 100, 22 0, 30, 1012, 'Отключение сетевого ресурса');

CreateButton(23 0, 140, 22 0, 30, 1013, 'Системное окно "О программе"');

//Текстовое поле для результата

CreateMemo (20, 270, 430, 100, 2001);

ShowWindow(hMainWnd, SW_NORMAL);

//Запуск цикла обработки сообщений

while (Longint(GetMessage(mess, 0, 0, 0)) <> 0) do

begin

if (IsDialogMessage(hMainWnd, mess) = False) then

begin

TranslateMessage(mess);

DispatchMessage(mess);

end;

end;

end.


Код функции RegisterWindow опущен, поскольку он аналогичен приведенному в листинге 2.4. Функции работы с рассмотренными ранее окнами вынесены в модуль StdWindows(файл StdWindows.pas).

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

Перед функцией RegisterWindow (на месте многоточия перед ее объявлением в листинге 2.33) находится функция обработки сообщений, имеющая следующий вид (листинг 2.34).

Листинг 2.34. Функция обработки сообщений

function WindowFunc(hWnd:HWND; msg:UINT; wParam:WPARAM; lParam:LPARAM):LRESULT; stdcall;

var

hOldFont: HFONT;

strBuf: String;

hEditDC: HDC;

begin

case msg of

WM_CLOSE:

if (hWnd = hMainWnd) then PostQuitMessage(0);

WM_CTLCOLOREDIT: //Сообщения от Edit перед перерисовкой

begin

//Зададим тексту Edit выбранный цвет

hEditDC:= HDC(wParam);

SetTextColor(hEditDC, curColor);

GetCurrentObject(hEditDC, OBJ_BRUSH);

end;

WM_COMMAND:

if (HIWORD(wParam) = BN_CLICKED) then

begin

//Определим, какая кнопка нажата

case LOWORD(wParam) of

1001: //Открытие файла

begin

SetText(2 001, 'Открыт файл:'+ #13 + #10 +

ShowOpen('Все файлы|*.*||'));

end;

1002: //Сохранение файла

begin

SetText(2001, 'Путь для сохранения:'+ #13 + #10 +

ShowSave('Все файлы|*.*||'));

end;

1003: //Выбор цвета

begin

curColor:= ShowChooseColor(curColor);

Str(curColor, strBuf);

SetText(2001, 'Выбранный цвет:'+ #13 + #10 + strBuf);

end;

1004: //Выбор шрифта

begin

if (ShowChooseFont(font) = True) then

begin

//Замена шрифта в Edit

hOldFont:= HFONT(

SendDlgItemMessage(hMainWnd,2001,WM_GETFONT, 0,0));

hCurFont:= CreateFontIndirect(font);

SendDlgItemMessage(hMainWnd, 2001, WM_SETFONT,

Integer(hCurFont), Integer(True));

SetText(2001, 'Текст, записанный выбранным шрифтом');

if (hOldFont <> 0) then DeleteObject(hOldFont);

end;

end;

1010: //Выбор папки

begin

SetText(2 001, 'Выбранная папка:'+ #13 + #10 +

ShowChooseFolder());

end;

1011: //Подключение сетевого ресурса

begin

ShowConnection();

end;

1012: //Отключение сетевого ресурса

begin

ShowDisconnect();

end;

1013: //Окно "О программе"

begin

ShowAbout('Standart windows',

'Демонстрация использования стандартных '+

'окон из чистого API-приложения');

end;

end;

end;

else

begin

//Обработка по умолчанию

WindowFunc:= DefWindowProc(hWnd, msg, wParam, lParam);

Exit;

end;

end;

WindowFunc:= S_OK; //Сообщение обработано

end;


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


//Замена шрифта в Edit

hOldFont:= HFONT(SendDlgItemMessage(hMainWnd,2001,WM_GETFONT, 0,0));

hCurFont:= CreateFontIndirect(font);

SendDlgItemMessage(hMainWnd, 2001, WM_SETFONT,

Integer(hCurFont), Integer(True));

SetEdit Text(2001, 'Текст, записанный выбранным шрифтом');

if (hOldFont <> 0) then DeleteObject(hOldFont);


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

Способ, которым можно установить шрифт всех элементов управления окна, рассмотрен ниже. Остался еще один существенный момент: не забывайте удалять объекты GDI (в данном случае – шрифт) после того, как они стали не нужны. Дело в том, что приложение может содержать не более 65 ООО объектов GDI, и при наличии так называемой «утечки» ресурсов GDI может произойти аварийное завершение приложения из-за исчерпания лимита ресурсов GDI.

Установка шрифта элементов управления

Существует множество способов установки шрифта текста, отображаемого в элементах управления. Можно, например, при создании элемента управления посылать ему сообщение WM_SETFONT, передавая дескриптор (HFONT) созданного ранее объекта шрифта. В таком случае код создания и установки шрифта элементов управления (с использованием рассмотренных в этой главе функций) может выглядеть, как показано в листинге 2.35.

Листинг 2.35. Установка шрифта во время создания элементов управления

//Шрифт для элементов управления

font:= CreateFont(16, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,

OUT_CHARACTER_PRECIS, CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY, DEFAULT_PITCH, 'Courier new');

//Создание элементов управления

ctrl:= CreateButt on(20, 30, 70, 30, 1001, 'Кнопка 1');

SendMessage(ctrl, WM_SETFONT, HFONT(font), 1);

ctrl:= CreateButt on(100, 30, 70, 30, 1002,'Кнопка 2');

SendMessage(ctrl, WM_SETFONT, HFONT(font), 1);

ctrl:= CreateCheck(210, 30, 180, 20, 2001, 'Флажок 1');

SendMessage(ctrl, WM_SETFONT, HFONT(font), 1);

ctrl:= CreateCheck(210, 60, 180, 20, 2001, 'Флажок 2', True);

SendMessage(ctrl, WM_SETFONT, HFONT(font), 1);

ctrl:= CreateOpti on(210, 100, 180, 20, 3001, 'Переключатель 1', True);

SendMessage(ctrl, WM_SETFONT, HFONT(font), 1);

ctrl:= CreateOpti on(210,130,180,20,3002, 'Переключатель 2', False, True);

SendMessage(ctrl, WM_SETFONT, HFONT(font), 1);

ctrl:= CreateOpti on(210, 160, 180, 20, 3003, 'Переключатель 3', True);

SendMessage(ctrl, WM_SETFONT, HFONT(font), 1);

//Запуск цикла обработки сообщений

while (Longint(GetMessage(mess, 0, 0, 0)) <> 0)

do begin

TranslateMessage(mess);

DispatchMessage(mess);

end;

//Удаление шрифта

DeleteObject(font);


Вид окна с элементами управления, шрифт которых установлен любым из рассмотренных способов, показан на рис. 2.9.

Рис. 2.9. Шрифт элементов управления, отличный от системного


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

Листинг 2.36. Установка шрифта перебором элементов управления

//Шрифт для элементов управления

font:= CreateFont(16, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,

OUT_CHARACTER_PRECIS, CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY, DEFAULT_PITCH, 'Courier new');

//Создание элементов управления

CreateButton(20, 30, 70, 30, 1001, 'Кнопка 1');

CreateButton(100, 30, 70, 30, 1002,'Кнопка 2');

CreateCheck(210, 30, 180, 20, 2001, 'Флажок 1');

CreateCheck(210, 60, 180, 20, 2001, 'Флажок 2', True);

CreateOption(210, 100, 180, 20, 3001, 'Переключатель 1', True);

CreateOption(210, 130, 180, 20, 3002, 'Переключатель 2', False, True);

CreateOption(210, 160, 180, 20, 3003, 'Переключатель 3', True);

//Установка шрифта элементов управления

EnumChildWindows(hMainWnd, Addr(EnumFunc), font);

//Запуск цикла обработки сообщений

while (Longint(GetMessage(mess, 0, 0, 0)) <> 0)

do begin

TranslateMessage(mess);

DispatchMessage(mess);

end;

DeleteObject(font);


Собственно, за установление шрифта отвечает в приведенном листинге только одна строка:


EnumChildWindows(hMainWnd, Addr(EnumFunc), font);


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

Листинг 2.37. Реализация функции EnumFunc

function EnumFunc(wnd: HWND; param: LPARAM): BOOL; stdcall;

begin

SendMessage(wnd, WM_SETFONT, WPARAM(param), LPARAM(True));

EnumFunc:= True; //Продолжать перечисление

end;


В принципе, имя этой функции и названия параметров могут быть любыми, а вот типы параметров, возвращаемого значения и способ вызова функции должны быть именно такими, какие представлены в листинге 2.37. Функция должна возвращать True, если нужно продолжать перечисление окон, и False – в противном случае. Значение, которое было передано в качестве третьего параметра API-функции EnumChildWindows, передается в функцию обратного вызова. В данном случае этим параметром является дескриптор шрифта.