Главная » Delphi » Анимация титров

0

Итак, визуальные аспекты работы компонента TddgMarquee реализованы. Для то го чтобы  этот  компонент заработал, осталось сделать  совсем  немного. Следует разра ботать  механизм, изменяющий значение CurrLine во времени и запускающий в нуж ный момент перерисовку компонента. Этого  легко добиться, используя компонент Delphi  TTimer.

Естественно, перед  тем как использовать компонент TTimer, необходимо создать

и инициализировать экземпляр этого класса. Компонент TddgMarquee будет обладать

собственным экземпляром класса TTimer по имени FTimer, который инициализиру 

ется в процедуре DoTimer:procedure DoTimer;

{ Процедура установки таймера компонента TddgMarquee }

begin

FTimer := TTimer.Create(Self);

with FTimer do begin

Enabled := False;

Interval := TimerInterval;

OnTimer := DoTimerOnTimer;

end;

end;

В этой  процедуре сначала  создается неактивный объект FTimer. Его свойству  In- terval присваивается  значение  константы  TimerInterval. Затем   событию  On- Timer объекта FTimer присваивается метод  DoTimerOnTimer класса  TddgMarquee. Этот метод будет вызываться при наступлении каждого  события OnTimer.

НА ЗАМЕТКУ

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

•                 Процедура, которая присваивается событию, должна быть методом некоторого объекта

(экземпляра). Это не может быть самостоятельная процедура или функция.

•     Метод, который присваивают событию, должен иметь тот же список параметров, что и тип события. Например, событие OnTimer компонента TTimer имеет тип TNoti- fyEvent.  Поскольку  у  TNotifyEvent —  всего  один  параметр  (Sender)  типа TObject, то любой метод, присваиваемый событию OnTimer, должен иметь один па- раметр типа TObject.

Метод DoTimerOnTimer() определяется следующим образом:

procedure TddgMarquee.DoTimerOnTimer(Sender: TObject);

{ Этот метод выполняется в ответ на событие таймера. }

begin

IncLine;

{ Перерисовка только внутри границ }

InvalidateRect(Handle, @InsideRect, False);

end;

В этом методе вызывается процедура IncLine(), увеличивающая или уменьшающая значение CurrLine. Затем  функция API InvalidateRect() вызывается для перерисов ки (invalidate или repaint) внутренней части  компонента. Авторы  предпочли использо вать функцию InvalidateRect() вместо  метода  Invalidate() класса  TCanvas, по скольку последний приводит к перерисовке всего  компонента, а эта функция — только его части.  Это  позволяет избавиться от неприятного  мерцания (flicker), связанного с перерисовкой всего окна компонента. Запомните: мерцания следует избегать!

Исходный  код  метода  IncLine(),  обновляющего  значение  CurrLine и  опреде

ляющего конец  прокрутки, приводится ниже.

procedure TddgMarquee.IncLine;

{ Этот метод вызывается для приращения индекса текущей строки. }

begin

if not FScrollDown then begin // если титры прокручиваются вверх

{ Проверка конца прокрутки. }if FItems.Count * LineHi + ClientRect.Bottom – ScrollPixels >= CurrLine then

{ Если еще не конец, то приращение индекса текущей строки. } Inc(CurrLine, ScrollPixels)

else SetActive(False);

end

else begin                       // если титры прокручиваются вниз

{ Проверка конца прокрутки. }

if CurrLine >= ScrollPixels then

{ Если еще не конец, то уменьшение индекса текущей строки. }

Dec(CurrLine, ScrollPixels)

else SetActive(False);

end;

end;

Конструктор класса  TddgMarquee довольно прост.  Он  вызывает унаследованный метод  Create(), создает  экземпляр класса  TStringList, устанавливает таймер FTimer и значения по умолчанию для переменных экземпляра. Еще раз напоминаем о необходимости использовать в компонентах унаследованный конструктор Create(). Без него  компоненты будут лишены таких  немаловажных элементов, как дескриптор и свойство Canvas, возможности работы с потоками данных  и взаимодействия с со общениями Windows. Вот исходный код конструктора Create() класса TddMarquee:

constructor TddgMarquee.Create(AOwner: TComponent);

{ Конструктор класса TddgMarquee }

procedure DoTimer;

