1.7. Добавление пункта в системное меню окна

   Обратите внимание на меню, раскрывающееся при щелчке кнопкой мыши на значке окна. В этом системном меню обычно присутствуют пункты, выполняющие стандартные действия над окном, такие, как закрытие, минимизация, максимизация и др. Однако есть функции, позволяющие получить доступ к этому меню, что дает возможность использовать его в своих целях.
   Для получения дескриптора (HMENU) системного меню окна используем API-функцию GetSystemMenu, а для добавления пункта в меню – функцию AppentMenu. Пример процедуры, добавляющей пункты в системное меню, приведен в листинге 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;
   В результате системное меню формы Forml станет похожим на меню, показанное на рис. 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;
   Обратите внимание на то, что числовые значения, которые переданы в функцию AppendMenu, используются для определения, какой именно пункт меню выбран. Чтобы меню вело себя обычным образом, все поступающие от него команды должны быть обработаны. Поэтому для всех команд, реакция на которые не заложена в реализованном нами обработчике, вызывается обработчик по умолчанию (функция DefWindowProc).

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

   Иногда вам может пригодиться возможность отображения формы поверх всех окон. За примером далеко ходить не надо: посмотрите на окно Диспетчера задач Windows. А теперь вспомните, терялось ли хоть раз окно Свойства: Экран среди других открытых окон. Это происходит из-за того, что оно перекрывается другими окнами и при этом не имеет никакого значка на Панели задач (правда, это окно все же можно найти с помощью Диспетчера задач).
   Из сказанного выше можно заключить, что как минимум в двух случаях отображение поверх других окон может пригодиться: для важных окон приложения (например, окно ввода пароля) и/или в случае, если значок приложения не выводится на Панели задач (как скрыть значок, было рассказано выше).
   После небольшого отступления рассмотрим способы, позволяющие задать положение формы так, чтобы другие окна не могли ее закрыть.
   Первый способ прост «до безобразия»: достаточно задать свойству FormStyle в окне Object Inspector значение f sStayOnTo. Результат этого действия показан на рис. 1.19 (обратите внимание, что форма закрывает Панель задач, которая по умолчанию также отображается поверх всех окон).
   Рис. 1.19. Форма, отображаемая поверх других окон
 
   Второй способ пригодится, если форма отображается постоянно как обычно, однако в определенные моменты времени требует к себе пристального внимания, для чего и помещается наверх. Способ основан на использовании API-функции SetWindowPos, которая кроме позиции и размера окна может еще устанавливать порядок рисования окна (Z-order).
   Примечание
   Под Z-order подразумевается порядок следования окон вдоль оси Z, направленной перпендикулярно экрану (оси X и Улежат в плоскости экрана).
   Вызов функции 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
