Собственные элементы интерфейса.

Часть 2

© 2009 Антон Петрушенков, Wiseman Softworks

Итак, продолжим начатое в статье “Собственные элементы интерфейса” обсуждение базового класса кнопки - нашего первого собственного элемента интерфейса.

Класс TCustomButton. Продолжаем разбор.

Не буду повторять здесь текст класса, дабы совсем уж не загромождать статью - кода ещё будет предостаточно. Обратитесь к предыдущей статье или к тексту исходников. В этой статье я, как и обещал, закончу разбор класса и опишу процесс “встраивания” собственных элементов в реальное приложение.

Разберём процедуры класса. Конструктору передаются две переменные. Они, как несложно догадаться, присваиваются полям ParentCanvas и PosRect соответственно. Там же в конструкторе указываем, что наша кнопка при создании не выделена и не нажата. В общем случае делать это не обязательно, т.к. false это значение по умолчанию любой переменной типа boolean. Однако, такой ортодоксальный подход часто себя окупает - потратив две строчки кода мы точно видим, что происходит с каждым полем нашего класса и знаем, что всё у нас под контролем :-) .

constructor TCustomButton.Create(Canvas: TCanvas; TheRect: TRect);
begin
  ParentCanvas := Canvas;
  PosRect := TheRect;
  Selected := False;
  Pressed := False;
end;

Следующие три процедуры служат для передвижения кнопки и динамического изменения её размеров. Все они изменяют поле PosRect, которое мы благоразумно поместили в private область нашего класса.

procedure TCustomButton.ChangeWidth(NewWidth: integer);
begin
  PosRect.Right := PosRect.Left + NewWidth;
end;

/////*******

procedure TCustomButton.ChangeHeight(NewHeight: integer);
begin
  PosRect.Bottom := PosRect.Top + NewHeight;
end;

/////*******

procedure TCustomButton.ChangePosition(NewLeft, NewTop: integer);
begin
  PosRect.Left := NewLeft;
  PosRect.Top := NewTop;
end;

Функция InRect служит для отслеживания попадания курсора в кнопку. В моём примере приведена простейшая её вариация - проверка попадания в прямоугольник PosRect. Однако, никто не мешает сделать её сложнее и правильно отслеживать попадание в круглую, треугольную, да вобщем-то любую кнопку.

function TCustomButton.InRect(X, Y: integer): boolean;
begin
  Result := False;
  if (X > PosRect.Left) AND (X < PosRect.Right) then
     if (Y > PosRect.Top) AND (Y < PosRect.Bottom) then
       Result := true;
end;

Процедура Draw самая интересная - в ней мы описываем внешний вид и поведение кнопки. В зависимости от текущего статуса кнопки (выделена? нажата?) мы рисуем в родительский Canvas, всё что хотим. Можно пользоваться стандартными средствами GUI, можно рисовать заранее загруженные в память bmp файлы, можно совмещать эти подходы. В моём примере я использовал GUI и не сильно морочился с дизайном - главное в данном случае - наглядность.

procedure TCustomButton.Draw;
begin
  if Pressed then
     begin
       ParentCanvas.Brush.Color := clRed;
       ParentCanvas.FillRect(PosRect);
       Exit;
     end;

  if Selected then
     begin
       ParentCanvas.Brush.Color := clGreen;
       ParentCanvas.FillRect(PosRect);
     end
  else
     begin
       ParentCanvas.Brush.Color := clGreen;
       ParentCanvas.Pen.Color := clGreen;
       ParentCanvas.Ellipse(PosRect);
     end;
end;

Класс TExampleForm. Собственные элементы в реальном приложении.

Чтобы наша кнопка правильно отображалась в реальном приложении нам надо настроить обработчики её “родителя” для связанных с ней событий. В нашем случае родителем кнопки является простая форма, в примере всё максимально просто. Посмотрим, с какими событиями мы будем работать.

TExampleForm = class(TForm)
   procedure FormCreate(Sender: TObject);
   procedure FormPaint(Sender: TObject);
   procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
   procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
   procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
