Сообщений 0 Оценка 0 [+1/-0] Оценить |
Введение Визуальные компоненты в CLX Жизнь и смерть кнопки в run time Механизм обработки событий в VCL и CLX. Внедрение нового фильтра в существующий класс Стили визуальных компонентов |
В прошлом году Borland выпустил Kylix – RAD-среду для разработки Linux-приложений. Если выразиться точнее, это не Linux-приложения, а приложения для библиотеки Qt (кросс-платформной С++-библиотеки, облегчающей создание GUI-приложений). Так как Qt – это С++-библиотека, а Kylix – среда, использующая в качестве языка Object Pascal, напрямую эту библиотеку использовать нельзя. Поэтому Borland вынужден был создать библиотеку CLX, интерфейсно похожую на используемую в Delphi, но являющуюся оберткой не для Windows API, а для Qt. Эта библиотека была добавлена в Delphi 6, что позволило создавать кросс-платформный код.
Данная статья посвящена изучению некоторых особенностей CLX и сравнению ее с VCL.
Несмотря на сходство CLX и VCL, эти библиотеки серьезно различаются. Большинство нововведений вызвано необходимостью поддерживать, как минимум, две платформы – Windows и Linux. Серьезные отличия в архитектурах этих двух операционных систем и привели к созданию CLX, вместо доработки VCL, поскольку последняя активно использовала специфические возможности Windows.
CLX логически разбита на несколько частей, каждая из которых реализует какую-либо функциональность. Часть CLX, отвечающая за работу с визуальными компонентами, названа Visual CLX. При создании Visual CLX программистам Borland пришлось столкнуться с несколькими проблемами, понять и обсудить которые мы попытаемся ниже.
VCL использовала в своей работе «родные» элементы управления Windows, предоставляя объектную оболочку вокруг них. Именно этот факт не позволил сделать VCL мультиплатформенной. Элементы управления CLX созданы на основе элементов управления, реализованных в мультиплатформенной C++-библиотеке Qt фирмы Trolltech. Библиотека Qt позволяет обеспечить переносимость приложений между Linux и Windows на уровне исходного кода C++. Элемент управления в библиотеке Qt назван widget.
Так как Qt – это C++ библиотека, прямая работа из Kylix (который поддерживает Object Pascal), к сожалению, невозможна. Задача обеспечения взаимодействия была решена написанием промежуточного программного слоя, называемого Qt interface library. Данная прослойка реализована в виде набора глобальных функций и процедур (не принадлежащих каким-либо объектам Kylix). Она позволяет вызывать из Object Pascal методы объектов C++. Эти функции и процедуры названы в Kylix flat-методами. Первым параметром в них всегда выступает ссылка на экземпляр C++-объекта.
Например, в Object Pascal вызов метода выглядит так:
Button1.SetBounds(10, 10, 75, 25); |
а во flat-модели так:
TButton_SetBounds(Button1, 10, 10, 75, 25); |
Однако, так как Qt написана на C++, а не на Object Pascal, передать ссылку на паскалевский объект не получится. По этой причине в CLX введена дополнительная иерархия классов (дублирующая иерархию Qt), позволяющая в Object Pascal хранить и использовать указатели на объекты С++. Классы данной иерархии оканчиваются на H и называются opaque reference:
QGArrayH = class(TObject) end; QArrayH = class(QGArrayH) end; QPointArrayH = class(QArrayH) end; QGCacheIteratorH = class(TObject) end; QAsciiCacheIteratorH = class(QGCacheIteratorH) end; QCacheIteratorH = class(QGCacheIteratorH) end; QIntCacheIteratorH = class(QGCacheIteratorH) end; QGDictIteratorH = class(TObject) end; |
Вызов методов С++-объекта через opaque reference напрямую невозможен, однако они передаются в качестве параметров flat-методам. Иерархия классов позволяет отследить на этапе компиляции факт передачи opaque reference, соответствующей типу создаваемого объекта.
Никто не мешает использовать flat-методы напрямую, без ООП, как это показано ниже:
uses Qt, QTypes; ... var Btn: QPushButtonH; ... procedure TForm1.FormCreate(Sender: TObject); var Msg: TCaption; begin Btn := QPushButton_create(Handle, PChar('Btn')); QPushButton_setGeometry(Btn, 10, 10, 75, 25); Msg := 'Press me'; QButton_setText(Btn, PWideString(@Msg)); end; procedure TForm1.FormDestroy(Sender: TObject); begin QPushButton_destroy(Btn); end; |
Но все же гораздо удобнее воспользоваться объектами-оболочками CLX.
Некоторые из ключевых методов, отвечающих за создание визуального компонента в VCL (наследника TWinControl), отличаются от методов CLX (кстати, в CLX элемент управления назван widget). Попробуем разобраться в этих различиях.
Для отображения визуального компонента в VCL необходим handle окна. Для его создания вызывается виртуальный метод CreateHandle. В свою очередь CreateHandle вызывает виртуальный метод CreateWnd для получения handle и установки положения элемента управления по оси z (z-order).
CreateWnd вызывает необходимые для создания элемента управления функции Windows API. Для назначения параметров элемента управления из метода CreateWnd вызывается виртуальный метод CreateParams, который при использовании встроенных элементов управления Windows вызывает еще и метод CreateSubClass. В завершение вызывается метод CreateWindowHandle. Для создания элемента управления Windows он использует вызов функции API CreateWindowEx.
Если элемент управления создан, когда-то его придется и уничтожить. Цепочка вызовов методов при этом выглядит следующим образом: DestroyHandle, DestroyWnd, DestroyWindowHandle, DestroyWindow.
В CLX предок всех визуальных компонентов TWidgetControl также имеет цепочку методов для создания и уничтожения элементов управления, реализованных в библиотеке Qt. При создании элемента управления вызывается метод CreateHandle, однако, в отличие от VCL, он не виртуальный. CreateHandle вызывает виртуальный метод CreateWidget, отвечающий за создание элемента управления Qt и сохранение Hanlde данного элемента управления. Он также создает перехватчик (hook) и сохраняет его значение в свойстве Hooks. Хук необходим для организации обработки событий. Для инициализации параметров widget-а из метода CreateHandle вызывается InitWidget, а из него HookEvents, который использует объект Hook для перехвата сигналов widget.
Для уничтожения элемента управления вызывается не виртуальный метод DestroyHandle. Он вызывает виртуальный метод DestroyWidget. Первым делом метод DestroyWidget вызывает динамический метод WidgetDestroyed (который уничтожает hook), а затем уничтожает и сам widget.
Ниже в таблице отображены цепочки вызова методов при создании и уничтожении элемента управления для VCL и CLX.
VCL | CLX | |
Создание | CreateHandle CreateWnd CreateParams CreateSubClass CreateWindowHandle CreateWindowEx | CreateHandle CreateWidget widget constructor InitWidget HookEvents |
Освобождение | DestroyHandle DestroyWnd DestroyWindowHandle DestroyWindow | DestroyHandle DestroyWidget WidgetDestroyed widget destructor |
В данном разделе будут рассмотрены рассмотрим механизм доставки и обработки системных сообщений, посылаемых элементу управления.
CLX-приложения должны работать как под Windows, так и под X Window System (основа графических оболочек в Linux). Сообщения Windows специфичны для этой платформы и не могут быть взяты за основу при построении кроссплатформенной библиотеки.
Windows-программисты должны привыкнуть к отсутствию непосредственного взаимодействия с сообщениями Windows и научиться использовать механизмы библиотеки Qt.
В CLX эквивалентом сообщения Windows является событие Qt. В отличие от Windows, где сообщение хранится в формате записи (TMessage, или структура MSG в Windows API), содержащей идентификатор вида сообщения и два числовых параметра, в Qt событие – это объект. Существует базовый класс QEvent (и соответствующий класс QEventH), который имеет многочисленных наследников, таких как QKeyEvent и QMouseEvent.
При выполнении CLX-приложения на платформе Windows события Windows транслируются в события Qt, при работе с X Window транслируются системные события X events.
Для обработки сообщений в VCL используется специальный метод WndProc, которая разбирает сообщение и передает управление методам обработки сообщений. Данный метод объявлен как:
procedure WndProc(var Message: TMessage); virtual; |
Аналогом WndProc в CLX является EventFilter :
function EventFilter(Sender: QObjectH; Event: QEventH): Boolean; virtual; |
Чтобы определить тип события, переданного во втором параметре метода EventFilter, можно воспользоваться функцией QEvent_type. Далее, зная тип события и используя приведение типов, можно вызывать методы, специфические для данного класса событий.
В качестве примера ниже приведено небольшое приложение, позволяющее просматривать события, передаваемые компоненту. Для реализации этого нужно создать новый класс TButtonEx, унаследованный от класса кнопки TButton, изменить поведение метода EventFilter и добавить новое событие OnQtEvent.
type TQtEvent = procedure(Sender: TComponent; Widget: QObjectH; Event: QEventH) of object; TButtonEx = class(TButton) private FOnQtEvent: TQtEvent; protected function EventFilter(Sender: QObjectH; Event: QEventH): Boolean; override; published property OnQtEvent: TQtEvent read FOnQtEvent write FOnQtEvent; end; ... function TButtonEx.EventFilter(Sender: QObjectH; Event: QEventH): Boolean; begin if Assigned(OnQtEvent) then OnQtEvent(Self, Sender, Event); //Return True if you want to hide the event Result := inherited EventFilter(Sender, Event) end; |
Скриншот программы приведен ниже:
Рисунок 1
Остается только создать тестовое приложение, на форму которого поместить кнопку типа TButtonEx и обработать ее событие OnQtEvent:
procedure TForm1.ButtonEx1QtEvent(Sender: TComponent; Widget: QObjectH; Event: QEventH); begin ListBox1.Items.Add(EventToStr(Event)); end; |
Функция EventToStr была взята мной из модуля EventTypes, автор Brian Long.
Переопределение виртуального метода EventFilter полезно при разработке новых классов, однако как же нам изменить поведение существующего класса? В VCL это делалось с помощью подмены WindowProc. Однако, в CLX по причине отсутствия оконной процедуры такой фокус не пройдет. Но не стоит расстраиваться. Вся проблема при подмене метода EventFilter состоит в том, что метод Object Pascal не может быть напрямую передан в Qt. Для решения данной проблемы были введены перехватчики (hooks). Если требуется установить новый EventFilter, необходимо передать новую процедуру объекту-перехватчику. Перехватчик переопределяет C++-фильтр событий, передавая управление методу Object Pascal. Так же, как и для opaque reference, для перехватчиков в CLX описана иерархия классов.
type QObject_hookH = class(TObject) end; QApplication_hookH = class(QObject_hookH) end; QWidget_hookH = class(QObject_hookH) end; QButton_hookH = class(QWidget_hookH) end; QPushButton_hookH = class(QButton_hookH) end; QComboBox_hookH = class(QWidget_hookH) end; QFrame_hookH = class(QWidget_hookH) end; QTableView_hookH = class(QFrame_hookH) end; QMultiLineEdit_hookH = class(QTableView_hookH) end; |
Для работы с перехватчиками определены flat-методы, главным из которых является метод Qt_hook_hook_events, позволяющий переопределить метод фильтрации событий. Вот декларация этого метода:
type //from QControls.pas TEventFilterMethod = function (Sender: QObjectH; Event: QEventH): Boolean of object cdecl; ... //from Qt.pas QHookH = TMethod; procedure Qt_hook_hook_events(handle: QObject_hookH; hook: QHookH); cdecl; |
Новый фильтр имеет следующую декларацию:
function <Имя метода>(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
|
Ниже приведен участок кода, устанавливающий новый фильтр для кнопки Button1:
type TForm1 = class(TForm) ... private FBtnHook: QButton_hookH; function NewBtnEventFilter(Sender: QObjectH; Event: QEventH): Boolean; cdecl; public procedure SetNewBtnEventFilter(Enable: Boolean); end; ... procedure TForm1.SetNewBtnEventFilter(Enable: Boolean); var Method: TMethod; begin if Enabled then begin FBtnHook := QButton_hook_create(Button1.Handle); TEventFilterMethod(Method) := NewBtnEventFilter; Qt_hook_hook_events(FBtnHook, Method); end else FBtnHook.Free end; |
В данном коде явно создается объект-перехватчик, однако наследники TWidgetControl делают это автоматически в процедуре CreateWidget и хранят перехватчик в protected свойстве Hooks.
Библиотека CLX обзавелась интересной возможностью менять внешний вид визуальных компонентов. Набор характеристик визуального отображения визуального элемента называется стилем. Кроме визуального элемента, стиль может присвоить и объекту Application, т.е всему приложению в целом. Поддерживаются несколько стилей:
TDefaultStyle = (dsWindows, dsMotif, dsMotifPlus, dsCDE, dsQtSGI, dsPlatinum, dsSystemDefault); |
Чтобы опробовать эту возможность, создайте новое CLX-приложение. На главной форме этого приложения разместите ListBox (для отображения списка стилей), установив его свойство Align равным alLeft. На свободном месте формы можно разместить различные визуальные компоненты. Далее в обработчик события FormCreate добавьте код, заполняющий ListBox списком доступных стилей:
procedure TForm1.FormCreate(Sender: TObject); var Cnt:integer; begin for Cnt:=0 to Length(cStyles)-1 do ListBox1.Items.Add(#39+cStyles[TDefaultStyle(Cnt)]+#39); end; |
Осталось обработать двойной клик на элементе ListBox, чтобы пользователь мог сменить стиль приложения:
procedure TForm1.ListBox1DblClick(Sender: TObject); begin Application.Style.DefaultStyle:=TDefaultStyle(ListBox1.ItemIndex) end; |
К сожалению, у визуальных компонентов свойство Style объявлено в разделе protected. Однако с помощью нехитрых манипуляций это можно обойти. Для этого необходимо объявить класс-наследник TWidgetControl и перенести свойство Style в секцию public. Далее с помощью приведения типов можно менять стиль для каждого компонента индивидуально.
Сообщений 0 Оценка 0 [+1/-0] Оценить |