{ Процедура установки таймера для класса TddgMarquee }

begin

FTimer := TTimer.Create(Self);

with FTimer do begin

Enabled := False;

Interval := TimerInterval;

OnTimer := DoTimerOnTimer;

end;

end;

begin

inherited Create(AOwner);

{ Создание экземпляра списка строк }

FItems := TStringList.Create;

DoTimer;                          // Установка таймера

{ Установка значений по умолчанию для экземпляра }

Width := 100;

Height := 75;

FActive := False;

FScrollDown := False;

FJust := tjCenter;

BevelWidth := 3;

end;Деструктор TddgMarquee еще  проще:  он  дезактивирует компонент, передавая в метод  SetActive() параметр False, освобождает память  занимаемую таймером и списком строк, а затем вызывает унаследованный метод Destroy():

destructor TddgMarquee.Destroy;

{ Деструктор класса TddgMarquee }

begin

SetActive(False);

FTimer.Free;          // Освобождение памяти,

FItems.Free;          // выделенной для объектов.

inherited Destroy;

end;

CОВЕТ

Советуем придерживаться правила, согласно которому при переопределении конст- рукторов унаследованный конструктор вызывается в начале, а при переопределении деструкторов унаследованный деструктор вызывается в самом конце. Это гарантиру- ет, что класс будет создан до его модификации и все зависимые ресурсы будут осво- бождены до освобождения класса.

Естественно, из данного правила есть исключения, но для этого нужны достаточно веские причины.

Метод  SetActive(), служащий  методом  записи свойства Active и вызываемый методом IncLine() и деструктором, является механизмом, обеспечивающим начало и останов прокрутки титров:

procedure TddgMarquee.SetActive(Value: Boolean);

{ Вызывается для активации/дезактивации прокрутки титров }

begin

if Value and (not FActive) and (FItems.Count > 0) then begin

FActive := True;                // Установка флага активности

MemBitmap := TBitmap.Create;

FillBitmap;                     // Прорисовка растрового изображения

FTimer.Enabled := True;         // Запуск таймера

end

else if (not Value) and FActive then begin

FTimer.Enabled := False; // Отключить таймер,

if Assigned(FOnDone) then

FOnDone(Self);                 // передать событие OnDone,

FActive := False;              // установить FActive в False,

MemBitmap.Free;                // освободить память изображения,

Invalidate;                    // очистить окно элемента управления.

end;

end;

У компонента TddgMarquee пока  отсутствует важное  событие, которое сооб щало  бы пользователю о конце  прокрутки. Ничего страшного, это  событие FOn- Done легко  добавить в наш  компонент. Вначале  будет объявлена переменная эк земпляра события некоторого типа  в разделе private определения класса. Собы тие FOnDone будет иметь  тип TNotifyEvent:

FOnDone: TNotifyEvent;Затем  это  событие следует  объявить в качестве свойства в разделе published

определения класса:

property OnDone: TNotifyEvent read FOnDone write FOnDone;

Вспомним, что директивы read и write здесь определяют функции или перемен

ные для установки и получения значения свойства.

Этих строк  кода достаточно, чтобы  свойство OnDone появилось во вкладке  Events окна инспектора объектов во время  разработки. Осталось лишь вызвать пользова тельский обработчик события OnDone (как  метод,  назначенный событию OnDone), что и делается в коде метода Deactivate() компонента TddgMarquee:

if Assigned(FOnDone) then FOnDone(Self); //Передача события OnDone

Данную  строку  нужно  понимать следующим  образом: “Если  пользователь компо нента  назначил событию OnDone какой то метод,  то именно этот  метод  и нужно  вы звать,  передав ему текущий  экземпляр класса  TddgMarquee (т.е.  Self) в качестве па раметра”.

В листинге 12.2  приведен  полный  исходный код  модуля  Marquee. Обратите внимание, поскольку  рассматриваемый компонент является потомком класса TCustomXXX,  большинство  свойств  компонента  TCustomPanel необходимо  сде лать публикуемыми.

Листинг 12.2. Marquee.pas — компонент TddgMarquee

unit Marquee;

interface uses

SysUtils, Windows, Classes, Forms, Controls, Graphics,

Messages, ExtCtrls, Dialogs;

const

ScrollPixels = 3;        // Количество пикселей для каждой прокрутки