end;

Далее мы подробно рассмотрим содержание и назначение каждого обработчика. В нашем примере для кнопки мы просто заводим глобальную переменную:

var
   ExampleForm: TExampleForm;
   CButton : TCustomButton;

Разумеется для реальных приложений этот подход применять не стоит. Как вариант, используйте глобальный объект-контейнер, который будет управлять иерархическими списками более мелких объектов… или ещё чего поинтереснее придумайте .

Конструктор формы максимально прост. Мы создаём объект CButton класса TCustomButton с нужными нам параметрами. Передаём объекту Canvas нашей формы, т.к. собираемся рисовать именно в него. Задаём размеры и положение кнопки. Заодно выставляем для формы свойство DoubleBuffered в true - это обеспечит нам отсутствие “мерцания” изображения при прямом выводе на форму.

procedure TExampleForm.FormCreate(Sender: TObject);
begin
  DoubleBuffered := true;
  CButton := TCustomButton.Create(Canvas, Rect(150,100,250,200));
end;

Обработчик события OnPaint посложнее. Перед каждой прорисовкой мы очищаем поверхность формы. Для этого пользуемся функцией InvalidateRect, выставив параметр bErase в true. В нашем примере мы очищаем всю форму, но при желании, можно это делать лишь частично.

После очистки мы рисуем нашу кнопку - выполняем ту саму процедуру TCustomButton.Draw. Если бы у нас было несколько кнопок, следовало бы вызвать процедуру Draw для каждой из них.

procedure TExampleForm.FormPaint(Sender: TObject);
  var R : TRect;
begin
  /// Cleaning the screen
  R := ClientRect;
  InvalidateRect(handle, @R, true);
  /// Drawing our button
  CButton.Draw;
end;

Событие OnMouseMove вызывается формой при движении над ней курсора мыши. При помощи функции InRect (описанной выше) мы отслеживаем положение курсора относительно кнопки и реагируем на наведение. Очень важно после каждого изменения статуса кнопки перерисовывать форму - иначе пользователь не увидит соответсвующей реакции.

procedure TExampleForm.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  CButton.Selected := CButton.InRect(X,Y);
  OnPaint(Self);
end;

Следующее событие OnMouseDown - в нём мы обрабатываем нажатия кнопок мыши над формой. В нашем примере, мы следим только за левой кнопкой, и если при нажатии курсор мыши над нашей кнопкой, мы изменяем статус кнопки и перерисовываем форму.

procedure TExampleForm.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
     begin
       CButton.Pressed := CButton.InRect(X,Y);
       onPaint(Self);
     end;
end;

В последнем событии OnMouseUp мы обрабатываем отпускание кнопки мыши над формой. Если кнопка до отпускания была нажата - мы выполняем действие связанное с этой кнопкой, изменяем статус и перерисовываем форму.

Повторюсь, в нашем примере всё максимально просто - мы не рассматриваем пример кнопки “запоминающей” нажатие, не рассматриваем кластеры кнопок и т.п. но все эти поведения очень просто реализовать пользуясь описанной технологией.

procedure TExampleForm.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if CButton.Pressed then
     begin
       /// Do something here when the button is released...
       /// ...
       /// Stop doing.
       CButton.Pressed := false;
       OnPaint(Self);
     end;
end;

Заключение.

Таким образом, мы видим, что создать собственную кнопку, задать её поведение и внешний вид - довольно просто. На всякий случай повторю исходный текст примера.

Я намереваюсь продолжить цикл статей, и опишу создание более сложных элементов интерфейса.

Если вы заинтересовались этой темой, или у вас есть вопросы - не стесняйтесь оставить комментарий. Я постараюсь помочь.

Copyright© 2009 Антон Петрушенков, Wiseman Softworks

2011123456789101112
2010123456789101112
2009123456789101112
2008123456789101112
2007123456789101112
2006123456789101112
2005123456789101112
2004123456789101112
2003123456789101112
2002123456789101112
2001123456789101112
2000123456789101112
1999123456789101112

Последние статьи
Литература