newHeight:= (rc.Bottom – rc.Top) * 120 div 100;
   Width:= Width + newWidth – (rc.Right – rc.Left);
   Height:= Height + newHeight – (rc.Bottom – rc.Top);
 
   Примечание
   Чтобы после изменения масштаба формы можно было вернуться в точности к исходному масштабу (с помощью соответствующей обратной операции), нужно для уменьшения и увеличения использовать коэффициенты, произведение которых равно единице. Например, при уменьшении масштаба на 20 % (в 0,8 раз) его нужно увеличивать при обратной операции на 25 % (в 1/0,8 = 1,25 раза).

Добавление команды в системное меню окна

   Обратите внимание на меню, раскрывающееся при щелчке кнопкой мыши на значке окна. В этом системном меню обычно присутствуют пункты, выполняющие стандартные действия над окном, такие как закрытие, минимизация, максимизация и др. Для доступа к этому меню предусмотрены специальные функции, что дает возможность использовать его в своих целях.
   Для получения дескриптора (HMENU) системного меню окна используется API-функция GetSystemMenu, а для добавления пункта в меню – функция AppendMenu. Пример процедуры, добавляющей пункты в системное меню, приведен в листинге 1.26.
Листинг 1.26. Добавление пунктов в системное меню окна
   procedure TForm1.FormCreate(Sender: TObject);
   var hSysMenu: HMENU;
   begin
   hSysMenu:= GetSystemMenu(Handle, False);
   AppendMenu(hSysMenu, MF_SEPARATOR, 0, '');
   AppendMenu(hSysMenu, MF_STRING, 10001, 'Увеличить на 20%');
   AppendMenu(hSysMenu, MF_STRING, 10002, 'Уменьшить на 20 %');
   end;
 
   В результате выполнения этого кода системное меню формы Form1 станет похожим на меню, показанное на рис. 1.18.
   Рис. 1.18. Пользовательские команды в системном меню
 
   Однако недостаточно просто создать команды меню – нужно предусмотреть обработку их выбора. Это делается в обработчике сообщения WM_SYSCOMMAND (листинг 1.27).
Листинг 1.27. Обработка выбора пользовательских пунктов в системном меню
   procedure TForm1.WMSysCommand(var Message: TWMSysCommand);
   begin
   if Message.CmdType = 10001 then
   //Увеличение масштаба
   ChangeScale(120, 100)
   else if Message.CmdType = 10002 then
   ChangeScale(80, 100)
   else
   //Обработка по умолчанию
   DefWindowProc(Handle, Message.Msg, Message.CmdType, 65536 * Message.YPos+ Message.XPos);
   end;
 
   Обратите внимание на то, что числовые значения, переданные в функцию Append-Menu, используются для определения, какой именно пунктменю выбран. Чтобы меню работало стандартным образом, все поступающие от него команды должны быть обработаны. Поэтому для всех команд, реакция на которые не заложена в реализованном обработчике, вызывается обработчик по умолчанию (функция DefWindowProc).

Отображение формы поверх других окон

   Иногда вам может пригодиться возможность отображения формы поверх всех окон. За примером далеко ходить не надо: посмотрите на окно Диспетчера задач Windows. Теперь вспомните, терялось ли хоть раз окно Свойства: Экран среди других открытых окон. Это происходит благодаря тому, что это окно перекрывается другими окнами и при этом не имеют никакого значка на Панели задач (правда, это окно все же можно найти с помощью Диспетчера задач).
   Из сказанного выше можно заключить, что отображение окна поверх других окон может пригодиться как минимум в двух случаях: для важных окон приложения (например, окна ввода пароля) и/или в случае, если значок приложения не выводится на Панели задач (как скрыть значок, было рассказано выше).
   После небольшого отступления можно рассмотреть способы, позволяющие задать положение формы так, чтобы другие окна не могли ее закрыть.
   Первый способ прост до предела: достаточно присвоить свойству FormStyle в окне Object Inspector значение fsStayOnTo. Результат этого действия показан на рис. 1.19 (обратите внимание, что форма закрывает Панель задач, которая по умолчанию также отображается поверх всех окон).
   Рис. 1.19. Форма, отображаемая поверх других окон
 
   Второй способ пригодится, если форма постоянно отображается обычным образом, но в определенные моменты времени требует к себе более пристального внимания, для чего и помещается наверх. Способ основан на использовании API-функции SetWindowPos, которая, кроме позиции и размера окна, может устанавливать и порядок рисования окна (Z-order).
   Примечание
   Под Z-order подразумевается порядок следования окон вдоль оси Z, направленной перпендикулярно экрану (оси X и Y лежат в плоскости экрана).
   Вызов функции SetWindowPos для перемещения окна наверх выглядит следующим образом (Handle – дескриптор нужного окна):
 
   SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE)
 
   В этом случае окно может быть закрыто другим окном, также отображающимся поверх других (например, Диспетчером задач).
   Чтобы восстановить нормальное положение (порядок рисования) окна, можно вызвать функцию SetWindowPos со следующим набором параметров:
 
   SetWindowPos(Handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE)
 
   После этого другие окна, не отображаемые поверх других, смогут снова перекрывать форму.

Глава 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);