Сообщений 0    Оценка 0 [+1/-0]         Оценить  
Система Orphus

Жизнь и смерть кнопки в CLX

Автор: Михаил Голованов
Источник: RSDN Magazine #1
Опубликовано: 18.05.2002
Исправлено: 13.03.2005
Версия текста: 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 логически разбита на несколько частей, каждая из которых реализует какую-либо функциональность. Часть 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.

Жизнь и смерть кнопки в run time

Некоторые из ключевых методов, отвечающих за создание визуального компонента в 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.

 VCLCLX
СозданиеCreateHandle CreateWnd CreateParams CreateSubClass CreateWindowHandle CreateWindowExCreateHandle CreateWidget widget constructor InitWidget HookEvents
ОсвобождениеDestroyHandle DestroyWnd DestroyWindowHandle DestroyWindowDestroyHandle DestroyWidget WidgetDestroyed widget destructor

Механизм обработки событий в VCL и CLX.

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

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. Далее с помощью приведения типов можно менять стиль для каждого компонента индивидуально.


Эта статья опубликована в журнале RSDN Magazine #1. Информацию о журнале можно найти здесь
    Сообщений 0    Оценка 0 [+1/-0]         Оценить