TimerInterval = 50; // Время между прокрутками в м/с

type

TJustification = (tjCenter, tjLeft, tjRight);

EMarqueeError = class(Exception); TddgMarquee = class(TCustomPanel)

private

MemBitmap: TBitmap;

InsideRect: TRect;

FItems: TStringList;

FJust: TJustification;

FScrollDown: Boolean;

LineHi : Integer;

CurrLine : Integer;

VRect: TRect;FTimer: TTimer; FActive: Boolean; FOnDone: TNotifyEvent;

procedure SetItems(Value: TStringList); procedure DoTimerOnTimer(Sender: TObject); procedure PaintLine(R: TRect; LineNum: Integer); procedure SetLineHeight;

procedure SetStartLine;

procedure IncLine;

procedure SetActive(Value: Boolean);

protected

procedure Paint; override;

procedure FillBitmap; virtual;

public

property Active: Boolean read FActive write SetActive;

constructor Create(AOwner: TComponent); override;

destructor Destroy; override;

published

property ScrollDown: Boolean read FScrollDown

write FScrollDown;

property Justify: TJustification read Fjust

write FJust default tjCenter;

property Items: TStringList read FItems write SetItems;

property OnDone: TNotifyEvent read FOnDone write FOnDone;

{ Публикация унаследованных свойства: }

property Align;

property Alignment;

property BevelInner;

property BevelOuter;

property BevelWidth;

property BorderWidth;

property BorderStyle;

property Color;

property Ctl3D;

property Font;

property Locked;

property ParentColor;

property ParentCtl3D;

property ParentFont;

property Visible;

property OnClick;

property OnDblClick;

property OnMouseDown;

property OnMouseMove;

property OnMouseUp;

property OnResize;

end;

implementation

constructor TddgMarquee.Create(AOwner: TComponent);

{ Конструктор класса TddgMarquee }procedure DoTimer;

{ Процедура установки таймера для класса TddgMarquee }

begin

FTimer := TTimer.Create(Self);

with FTimer do begin

Enabled := False;

Interval := TimerInterval;

OnTimer := DoTimerOnTimer;

end;

end;

begin

inherited Create(AOwner);

{ Создание экземпляра списка строк }

FItems := TStringList.Create;

DoTimer;                           // Установка таймера

{ Установка значений по умолчанию для экземпляра }

Width := 100;

Height := 75;

FActive := False;

FScrollDown := False;

FJust := tjCenter;

BevelWidth := 3;

end;

destructor TddgMarquee.Destroy;

{ Деструктор класса TddgMarquee }

begin

SetActive(False);

FTimer.Free;          // Освобождение памяти,

FItems.Free;          // выделенной для объектов.

inherited Destroy;

end;

procedure TddgMarquee.DoTimerOnTimer(Sender: TObject);

{ Этот метод выполняется в ответ на событие таймера. }

begin

IncLine;

{ Перерисовка только внутри границ. }

InvalidateRect(Handle, @InsideRect, False);

end;

procedure TddgMarquee.IncLine;

{ Этот метод вызывается для приращения индекса текущей строки. }

begin

if not FScrollDown then begin // если титры прокручиваются вверх

{ Проверка конца прокрутки. }

if FItems.Count * LineHi + ClientRect.Bottom -

ScrollPixels                                  >= CurrLine then

{ Если еще не конец, то приращение индекса текущей строки. }

Inc(CurrLine, ScrollPixels)

else SetActive(False);

endelse begin                    // если титры прокручиваются вниз

{ Проверка конца прокрутки. }

if CurrLine >= ScrollPixels then

{ Если еще не конец, то уменьшение индекса текущей строки. }

Dec(CurrLine, ScrollPixels)

else SetActive(False);

end;

end;

procedure TddgMarquee.SetItems(Value: TStringList);

begin

if FItems <> Value then

FItems.Assign(Value);

end;

procedure TddgMarquee.SetLineHeight;

{ Этот виртуальный метод устанавливает переменную экземпляра

LineHi. }

var

Metrics: TTextMetric;

begin

{ Получить размеры шрифта }

GetTextMetrics(Canvas.Handle, Metrics);

{ Вычисление высоты строки }

LineHi := Metrics.tmHeight + Metrics.tmInternalLeading;

