Теоретический материал по элементам управления (виджетам)

Интерфейсные элементы GUI носят название "элемент управления" или "виджет" (widget). Современные GUI-библиотеки (win32, qt, gtk и т.п.) предлагают достаточно большой набор разнообразных виджетов (окна, меню, кнопки, метки и т.п.) и обеспечивают средства управления ими. Часто управление виджетами основано на объектно-ориентированной парадигме, когда каждый виджет является экземпляром того или иного класса, при этом базовый класс иерархии имеет достаточный набор абстрактных методов для управления виджетами. Другой подход предлагает использовать дескрипторы графических виджетов и позволяет управлять ими с помощью набора графических функций. При любом подходе GUI-система реализует обработку внешних событий, обеспечивает передачу событий виджетам, организует отрисовку виджетов на экране и т.п. Для виджетов поддерживается отношение владения (когда один виджет владеет другими) и отношение визуализации (когда один виджет визуализируется в рамках другого). При этом часто поддерживается механизм нотификации (предупреждения) виджета-хозяина (родителя) об уничтожении или изменении содержимого вложенного виджета. Данный механизм используется, в частности, для организации перерисовки вложенного виджета.

Базовый класс виджета

Базовый класс виджета должен инкпсулировать все необходимые для манипуляций с виджетом свойства и методы. Например:

type
 TGWidget=class;
 TGNotifyEvent=procedure (Sender:TGWidget) of object;
 TGMouseClickEvent=procedure (Sender:TGWidget; x,y:integer) of object;

 { TGWidget }

 TGWidget=class
   private
     Fheight: integer;
     Fleft: integer;
     fOnClick: TGMouseClickEvent;
     Ftop: integer;
     Fwidth: integer;
     procedure Setheight(AValue: integer);
     procedure Setleft(AValue: integer);
     procedure Settop(AValue: integer);
     procedure Setwidth(AValue: integer);
   protected
     canvas:tcanvas; // холст для прорисовки виджета
     notifyredraw:tgnotifyevent;// callback для предупреждения родителя о
                                // необходимости перерисовки виджета
   public
     constructor create(_canvas:tcanvas=nil; _notifyredraw:tgnotifyevent=nil;
                        _top:integer=100;    _left:integer=100;
                        _width:integer=100;  _height:integer=50);
      // перерисовка виджета
     procedure redraw(); virtual; abstract;
       // координаты виджета
     property top:integer read Ftop write Settop;
     property left:integer read Fleft write Setleft;
       // размеры виджета
     property width:integer read Fwidth write Setwidth;
     property height:integer read Fheight write Setheight;
     // реакция виджета на мышиный клик
     procedure mouseClick(x,y:integer); virtual;
     property onClick:TGMouseClickEvent read fOnClick write fOnClick;
      // далее - методы и свойства для реакции на различные типы событий
 end;

Экземпляры базового класса виджета не создаются, он является предком для виджетов других типов. В потомках базового класса виджета предусматривается перекрытие всех абстрактных методов предка с описанием соответствующей реакции на события (в реальных системах предлагается создавать множество промежуточных классов с обеспечением реакции по умолчанию на все события).

Рассматриваемый класс виджета поддерживает минимум из требуемого в GUI-системах функционала:

  1. Виджет умеет обрабатывать события в виде прямого вызова соответствующих функций (mouseClick и др.) и в виде определяемых пользователем функций-обработчиков (onClick и др.)
  2. Виджет умеет визуализировать свое содержимое.
  3. Виджет поддерживает отношение владения ЧАСТИЧНО. Владение виджетом обеспечивается на уровне рабочего стола, который моделируется с помощью формы (см. далее). Таким образом в данной системе один виджет НЕ МОЖЕТ владеть другими виджетам.
  4. Виджет поддерживает отношение визуализации ЧАСТИЧНО. Визуализация виджета реализуется на уровне рабочего стола, но не на уровне других виджетов. Таким образом в данной системе один виджет НЕ МОЖЕТ содержать в себе других виджетов.

Виджет-кнопка

В качестве примера рассмотрим описание класса TGClickButton. Данный класс представляет собой кнопку, которая может существовать в двух состояних - нажатом и отжатом.
TGClickButton=class(TGWidget)
   private
       frised: boolean;
       procedure setrised(AValue: boolean);
   protected
       procedure mouseClick(x,y:integer); override;
       procedure redraw(); override;
   public
       property rised:boolean read frised write setrised;
end;

Конструктор для данного класса не предусматривается, т.к. все необходимые инициализации уже реализованы в классе-предке, кроме состояния кнопки (по умолчанию rised=false - кнопка отжата). Конструктор может быть создан, если потребуется реализовать дополнительный функционал, либо изменить состояние кнопки по умолчанию.

При изменении состояния кнопки следует обеспечивать нотификацию родителя о данном факте, который впоследствии перерисует кнопку методом redraw.

В исходных файлах примера (см.ниже) отсутствует реализация методов класса TGClickButton (файл gclickbutton.inc). Cтуденту предлагается реализовать ее самостоятельно.

Рабочий стол

Рабочий стол предлагается реализовать на базе холста основной формы. Рабочий стол в данном случае будет являться "хозяином" и "родителем" всех виджетов.

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormPaint(Sender: TObject);
    destructor Destroy;override;
  private
   WGS:array of TGWidget;
  public
   procedure RedrawWidget(Sender:TGWidget);
   function addWidget(Widget:TGWidget):TGWidget;
   procedure clickOnButton(Sender:TGWidget; x,y:integer);
  end;
WGS - динамический массив для хранения всех виджетов.
Метод FormCreate (событие onCreate формы) обеспечивает инициализацию виджетов и их связь с формой.
Метод FormMouseUp (событие onMouseUp формы) обеспечивает передачу виджетам поступающих на форму кликов мыши.
Метод FormPaint (событие onPaint формы) обеспечивает перерисовку виджетов.
Метод RedrawWidget является функцией обратного вызова (callback) для виджетов и предназначен для информирования базовой формы об изменения состояния виджетов.
Метод addWidget используется для добавления виджета на форму.
Метод clickOnButton назначается обрабочиком события нажатия кнопки мыши для некоторых виджетов-кнопок
Деструктор Destroy уничтожает массив виджетов, предварительно уничтожая каждый виджет.