Уменьшение размера ЕХЕ-файла. Использование Windows API

   • Источник лишних килобайт
   • Создание окна вручную
   • Окно с элементами управления
   • Стандартные диалоговые окна Windows
   • Установка шрифта элементов управления
 
   Не секрет, что размер скомпилированного ЕХЕ-файла Delphi часто значительно превосходит размер программ, написанных с использованием сред разработки от Microsoft (например, Visual C++, Visual Basic).
   Примечание
   Здесь и далее имеются в виду приложения с оконным интерфейсом (не консольные).
   При разработке крупных проектов этот факт абсолютно не смущает. Однако что же делать, если программисту на Delphi нужно написать программу, занимающую как можно меньше места (например, инсталлятор) или загружающуюся за минимальное время (например, сервисную программу). Конечно, такое приложение можно написать на C++, но что делать, если осваивать новый язык программирования нет времени?
   В этой главе будут рассмотрены два способа уменьшения размера ЕХЕ-файла: отказ от библиотеки Borland за счет прямого использования Windows API и разбиение приложения на несколько DLL. Первый способ позволяет реально уменьшить размер приложения. Однако написание Delphi-приложения (да еще и с оконным интерфейсом) с использованием только API-функций является задачей весьма трудоемкой, хотя и интересной, да к тому же и экзотичной. Второй же способ не уменьшает размера проекта в целом, но может сэкономить время запуска приложения.
   Вначале небольшое отступление. Итак, операционная система (в нашем случае это Windows) предоставляет интерфейс для программирования внутри себя – набор функций, заключенных в нескольких системных библиотеках, называемый Windows API (Windows Application Programming Interface – интерфейс программирования Windows-приложений). Любой проект под Windows на любом языке программирования в конечном счете сводится именно к приложению, использующему функции Windows API. Только использование этих самых функций может быть как явным, так и скрытым за использованием библиотек, поставляемых вместе со средой программирования.
   И еще один момент. В тексте постоянно говорится о Windows API, а не просто API. Это потому, что само понятие Application Programming Interface применяется ко многим системам, а не только к ОС, и уж тем более не только к Windows. Вот несколько примеров: UNIX API, Linux API, Oracle API (интерфейс для работы с СУБД Oracle) и т. д.
   Примечание
   В книге описаны только те возможности Window API, которые непосред – ственно используются в примерах. Полное описание Windows API является слишком большой задачей, для которой не хватит и книги. Если вам захочется изучить или хотя бы узнать больше о Windows API, то можно обратиться к специализированным изданиям по этой теме. Однако никакое издание не заменит MSDN (огромная справочная система от Micr osoft для Visual Studio).
   Теперь выясним, за счет чего разрастается ЕХЕ-файл приложения при использовании среды программирования Delphi.

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

   Для начала создадим новый проект Windows-приложения (Pro j ectl. exe). По умолчанию оно создает и показывает одну пустую форму (объявлена в модуле Unitl. pas). Ничего менять не будем, просто скомпилируем и посмотрим размер ЕХЕ-файла. Больше 300 Кбайт – не многовато ли для такого простого приложения?
   Кстати, простейшее оконное приложение, написанное на Visual C++ 6.0 (в Release-конфигурации, то есть без отладочной информации в ЕХЕ-файле) без использования MFC, имеет размер 28 Кбайт, с использованием библиотеки MFC (правда, окно диалоговое) – 20 Кбайт. Простейшее оконное приложение на Visual Basic 6.0 занимает всего 16 Кбайт.
   Из-за чего такая разница? Посмотрим, какие библиотеки используются приложениями, написанными на этих языках программирования. Это можно сделать, например, с помощью программы Dependency Walker, входящей в комплект Microsoft Visual Studio (рис. 2.1).
   Рис. 2.1. Библиотеки, используемые приложениями
 
   Как видим, приложение на Delphi (правый верхний угол окна на рис. 2.1) использует приличный набор функци й, помещенных в стандартные библиотеки операционной системы Windows. Кроме библиотек операционной системы, приложение на Delphi ничего не использует.
   Приложение WinAPI. ехе (левое верхнее окно на рис. 2.1) является примером чистого Windows API приложения в том смысле, что в нем не задействованы библиотеки-оболочки над API-функциями, каким-либо образом облегчающие программирование. Собственно, столько реально и «весит» простейшее оконное приложение.
   С приложением MFC. ехе уже интереснее: размер самого ЕХЕ-файла уменьшился за счет того, что часть кода работы с API-функциями переместилась в библиотеки. С приложением на Visual Basic (правое нижнее окно) еще интереснее – оно фактически представляет собой вызовы функций одной библиотеки, в которой и реализована вся поддержка программирования на этом языке (при детальном рассмотрении этой библиотеки в ней можно найти объявления встроенных функций Visual Basic).
   К чему это все? А к тому, что приложения на других языках программирования (в данном случае речь идет о продуктах Microsoft) совсем не менее «тяжеловесны», чем приложения, написанные на Borland Delphi, если при их написании программист пользуется не только API-функциями. Особенно примечателен в этом случае пример исполняемого файла Visual Basic, который хотя и имеет малый размер, но требует наличия библиотеки, размер которой около 1,32 Мбайт. Программа на Visual C++ с использованием, например, MFC, в которой реализованы классы оболочки над функциями Windows API (правда, не только они), требуетналичия нескольких DLL. Для Microsoft это не проблема, так как операционная система Windows выпускается именно этой компанией, а следовательно, обеспечить переносимость (здесь – работоспособность без установки) приложений, написанных с использованием ее же сред разработки, очень просто: достаточно добавить нужные библиотеки в состав ОС.
   Что же в таком случае осталось сделать Borland? Дабы не лишать программиста возможности пользоваться библиотеками с реализацией самых полезных классов (VCL и не только), код с реализацией этих самых классов приходится компоновать в один файл с самой программой. Вот и получается, что реализация этих самых классов в ЕХЕ-файле может занимать места гораздо больше, чем реализация собственно приложения. Так в нашем случае и получилось.
   Примечание
   Кстати, проект на Visual C++ также можно статически скомпоновать с библиотекой MFC (то есть включить код реализации классов в сам ЕХЕ-файл). Таким способом можно добиться независимости приложения от различных библиотек, кроме тех, что гарантированно поставляются с Windows. Но при этом размер ЕХЕ-файла рассмотренного выше приложения (в Release-конфигурации) возрастает до 192 Кбайт.
   Теперь обратимся к нашему проекту на Delphi. Посмотрим, что записано в файлах Unitl.pas и Projectl. dpr. Текст файла Unitl.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. Здесь можно увидеть подключение девяти модулей, объявление собственно класса формы TForml, а также строку, указывающую компилятору на использование файла ресурсов. Все модули, кроме первых двух, – это уже труды компании Borland, облегчающие жизнь простым программистам. Модуль такого же рода используется и в файле Pro j ectl. 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.3).
   Собственно, этих двух модулей должно хватить для того, чтобы реализовать оконное приложение, правда, использующее только стандартные функции WindowsAPI, стандартные элементы управления. В листинге 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.

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

   Раз уж речь зашла о приложениях с оконным интерфейсом, то самое время приступить к его реализации средствами 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.hbrBackground := 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
   PostQuitMessage(0); //При закрытии окна – выход
   WM_PAINT:
   begin
   //Перерисовка содержимого окна
   BeginPaint(hWnd, ps);
   TextOut(ps.hdc, 10, 10, 'Текст в окне', 12);
   EndPaint(hWnd, ps);
   end;
   else
   begin
   //Обработка по умолчанию
   WindowFunc := DefWindowProc(hWnd, msg, wParam, lParam);
   Exit;
   end;
   end;
   WindowFunc := S_OK; //Сообщение обработано
   end;
   В этой функции реализована обработка сообщения WMPAINT – запроса на перерисовку содержимого окна. Обработка сообщения WMCLOSE предусмотрена для того, чтобы при закрытии главного окна происходил выход из приложения. Для всех остальных сообщений выполняется обработка по умолчанию.
   Обратите особое внимание на прототип этой функции: типы возвращаемых значений, типы параметров и способ вызова функции должны быть именно такими, как в листинге 2.5. Возвращаемое значение зависит от конкретного сообщения. Чаще всего это SOK (константа, равная 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 и RegisterWindow. При создании окна использовались только стили WS_VISIBLE и WS_OVERLAPPEDWINDOWS. Но это далеко не все возможные стили окон. В приложении 2 приводится список всех стилей окон (если другого не сказано, то стили можно комбинировать при помощи оператора or). Кроме функции CreateWindow, для создания окон можно использовать фyнкциюCreateWindowEx. При этом появится возможность указать дополнительный (расширенный) стиль окна (первый параметр функции CreateWindowEx). Список расширенных стилей приводится все в том же приложении 2.
   В конце листинга 2.6 расположен цикл обработки сообщений:
   while (Longint(GetMessage(mess, hMainWnd, 0, 0)) > 0)
   do begin
   TranslateMessage(mess);
   DispatchMessage(mess);
   end;
   Здесь API-функция GetMessage возвращает значения больше 0, пока в очереди не обнаружится сообщение WMQUIT. В случае возникновения какой-либо ошибки функция GetMessage возвращает значение-1. Функция TranslateMessage преобразует сообщения типа WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN и WM_ SYSKEYUP В сообщения СИМВОЛЬНОГО ввода (WM_CHAR, WM_SYSCHAR, WM_DEADCHAR, WM_SYSDEADCHAR). Функция DispatchMessage в общем случае (за исключением сообщения WMTIMER) вызывает функцию обработки сообщений нужного окна.
   Внешний вид самого окна, созданного в этом примере, приводится на рис. 2.2.
   Рис. 2.2. Окно, созданное вручную
 
   Кстати, пока размер приложения всего 16 Кбайт.

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

   После того как мы рассмотрели создание простейшего окна, самое время позаботиться о его наполнении элементами управления. Для стандартных элементов управления в системе уже зарегистрированы классы окон. Их перечень следующий:
   • BUTTON – оконный класс, реализующий работу обычной кнопки, флажка, переключателя и даже рамки для группы элементов управления (GroupBox);
   • СОМВОВОХ – раскрывающийся список;
   • EDIT – текстовое поле, может быть как однострочным, так и многострочным, с полосами прокрутки или без;
   • LISTBOX – список;
   • SCROLLBAR – полоса прокрутки;
   • STATIC – статический текст (он же Label, надпись, метка и пр.), кроме текста, может содержать изображение.
   Ввиду большого количества возможных стилей окон элементов управления их перечень здесь не приводится, но его можно найти в приложении 2.

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

   Целесообразно написать более краткие функции создания элементов управления, чтобы, формируя интерфейс формы «на лету», не приходилось «украшать» код громоздкими вызовами функций CreateWindow tumCreateWindowEx. Этим мы сейчас и займемся. Сразу необходимо отметить: предполагается, что все функции помещены в модуль (модуль 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), можно использовать функцию CreateCombo из листинга 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) вполне подойдет фyнкция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);