end;

procedure TddgMarquee.SetStartLine;

{ Этот виртуальный метод инициализирует переменную CurrLine

экземпляра. }

begin

// Инициализация текущей строки в начало списка, если прокрутка

// вверх…

if not FScrollDown then CurrLine := 0

// …или в конец списка, если прокрутка вниз.

else CurrLine := VRect.Bottom – Height;

end;

procedure TddgMarquee.PaintLine(R: TRect; LineNum: Integer);

{ Этот метод помещает строки текста в область памяти MemBitmap. }

const

Flags: array[TJustification] of DWORD = (DT_CENTER,

DT_LEFT, DT_RIGHT);

var

S: string;

begin

{ Упростим код, скопировав очередную строку в локальную

переменную. }

S := FItems.Strings[LineNum];

{ Прорисовать строку текста в изображение, размещенное в памяти}

DrawText(MemBitmap.Canvas.Handle, PChar(S), Length(S), R,

Flags[FJust] or DT_SINGLELINE or DT_TOP);

end;

procedure TddgMarquee.FillBitmap;

var

y, i : Integer;

R: TRect;

begin

SetLineHeight;                     // Установка высоты каждой строки

{ Прямоугольник Vrect – это растровое изображение в памяти }

VRect := Rect(0, 0, Width, LineHi * FItems.Count + Height * 2);

{ Прямоугольник InsideRect представляет собой внутреннюю часть

прямоугольной области. }

InsideRect := Rect(BevelWidth, BevelWidth,

Width – (2 * BevelWidth),

Height – (2 * BevelWidth));

R := Rect(InsideRect.Left, 0, InsideRect.Right, VRect.Bottom);

SetStartLine;

MemBitmap.Width := Width;      // Инициализация изображения в памяти

with MemBitmap do begin

Height := VRect.Bottom;

with Canvas do begin

Font := Self.Font;

Brush.Color := Color;

FillRect(VRect);

Brush.Style := bsClear;

end;

end;

y := Height;

i := 0;

repeat

R.Top := y;

PaintLine(R, i);

{ Увеличить y на высоту строки (в пикселях). }

inc(y, LineHi);

inc(i);

until i >= FItems.Count;         // повторить для всех строк

end;

procedure TddgMarquee.Paint;

{ Этот виртуальный метод вызывается в ответ на сообщение Windows о

необходимости перерисовки. }

begin

if FActive then                  // Копировать из памяти на экран

BitBlt(Canvas.Handle, 0, 0, InsideRect.Right,

InsideRect.Bottom, MemBitmap.Canvas.Handle,

0, CurrLine, srcCopy)

else

inherited Paint;

end;

procedure TddgMarquee.SetActive(Value: Boolean);

{ Вызывается для активации/дезактивации прокрутки титров. }

begin

if Value and (not FActive) and (FItems.Count > 0) then beginFActive := True;   // Установка флага активности

MemBitmap := TBitmap.Create;

FillBitmap;                     // Прорисовка растрового изображения

FTimer.Enabled := True;         // Запуск таймера

end

else if (not Value) and FActive then begin

FTimer.Enabled := False; // Отключить таймер,

if Assigned(FOnDone) then

FOnDone(Self);                 // передать событие OnDone,

FActive := False;              // установить FActive в False,

MemBitmap.Free;                // освободить память изображения,

Invalidate;                    // очистить окно элемента управления.

end;

end;

end.CОВЕТ

Обратите внимание на использование директивы default со свойством Justify ком- понента TddgMarquee. Использование директивы default оптимизирует работу компо- нента с потоками данных, что повышает производительность во время разработки. Зна- чения по умолчанию можно создавать для свойств любого упорядоченного типа (Integer, Word, Longint, а также, например, для перечислимых типов), но для свойств неупорядоченных типов, таких как строки, числа с плавающей точкой, массивы, записи и классы, значения по умолчанию не могут быть созданы.

Кроме того, инициализировать значения по умолчанию для свойств необходимо в конструк-

торе. В противном случае возможны проблемы при работе с потоками данных.

Источник: Тейксейра, Стив, Пачеко, Ксавье.   Borland Delphi 6. Руководство разработчика. : Пер.  с англ. — М. : Издательский дом “Вильямс”, 2002. —  1120 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии