Введение
Любую современную программу или программную технологию можно представить как совокупность программных "слоев". Каждый из этих слоев производит свою собственную работу, которая заключается в повышении уровня абстракции производимых операций. Так, самый низший слой (слои) вводит понятия, которые позволяют абстрагироваться от используемого оборудования; следующий слой (слои) позволяет программисту абстрагироваться от сложной последовательности вызовов функций, вводя такое понятие как протокол и т.д. Практически в любом современном программном продукте можно обнаружить и выделить около десятка последовательных слоев абстракции.
Абстракция от оборудования и низкоуровневых протоколов вводится в ядра операционных систем в виде библиотек API (Application Program Interface). Однако современные тенденции приводят к необходимости абстрагирования и от самих операционных систем, что позволяет переносить программы с одной операционной системы на другую путем простой перекомпиляции (транслируемые программы, в основном, вообще не требуют никаких действий по переносу).
Абстракцию, которая доступна программисту в виде библиотек API можно назвать базовой. Это самый низкий уровень абстракции, который доступен для прикладного программирования. На уровне ядра системы доступны и более низкие уровни абстракции, однако для их использования необходимо разрабатывать специализированные программы (драйвера, модули). Базовый уровень абстракции (API) предоставляет максимально широкие возможности для прикладного программирования и является наиболее гибким. Однако, программирование с использованием API является гораздо более трудоемким и приводит к значительно большим объемам исходного кода программы, чем программирование с использованием дополнительных библиотек.
Дополнительные библиотеки поставляются со многими средствами разработки с целью уменьшения трудоемкости и сроков разработки программ, что в итоге приводит к повышению их конкурентноспособности. Но применение дополнительных библиотек абстракций приводит к резкому увеличению размеров откомпилированных программ, из-за того что в программу включается код используемых библиотек, к тому же это включение зачастую бывает неэффективным - в программу включаются неиспользуемые участки кода. Кроме того, чем больше уровень абстракции библиотеки, тем сложнее ее код, и тем больше трудностей возникает при решении сложных задач. Приходится учитывать множество взаимосвязей и взаимных влияний отдельных элементов и процессов библиотеки друг на друга. Кроме того, структура и функциональность любой библиотеки обычно рассчитывается на удовлетворение всех потенциально возникающих задач, что приводит к ее громоздкости и неэффективности.
В Delphi используется очень мощная и сложная библиотека VCL (Visual Components Library), которая помимо непосредственных абстракций вводит также и множество своих функциональных классов. В этой библиотеке находятся компоненты для визуального отображения информации, работы с базами данных, с системными объектами, компоненты для работы с Internet-протоколами, классы для написания своих COM-объектов и многое другое. Модули библиотеки подключаются к компиляции по мере необходимости, однако базовый размер простейшего диалогового проекта с одной формой превышает 300кБ (со статически скомпонованной библиотекой). И такой размер во многих случаях может оказаться слишком большим, особенно если программа не требует большой функциональности в интерфейсе.
Для решения этой проблемы можно отказаться от использования библиотеки VCL, и программировать, используя базовый набор функций Win32 API. Однако, если при разработке линейных, недиалоговых, нерезидентных программ не возникает никаких трудностей, то разработка программ, требующих активного взаимодействия с пользователем или системой, становится трудоемкой. Структурное программирование, рекомендуемое в таких случаях, оказывается неэффективным и трудоемким.
Данная статья посвящена проблеме создания и использования компактной объектно-ориентированной библиотеки, которая бы облегчила построение небольших и эффективных программ на основе Win32 API.
Автору известны две объектно-ориентированные библиотеки, которые можно рассматривать как альтернативу библиотеке VCL при написании компактных программ. Это библиотеки классов XCL и ACL. Обе библиотеки бесплатны и поставляются в исходных кодах.
Библиотека ACL (API control library)
Автор: Александр Боковиков, Екатеринбург, Россия
Классы: TFont, TFonts, TControl, TWinControl, TStdControl, TLabel, TEdit, TListBox, TButton, TCheckBox, TComboBox, TGroupBox, TProgressBar, TKeyboard
Библиотека XCL (Extreme class library)
Автор: Vladimir Kladov (Mr.Bonanzas)
Классы: XForm, XApplet, XCanvas, XPen, XBrush, XFont, ZDDB, ZHiBmp, ZDIBitmap, ZBitmap, ZIcon, ZGifDecoder, ZGif, ZJpeg, XLabel, XButton, XBevel, XPanel, XSplitPanel, XStatus, XGrep, XGroup, XCheckBox, XRadioBox, XPaint, XScroller, XScrollBox, XScrollBoxEx, XEdit, XNumEdit, XCombo, XGrid, XListView, XMultiList, XNotebook, XTabs, XTabbedNotebook, XCalendar, XGauge, XGaugePercents, XHysto, XHystoEx, XImageList, XImgButton, XTooltip, XCustomForm, XDsgnForm, XDsgnNonvisual, CLabel, CPaint, CButton, CEdit, CMemo, CCheckBox, CRadioBox, CListBox, CComboBox, ZList, ZMenu, ZPopup, ZMainMenu, ZPopupMenu, ZTimer, ZStrings, ZStringList, ZIniFile, ZThread, ZQueue, ZFileChange, ZDirChange, ZOpenSaveDialog, ZOpenDirDialog, ZTree, ZDirList, ZDirListEx, ZRegistry, ZStream, ZFileStream, ZMemoryStream, XStrUtils.pas, XDateUtils.pas, XFileUtils.pas, XWindowUtils, XPrintUtils, XShellLinks.pas, XJustOne.pas, XJustOneNotify.pas, XPascalUnit.pas, XSysIcons.pas, XCanvasObjectsManager, XRotateFonts, XFocusPainter, XFormsStdMouseEvents, XFormsStdKeyEvents, XFormAutoSizer, XAligner, XControlAutoPlacer, XMfcAntiFlicker, XSplitSizer, XResizeAntiFlicker, XCaretShower, XEditMouseSelect, XEditClipboard, XEditUndo, XListMouseSel, XListKeySel, XListEdit, ZNamedTags, XBtnRepeats, XBufLabels, XBackgrounds, XWndDynHandlers
Как видно из списка приведенных для каждой библиотеки классов, эти библиотеки предендуют скорее не на помощь при написании программ с использованием Win32 API, а пытаются создать более высокий уровень абстракции чем API, по крайней мере в графической части (особенно это относится к XCL). Более того, иерархия и перечень объектов совпадают с соответствующими структурами в библиотеке VCL, что скорее всего связано с желанием авторов обеспечить логическую совместимость с VCL при построении программ на основе этих библиотек.
Данные библиотеки не обеспечивают минимального размера программы, за счет того что предоставляют более высокий уровень абстракции. Они являются компромисом между программированием с использованием VCL и программированием на чистом API.
Можно выделить несколько недостатков присущих даже не столько указанным библиотекам, а сколько решениям и принципам на которых они основаны.
· | Несмотря на сравнительно малый размер получаемых программ, размер программ написанных с использованием только Win32 API был бы меньше. |
· | Помимо изучения Win32 необходимо изучение структуры классов, предлагаемых в этих библиотеках. |
· | Библиотека XCL не поддерживает механизм message-процедур. |
· | Архитектура этих библиотек, по мнению автора, является весьма громоздкой. Структуры и классы данных библиотек аналогичны структурам VCL, что приводит к неэффективности программ (ведь мы стараемся написать компактную программу, не так ли ?). |
· | Использование библиотеки ACL невозможно совместно с библиотекой VCL. |
· | Запутанность и большой размер классов наряду с "самодокументированным" кодом (то есть отсутствием файлов помощи) затрудняют изучение библиотек. |
· | Библиотеки разрабатываются и поставляются в частном порядке на некоммерческой основе, поэтому при разработке большого проекта на основе этих библиотек существует потенциальный риск отказа от поддержки. При этом вся тяжесть по устранению ошибок и развитию кода библиотеки ляжет на вас (если вы не вращаетесь в Delphi-сообществе). |
Стандартным видом API-программирования является структурное программирование. Примеры такого программирования на Win32 API есть практически в любой книжке по Borland Pascal, Borland C++, Microsoft Visual C++ и другим системам разработки. Множество примеров API-программирования на С содержится в поставке Microsoft Visual C++.
Структурное программирование с оконными функциями, процедурами обработки команд, не в состоянии обеспечить быструю и эффективную разработку программ. В современной ситуации большинство программистов привыкло к объектно-ориентированному методу, с возможностью инкапсуляции, наследования и переопределения методов объектов. Такое программирование оказывается наиболее эффективным.
Кроме того, для построения эффективной API-библиотеки прежде всего нужно выяснить, какие задачи при работе с Win32 API являются наиболее трудоемкими. Практика показывает, что наиболее неудобным и трудоемким элементом является реализация основного диспетчера логики программы - оконной функции. Реализация этой функции в качестве метода класса, а не простой глобальной функции, позволила бы улучшить структуру кода и облегчить программирование путем инкапсулирования всех переменных внутри оконного класса.
Программирование может быть еще более облегчено, есть возпользоваться механизмом message-процедур языка Object Pascal. Вызов этих процедур полностью лежит на компиляторе и корневом объекте TObject и включает в себя методы Dispatch, DefaultHandler, а также все методы, объявленные с директивой message. Такое решениее позволит полностью отказаться от громоздкого оператора case в оконной функции.
Учитывая все вышеперечисленное автором была создана компактная библиотека оконных классов WinLite. Эта библиотека является минимальной, она не вводит более высоких уровней абстракции чем существуют в Win32 API - она только облегчает работу, переводом программирования в объектно-ориентированное русло. Размер библиотеки очень небольшой и вся она помещается в один модуль. Библиотека реализует базовый класс TLiteFrame и построенные на основе него оконные классы:
· | TLiteWindow - класс окна, с возможностью subclass'инга; |
· | TLiteDialog - класс немодального диалога; |
· | TLiteDialogBox - класс модального диалога. |
Библиотека может быть использована совместно с VCL. На первый взгляд, это возможность является абсурдной и ненужной, так как об экономии размера в этом случае не может быть и речи. Однако, иногда бывают моменты, когда реализация специфических оконных элементов на основе объектов TWinControl или TCustomControl может быть затруднена или неэффективна из-за их сложности и неочевидного поведения. В этом случае, можно реализовать такой элемент на базе класса TLiteWindow - он будет вести себя стандартным образом, как и полагается вести себя стандартному оконному элементу Win32.
Благодаря своей простой архитектуре библиотека может быть использована в многопоточной программе. Конечно, вы не сможете вызывать методы классов одного потока из другого потока без соответствующей синхронизации. Однако, вы можете беспрепятственно создавать оконные классы в различных потоках без блокировки и синхронизации, а также посылать сообщения оконным классам в другом потоке.
Практический совет: при API-программировании программист должен сам следить за корректным освобождением многочисленных ресурсов, которые занимает программа во время выполнения. Поэтому, для облегчения этой задачи используйте какую-либо контролирующую утилиту, например MemProof или Numega BoundsChecker. Корректное освобождение занятых ресурсов крайне необходимо !
К тому же, прежде чем вы решите работать над своим проектом в русле Win32 API, подумайте, а зачем вам это нужно? В подавляющем числе случаев размер программы не имеет никакого значения. Я не хочу сказать, что API-программирование сложнее чем VCL-программирование. Во многих случаях легче изучить и написать 10 вызовов API с кучей аргументов и понимать, что происходит, чем написать 1 вызов простой, на первый взгляд, VCL-инструкции и потом долго исследовать дебри VCL в поисках ответа. Просто API-программирование - это другая культура, к которой вы, возможно, не привыкли. И первоначальная работа может вызвать у вас сильное разочарование. API-программирование требует дотошности, кропотливости и внимательного изучения документации.
Те же, кто отважился программировать на API, наряду с библиотекой WinLite могут совместно использовать невизуальные классы как из состава VCL (модули SysUtils, Classes), так и многие сторонние - естественно, что размер вашей программы при этом увеличится.
Для редактирования шаблонов диалогов можно использовать любой редактор ресурсов, например Borland Resource WorkShop, правда он несколько неудобен, а окончательный
Code: |
// WinLite, библиотека классов и функций для работы с Win32 API // (c) Николай Мазуркин, // _____________________________________________________________________________ // Оконные классы ////////////////////////////////////////////////////////////////////////////////
unit WinLite;
interface
uses Windows, Messages;
Инициализационные структуры Объявление структур, которые используются для формирования параметров вновь создаваемых окон и диалогов соответственно.
//////////////////////////////////////////////////////////////////////////////// // Параметры для создания окна //////////////////////////////////////////////////////////////////////////////// type TWindowParams = record Caption : PChar; Style : DWord; ExStyle : DWord; X : Integer; Y : Integer; Width : Integer; Height : Integer; WndParent : THandle; WndMenu : THandle; Param : Pointer; WindowClass : TWndClass; end;
//////////////////////////////////////////////////////////////////////////////// // Параметры для создания диалога //////////////////////////////////////////////////////////////////////////////// type TDialogParams = record Template : PChar; WndParent : THandle; end;
Декларация базового класса TLiteFrame Базовый класс для окон и диалогов. Инкапсулирует в себе дескриптор окна и объявляет общую оконную процедуру. Реализует механизм message-процедур.
//////////////////////////////////////////////////////////////////////////////// // TLiteFrame // _____________________________________________________________________________ // Базовый класс для объектов TLiteWindow, TLiteDialog, TLiteDialogBox //////////////////////////////////////////////////////////////////////////////// type TLiteFrame = class(TObject) private FWndCallback: Pointer; FWndHandle : THandle; FWndParent : THandle; function WindowCallback(hWnd: HWnd; Msg, WParam, LParam:Longint):Longint; stdcall; protected procedure WindowProcedure(var Msg: TMessage); virtual; public property WndHandle: THandle read FWndHandle; property WndCallback: Pointer read FWndCallback; public constructor Create(AWndParent: THandle); virtual; destructor Destroy; override; end;
Декларация оконного класса TLiteWindow Создание уникального класса окна и создание окна. Возможность субклассинга стороннего окна.
//////////////////////////////////////////////////////////////////////////////// // TLiteWindow // _____________________________________________________________________________ // Оконный класс //////////////////////////////////////////////////////////////////////////////// type TLiteWindow = class(TLiteFrame) private FWndParams : TWindowParams; FWndSubclass: Pointer; protected procedure CreateWindowParams(var WindowParams: TWindowParams); virtual; public procedure DefaultHandler(var Msg); override; constructor Create(AWndParent: THandle); override; constructor CreateSubclassed(AWnd: THandle); virtual; destructor Destroy; override; end;
Декларация диалогового класса TLiteDialog Загрузка шаблона диалога и создание диалога.
//////////////////////////////////////////////////////////////////////////////// // TLiteDialog // _____________________________________________________________________________ // Диалоговый класс //////////////////////////////////////////////////////////////////////////////// type TLiteDialog = class(TLiteFrame) private FDlgParams : TDialogParams; protected procedure CreateDialogParams(var DialogParams: TDialogParams); virtual; public procedure DefaultHandler(var Msg); override; constructor Create(AWndParent: THandle); override; destructor Destroy; override; end;
Декларация модального диалогового класса TLiteDialogBox Загрузка шаблона диалога и создание диалога. Модальный показ диалога.
//////////////////////////////////////////////////////////////////////////////// // TLiteDialogBox // _____________________________________________________________________________ // Модальный диалоговый класс //////////////////////////////////////////////////////////////////////////////// type TLiteDialogBox = class(TLiteFrame) private FDlgParams : TDialogParams; protected procedure CreateDialogParams(var DialogParams: TDialogParams); virtual; public procedure DefaultHandler(var Msg); override; public function ShowModal: Integer; end;
Реализация базового класса TLiteFrame implementation
//////////////////////////////////////////////////////////////////////////////// // TLiteFrame // _____________________________________________________________________________ // Инициализация / финализация ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// // Конструктор //////////////////////////////////////////////////////////////////////////////// constructor TLiteFrame.Create(AWndParent: THandle); begin inherited Create; // Запоминаем дескриптор родительского окна FWndParent := AWndParent; // Создаем место под блок обратного вызова FWndCallback := VirtualAlloc(nil,12,MEM_RESERVE or MEM_COMMIT,PAGE_EXECUTE_READWRITE); // Формируем блок обратного вызова asm mov EAX, Self mov ECX, [EAX].TLiteFrame.FWndCallback mov word ptr [ECX+0], $6858 // pop EAX mov dword ptr [ECX+2], EAX // push _Self_ mov word ptr [ECX+6], $E950 // push EAX mov EAX, OFFSET(TLiteFrame.WindowCallback) sub EAX, ECX sub EAX, 12 mov dword ptr [ECX+8], EAX // jmp TLiteFrame.WindowCallback end; end;
//////////////////////////////////////////////////////////////////////////////// // Деструктор //////////////////////////////////////////////////////////////////////////////// destructor TLiteFrame.Destroy; begin // Уничтожаем структуру блока обратного вызова VirtualFree(FWndCallback, 0, MEM_RELEASE); // Уничтожение по умолчанию inherited; end;
//////////////////////////////////////////////////////////////////////////////// // TLiteFrame // _____________________________________________________________________________ // Функции обработки сообщений ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// // Функция обратного вызова для получения оконных сообщений //////////////////////////////////////////////////////////////////////////////// function TLiteFrame.WindowCallback(hWnd: HWnd; Msg, WParam, LParam: Integer): Longint; var WindowMsg : TMessage; begin // Запоминаем дескриптор окна, если это первый вызов оконной процедуры if FWndHandle = 0 then FWndHandle := hWnd; // Формируем сообщение WindowMsg.Msg := Msg; WindowMsg.WParam := WParam; WindowMsg.LParam := LParam; // Обрабатываем его WindowProcedure(WindowMsg); // Возвращаем результат обратно системе Result := WindowMsg.Result; end;
//////////////////////////////////////////////////////////////////////////////// // Виртуальная функция для обработки оконных сообщений //////////////////////////////////////////////////////////////////////////////// procedure TLiteFrame.WindowProcedure(var Msg: TMessage); begin // Распределяем сообщения по обработчикам Dispatch(Msg); end;
Реализация оконного класса TLiteWindow //////////////////////////////////////////////////////////////////////////////// // TLiteWindow // _____________________________________________________________________________ // Инициализация / финализация ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// // Конструктор //////////////////////////////////////////////////////////////////////////////// constructor TLiteWindow.Create(AWndParent: THandle); begin inherited; // Формируем параметры окна CreateWindowParams(FWndParams); // Регистрируем класс окна RegisterClass(FWndParams.WindowClass); // Создаем окно with FWndParams do CreateWindowEx(ExStyle, WindowClass.lpszClassName, Caption, Style, X, Y, Width, Height, WndParent, WndMenu, hInstance, Param ); end;
//////////////////////////////////////////////////////////////////////////////// // Конструктор элемента с субклассингом //////////////////////////////////////////////////////////////////////////////// constructor TLiteWindow.CreateSubclassed(AWnd: THandle); begin inherited Create(GetParent(AWnd)); // Сохраняем оконную функцию FWndSubclass := Pointer(GetWindowLong(AWnd, GWL_WNDPROC)); // Сохраняем дескриптор окна FWndHandle := AWnd; // Устанавливаем свою оконную функцию SetWindowLong(FWndHandle, GWL_WNDPROC, DWord(WndCallback)); end;
//////////////////////////////////////////////////////////////////////////////// // Деструктор //////////////////////////////////////////////////////////////////////////////// destructor TLiteWindow.Destroy; begin // Наш объект - объект субклассиннга ? if FWndSubclass = nil then begin // Уничтожаем класс окна UnregisterClass(FWndParams.WindowClass.lpszClassName, hInstance); // Уничтожаем окно if IsWindow(FWndHandle) then DestroyWindow(FWndHandle); end else // Восстанавливаем старую оконную функцию SetWindowLong(FWndHandle, GWL_WNDPROC, DWord(FWndSubclass)); // Уничтожение по умолчанию inherited; end;
//////////////////////////////////////////////////////////////////////////////// // Формирование параметров окна по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure TLiteWindow.CreateWindowParams(var WindowParams: TWindowParams); var WndClassName : string; begin // Формируем имя класса Str(DWord(Self), WndClassName); WndClassName := ClassName+':'+WndClassName; // Заполняем информацию о классе окна with FWndParams.WindowClass do begin style := CS_DBLCLKS; lpfnWndProc := WndCallback; cbClsExtra := 0; cbWndExtra := 0; lpszClassName := PChar(WndClassName); hInstance := hInstance; hIcon := LoadIcon(0, IDI_APPLICATION); hCursor := LoadCursor(0, IDC_ARROW); hbrBackground := COLOR_BTNFACE + 1; lpszMenuName := ''; end; // Заполняем информацию об окне with FWndParams do begin WndParent := FWndParent; Caption := 'Lite Window'; Style := WS_OVERLAPPEDWINDOW or WS_VISIBLE; ExStyle := 0; X := Integer(CW_USEDEFAULT); Y := Integer(CW_USEDEFAULT); Width := Integer(CW_USEDEFAULT); Height := Integer(CW_USEDEFAULT); WndMenu := 0; Param := nil; end; end;
//////////////////////////////////////////////////////////////////////////////// // TLiteWindow // _____________________________________________________________________________ // Функции обработки сообщений ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// // Обработчик сообщений по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure TLiteWindow.DefaultHandler(var Msg); begin // Наш объект - объект субклассиннга ? if FWndSubclass = nil then // Вызываем системную функцию обработки сообщений with TMessage(Msg) do Result := DefWindowProc(FWndHandle, Msg, WParam, LParam) else // Вызываем старую оконную функцию обработки сообщений with TMessage(Msg) do Result := CallWindowProc(FWndSubclass, FWndHandle, Msg, WParam, LParam); end;
Реализация диалогового класса TLiteDialog //////////////////////////////////////////////////////////////////////////////// // TLiteDialog // _____________________________________________________________________________ // Инициализация / финализация ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// // Конструктор //////////////////////////////////////////////////////////////////////////////// constructor TLiteDialog.Create(AWndParent: THandle); begin inherited; // Формируем параметры диалога CreateDialogParams(FDlgParams); // Создаем диалог with FDlgParams do CreateDialogParam(hInstance, Template, WndParent, WndCallback, 0); end;
//////////////////////////////////////////////////////////////////////////////// // Деструктор //////////////////////////////////////////////////////////////////////////////// destructor TLiteDialog.Destroy; begin // Уничтожаем диалог if IsWindow(FWndHandle) then DestroyWindow(FWndHandle); // Уничтожение по умолчанию inherited; end;
//////////////////////////////////////////////////////////////////////////////// // Формирование параметров диалога по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure TLiteDialog.CreateDialogParams(var DialogParams: TDialogParams); begin DialogParams.WndParent := FWndParent; DialogParams.Template := ''; end;
//////////////////////////////////////////////////////////////////////////////// // Обработка сообщений по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure TLiteDialog.DefaultHandler(var Msg); begin // Возвращаемые значения по умолчанию with TMessage(Msg) do if Msg = WM_INITDIALOG then Result := 1 else Result := 0; end;
Реализация модального диалогового класса TLiteDialogBox //////////////////////////////////////////////////////////////////////////////// // TLiteDialogBox // _____________________________________________________________________________ // Инициализация / финализация ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// // Формирование параметров диалога по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure TLiteDialogBox.CreateDialogParams(var DialogParams: TDialogParams); begin DialogParams.WndParent := FWndParent; DialogParams.Template := ''; end;
//////////////////////////////////////////////////////////////////////////////// // Активизация модального диалога //////////////////////////////////////////////////////////////////////////////// function TLiteDialogBox.ShowModal: Integer; begin // Формируем параметры диалога CreateDialogParams(FDlgParams); // Показываем диалог with FDlgParams do Result := DialogBoxParam(hInstance, Template, WndParent, WndCallback, 0); end;
//////////////////////////////////////////////////////////////////////////////// // Обработка сообщений по умолчанию //////////////////////////////////////////////////////////////////////////////// procedure TLiteDialogBox.DefaultHandler(var Msg); begin // Возвращаемые значения по умолчанию with TMessage(Msg) do if Msg = WM_INITDIALOG then Result := 1 else Result := 0; end;
end. |
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!