sábado, 25 de agosto de 2007

Construção de Componentes II


Certa ocasião, o cliente solicitou que numa tela de consulta, fosse construído um temporizador. O objetivo era que, segundo um valor pré-configurado pelo usuário, o sistema, regularmente, desse um refresh na consulta. Essa funcionalidade deveria, através de algum componente visual, exibir o tempo restante até que disparasse a próxima atualização. Para isso, poderíamos usar uma barra de progressão ou simplesmente um Label.
Neste artigo vamos trabalhar num componente que possa atender esse tipo de requisito.


Dicas

Observar os conceitos discutidos no artigo sobre escopo de visibilidade. Isso é extremamente importante neste momento.
Dominar o conceito de polimorfismo do paradigma OO, também discutido em artigos anteriores (meus e do Felipe).
Relembrando, o que colocamos no artigo Construção de Componentes I, devemos construir um componente sempre a partir de um outro pré-existente, seja ele extendido direto da VCL ou extendido de outro construído por você (recursivamente). Uma consideração é extremamente importante: Qual a classe hierarquicamente mais alta (mais abstrata), possuidora das características essenciais do objeto que pretendo construir? Essa será a classe base, da qual herdará o seu componente. Quando você souber responder essa pergunta com precisão, estará um passo na frente para ser um bom construtor de componentes. Um pré-requisito fundamental para isso, é conhecer bem a VCL e ter sempre por perto uma boa referência para consultar. O Help do Delphi não pode faltar neste contexto (O Diagrama da VCL também é bem legal).
O que posso adiantar que invariavelmente, num universo de coisas mais simples, corriqueiras, o feijão com arroz do dia-a-dia, o componente que você pretende construir estará dentro de três grupos bases de classes:
  1- TWinControl:
Classe base para todo componente baseado em janela. Ou seja, a idéia de janelas que o
SO Windows transmite. As características mais marcantes dessa classe são:
1.1 Pode receber foco.
1.2 Obter mensagens do Windows.
1.3 Pode trabalhar com Handles de janelas. Em suma, você pode ficar moito próximo as
APIS do Windows. Finalizando, para trabalhar com essa categoria de classes você
deverá herdar seu componente de TCustomControl.

2- TGraphicControl:
Classe base para os componentes visuais que não possuem handle do Windows. Característica que
pode ser bastante preferível quando você deseja economizar recursos do SO. Em contraste com a
classe TWinControl , TGraphicControl não recebe foco, tão pouco pode trabalhar com mensagens do
Windows. Para você criar um componente gráfico desta categoria de classes deverá extender seu
componente de TCustomControl.

3 – TComponent:
Esta é a superclasse de todos os componentes. Inclusive os “controles” (“TGenecosControls”, digamos) .
Entretanto, quando o componente que você desejar criar não possuir características visuais essa classe
poderá ser ideal para atender sua necessidade. TComponent é a classe mais alta da VCL a possuir propriedades
(Properties). Nela surge a propriedade “Name”, por exemplo. Outro ponto importante é que nela surge o conceito
de Owner, cujo a importância falaremos um pouco adiante. A classe TComponent extende de TPersistent e apresentam
importantes características:
3.1 Integração com IDE:
A habilidade para serem adicionados em uma paleta de componentes da IDE.
Capaz de serem manipulados em tempo de projeto (design time).

3.2 Proprietariedade (Ownership):
A habilidade para administrar outros componentes. Se um componente A possui
o componente B, então A é responsável por destruir B, quando A é destruído.
Isso é muito importe e nos remete ao que foi discutido no artigo sobre o uso
do método “Free” ou “Destroy”. Lembre-se que quando estamos trabalhando com
uma variável cujo tipo é uma classe, obrigatoriamente estaremos alocando memória
dinamicamente. Portanto, somos responsáveis por gerenciar a liberação da mesma.

3.3 Streaming e Filing:
características de persistência herdadas de TPersistent.

3.4 Suporte a COM:
Podem ser convertidos em controles de ActiveX, objetos COM do Windows.
Os TComponents podem ser encapsulados por objetos COM.




Um Diagrama rezumido da VCL




Como no mundo real, os componentes existentes, em muitos casos, são objetos compostos por um conjunto de outros objetos. Da mesma forma, posso aproveitar essa idéia para montar o meu temporizador com display. O qual chamarei de ZNDisplayTimer. Sendo fiel a minha linha de raciocínio, vou compor o ZNDisplayTimer por dois componentes, inicialmente: Um Timer (TTimer), e um Label (TLabel).

Vou aproveitar este artigo para experimentarmos esse efeito na prática, o nosso componente será capaz de, em tempo de projeto exibir o tempo restante para ele executar uma ação.


Vamos ao que interessa .... Codificando

Inicie a construção do nosso novo componente, no menu do Delphi: Component ►New Component (Ou alternada mente digite “Alt ► C ► N”).





Vou preferir herdar o componente de TCustomLabel, porque a partir dessa classe eu posso decidir o que eu vou esconder (encapsular), e o que eu vou permitir acesso na que pretendo criar, a qual chamarei de TZNDisplayTimerUltimate. Neste caso específico, pretendo esconder a propriedade “Caption”. Minha finalidade com isso é não permitir que o programador que vier fazer uso deste componente não se confunda e atribua valor para essa propriedade. O que tornaria o comportamento do ZNDisplayTimer deficiente.



type
TZNDisplayTimerUltimate = class(TCustomLabel)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
property Align;
property Alignment;
property Color;
property Font;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Transparent;
property Visible;
end;

Das propriedades originais, as que pretendo expor são as digitadas no trecho de código acima: Align, Alignment, Color, Font, ParentColor, ParentFont, ParentShowHint, PopupMenu, ShowHint, Transparent, Visible. Digite conforme o exemplo acima, em seguida salve sua unit. Tudo que é declarado na seção “published” ficará listado no Object Inspector.

Além dessas propriedades, preciso de uma que armazene o valor que definirá o período entre a execução da ação desejada (essa ação inicialmente desejamos atualizar uma consulta). Denominarei essa propriedade de “ResetTimeValue”. Outra somente de leitura que indicará o valor corrente do tempo, cujo o nome será: “CurrentTimeValue”. Veja a seguir:


type
TZNDisplayTimerUltimate = class(TCustomLabel)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
property Align;
property Alignment;
property Color;
property Font;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Transparent;
property Visible;
(* Acrescente as linhas abaixo e pressione “Ctrl + Shift + C *)
property ResetTimeValue: Integer;
property CurrentTimeValue: Integer FCurrentTimeValue default 0;
end


O Delphi vai auto-completar a codificação das propriedades novas.

.
.
.
property ShowHint;
property Transparent;
property Visible;
property ResetTimeValue: Integer read FResetTimeValue write SetResetTimeValue;
property CurrentTimeValue: Integer read FCurrentTimeValue default 0;
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents('EstacaoZn', [TZNDisplayTimerUltimate]);
end;

{ TZNDisplayTimerUltimate }
procedure TZNDisplayTimerUltimate.SetResetTimeValue(const Value: Integer);
begin
FResetTimeValue := Value;
end;

Agora vou definir o componente interno possuidor da capacidade de ser um temporizador, um TTimer. Vou cuidar para que ele fique encapsulado porque, partindo do mesmo princípio que usei para esconder o caption do Label, não quero que meu componente Dilpay seja usado equivocadamente. Portanto, a fim de esconder, principalmente, o evento OnTimer (O qual terá um papel fundamental para o comportamento do ZNDisplayTimer), bem como as demais propriedades da classe TTimer, vou defini-lo na seção “protected”. Entretanto, isso demandará a codificação de uma interface para comunicação com as propriedades do Timer.
Esse encapsulamento é muito importante, pois somente mediante a manipulação do componente da forma que planejei, conseguirei garantir, em qualquer circunstancia, não importando o programador, o comportamento adequado para o nosso componente. Entenda que embora este componente tenha características de um temporizador, ou melhor, conforme especifiquei no inicio do artigo, seja de fato um, não quero deixar sombra de dúvida que ele não é, em essência, igual ao TTimer da VCL. Ele especializa um TTimer, e possui uma finalidade específica, bem diferente do seu progenitor. Veja o trecho abaixo:


type
TZNDisplayTimerUltimate = class(TCustomLabel)
private
{ Private declarations }
FResetTimeValue: Integer;
FCurrentTimeValue: Integer;
procedure SetResetTimeValue(const Value: Integer);
protected
FTimer: TTimer;
public
{ Public declarations }
published
property Align;
property Alignment;
property Color;
property Font;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Transparent;
property Visible;
property ResetTimeValue: Integer read FResetTimeValue write SetResetTimeValue;
property CurrentTimeValue: Integer read FCurrentTimeValue default 0;
(*As linhas abaixo implementam a interface com o componente FTimer. Após digitar pressione “Ctrl + Shift + C” *)
property EnabledTimer: Boolean read GetEnabledTimer write SetEnabledTimer default False;
property Interval: Integer read GetInterval write SetInterval;
end;


Neste momento, quero chamar a atenção para o fato de que, quem está construindo um componente deve arquitetar sua construção considerando a possibilidade desse componente ser extendido mais tarde. Ou seja, imaginar que seu componente poderá ser superclasse para um outro que o especializará. Não julgo que este exemplo, especificamente, constitua o cenário mais adequado para situação que descrevi. Todavia, mais uma vez sobre a premissa de uma ênfase didática, vou aproveitar e exemplificar aqui, como preparar sua classe para que ela seja herdada. Por isso, defini o campo FTimer, na “protected” e não na “private”. Visto que, isso o tornaria inacessível para o caso de um extensor. Logo, seguindo esta mesma linha de raciocínio, os métodos, “GetEnabledTimer” e “SetEnabledTimer” que fazem a interface para a propriedade “EnabledTimer”. Bem como, “GetInterval” e “SetInterval”, que fazem o mesmo para a propriedade “Interval”, serão definidos na seção “protected” e serão marcados como “virtual”, viabilizando assim a possibilidade de polimorfismo, no herdeiro. Se você pressionou “Ctrl + Shift + C” como recomendado acima, verá que o Delphi auto-completou a codificação do corpo dos métodos que citei na “implemantation”, sendo que suas assinaturas foram parar na “private”. Retire a assinatura dos métodos que citei (GetEnabledTimer e SetEnabledTimer, GetInterval e SetInterval) da seção “private” e transporte para a seção “propected” imediatamente abaixo da definição o campo “FTimer”. Em seguida marque-os com a diretiva “virtual”, conforme o trecho abaixo:




type
TZNDisplayTimerUltimate = class(TCustomLabel)
private FResetTimeValue: Integer;
FCurrentTimeValue: Integer;
procedure SetResetTimeValue(const Value: Integer);
{ Private declarations }
protected
FTimer: TTimer;
function GetEnabledTimer: Boolean; virtual;
function GetInterval: Integer; virtual;
procedure SetEnabledTimer(const Value: Boolean); virtual;
procedure SetInterval(const Value: Integer); virtual;
public
{ Public declarations }
published
property Align;
property Alignment;
property Color;
property Font;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Transparent;
property Visible;
property ResetTimeValue: Integer read FResetTimeValue write SetResetTimeValue;
property CurrentTimeValue: Integer read FCurrentTimeValue default 0;
property EnabledTimer: Boolean read GetEnabledTimer write SetEnabledTimer default False;
property Interval: Integer read GetInterval write SetInterval;
end;


Em seguida vamos codificar os corpo dos métodos que acabamos de criar.
1 - GetEnabledTimer

function TZNDisplayTimerUltimate.GetEnabledTimer: Boolean;
begin
Result := FTimer.Enabled;
end;


2 - GetInterval

function TZNDisplayTimerUltimate.GetInterval: Integer;
begin
Result := FTimer.Interval;
end;


3 - SetEnabledTimer

procedure TZNDisplayTimerUltimate.SetEnabledTimer(const Value: Boolean);
begin
FTimer.Enabled := Value;
end;


4 - SetInterval

procedure TZNDisplayTimerUltimate.SetInterval(const Value: Integer);
begin
FTimer.Interval := Value;
end;


Sobrescrevendo um Método – Override:
Por falar em polimorfismo, agora vamos especializar o método “constructor” da classe TCustomLabel, a fim de configuramos de maneira adequada a “TZNDisplayTimerUltimate”. Na seção “public” digite conforme ilustrado no trecho abaixo:

protected
procedure UpdateClock(Sender: TObject);
procedure UpdateCurrentTime(const Multiplicador: Integer);
public
(* Digite o construtor e pressione “Ctrl + Shift + C” *)
constructor Create(AOwner: TComponent); override;


Sobrescrevendo o construtor da classe ..... Após marcar o método “Create”, construtor, com a palavra chave “override”, vou codificar para instanciar o componente interno (temporizador) e setar os valores default.


constructor TZNDisplayTimerUltimate.Create(AOwner: TComponent);
begin
(* evocando o método do ancestral *)
inherited Create(AOwner);

(* Instanciando o componente interno*)
FTimer := TTimer.Create(Self);

(* Configurando a Classe com valores default *)
FTimer.Name := 'ZnDisplayTimer';
FTimer.Enabled := False;
FCurrentTimeValue := 0;
FResetTimeValue := 5;
end;


Codificando a funcionalidade do TZNDisplayTimerUltimate

O componente deverá possuir duas funcionalidades. Elas deverão ser sincronizadas e dependentes:
1° Ação- De tempos em tempos disparar uma ação, sendo que, o intervalo entre as ações deve ser constante podendo ser, dinamicamente, definido pelo usuário do sistema. Implementaremos ela em forma de evento e a propriedade que definirá esse intervalo é a “ResetTimeValue”.
2° Ação – Exibir o tempo restante para a próxima ação disparada. A propriedade “Caption” que vamos absorver da classe TCustomLabel.

Na seção “Type” vamos definir o evento que implementará a primeira ação do nosso componente.
Vejamos o código abaixo:


type
(* Primeiro codifique o evento *)
TExecuteEvent = procedure(Sender: TObject) of object;
TZNDisplayTimerUltimate = class(TCustomLabel)
private
FTimer: TTimer;
FResetTimeValue: Integer;
FCurrentTimeValue: Integer;
FResetMod: TResetMod;
.
.
.
constructor Create(AOwner: TComponent); override;
published
.
.
.
property Align;
property Alignment;
(*Em seguida, digite a propriedade aqui, assim ela seja listada no
Object Inspector *)
property OnExecute: TExecuteEvent read FOnExecute write FOnExecute;


Ao pressionar “Ctrl + Shift + C” será criado o campo privado “FOnExecute”, veja abaixo:


type
TExecuteEvent = procedure(Sender: TObject) of object;
TZNDisplayTimerUltimate = class(TCustomLabel)
private
FResetTimeValue: Integer;
FCurrentTimeValue: Integer;
FOnExecute: TExecuteEvent;
procedure SetResetTimeValue(const Value: Integer);

{ Private declarations }
protected
FTimer: TTimer;
.
.

O Evento “OnExecute” esta pronto, a primeira funcionalidade está implementada. Agora, vamos tratar da exibição e do sincronismo ente a contagem do tempo e a execução do evento.

type
TExecuteEvent = procedure(Sender: TObject) of object;
TZNDisplayTimerUltimate = class(TCustomLabel)
private
FResetTimeValue: Integer;
FCurrentTimeValue: Integer;
FOnExecute: TExecuteEvent;
procedure SetResetTimeValue(const Value: Integer);

protected
FTimer: TTimer;

function GetEnabledTimer: Boolean; virtual;
function GetInterval: Integer; virtual;
procedure SetEnabledTimer(const Value: Boolean); virtual;
procedure SetInterval(const Value: Integer); virtual;

procedure UpdateClock(Sender: TObject); virtual;
procedure UpdateCurrentTime(const Multiplicador: Integer); virtual;
public
constructor Create(AOwner: TComponent); override;
published
.
.
.


Na seção “protected” declarei dois procedimentos e os marquei com a diretiva virtual, preparando a classe para ser extendida. Mais uma vez o bom e velho “Ctrl + Shift + C” e vamos codificar cada um deles. Adicione na seção “uses” da “implementation” a unit “DateUtils”.

implementation

uses
DateUtils;

Por falar em uses, veja como deve estar a seção “uses” da “interface” .


interface

uses
SysUtils, Classes, Controls, StdCtrls, ExtCtrls;
.
.
.

Codificando o método UpdateCurrentTime.


procedure TZNDisplayTimerUltimate.UpdateCurrentTime(
const Multiplicador: Integer);
var
AuxSecond: Integer;
begin
AuxSecond := SecondOf(Time);
if ((AuxSecond * Multiplicador) mod FResetTimeValue = 0) then
FCurrentTimeValue := 1
else
FCurrentTimeValue := FCurrentTimeValue + 1;
end;

FCurrentTimeValue indica o valor do tempo corrente. O método acima incrementa o campo ou reseta seu valor caso ele atinja o limite determinado para isso. Ou seja, a cada intervalo determinado no campo FResetTimeValue o campo FCurrentTimeValue é resetado, recebendo valor 1. Enquanto isso não acontece ele é incrementado. O Parâmetro “Multiplicador” serve para flexibilizar nosso componente, de maneira que ele seja capaz de trabalhar com a ordem de grandeza segundo, minuto, ou hora. Basta para isso variar o valor do multiplicador entre: 1 para segundo, 60 para minuto, 360 para hora.
No método seguinte faremos a chamada ao “UpdateCurrentTime”, atualizaremos o “Caption” para assim exibir o valor de “FCurrentTimeValue” em seguida. A cada vez que ele for resetado dispararemos o evento “OnExecute”.

procedure TZNDisplayTimerUltimate.UpdateClock(Sender: TObject);
begin
(*Atualizando o tempo corrente*)
UpdateCurrentTime(1);
(*Atribuindo o valor atualizado na propriedade de output do componente*)
Self.Caption := IntToStr(FCurrentTimeValue);

(*Caso o campo tenha seido resetado dispara o evento OnExecute*)
if (FCurrentTimeValue = 1) then
if Assigned(FOnExecute) then FOnExecute(Self);
end;



Agora voltaremos ao construtor para a última configuração.


constructor TZNDisplayTimerUltimate.Create(AOwner: TComponent);
begin
(* evocando o método do ancestral *)
inherited Create(AOwner);

(* Instanciando o componente interno*)
FTimer := TTimer.Create(Self);

(* Configurando a Classe com valores default *)
FTimer.Name := 'ZnDisplayTimer';
FTimer.Enabled := False;
FCurrentTimeValue := 0;
FResetTimeValue := 5;
(*Ao evento onTimer atribuimos o método que possui
a principal funcionalidade do nosso componente*)
FTimer.OnTimer := UpdateClock;

end;


Agora que nosso componente está pronto o adicione a package que criamos no artigo anterior sobre construção de componentes. Compile a package e re-instale.



Clik em "Browse..." para selecionar a unit do seu component (ZNDisplayTimerUltimate.pas), em seguida click "OK"



É chegado o momento ... vamos fazer um pequeno programa para testarmos nossa criação.




Testando o componente que criamos

Inicie uma nova aplicação, antes de qualquer coisa feche o projeto da package. No menu do Delphi: File ►Close All. Em seguida, File ► New ►Application.

Adicione o componente ZNDisplayTimerUltimate



Configure a propriedade fonte do ZNDisplayTimerUltimate para um tamanho maior e set sua propriedade “Enabled”, no Object Inspector para True.
e um BitBtn (palheta additional).



No Evento OnExecute do ZNDisplayTimerUltimate




digite .....


procedure TForm1.ZNDisplayTimerUltimate1Execute(Sender: TObject);
begin
ShowMessage('Bla!!!! ... Estação ZN ... Boladão')
end;


Adicione um SpinEdit (palheta Samples), no evento OnCreate do Form1 atribua valor para ele.

procedure TForm1.FormCreate(Sender: TObject);
begin
(*Vamos iniciar a aplicação com o Display desativado*)
ZNDisplayTimerUltimate1.EnabledTimer := False;
(*setando um valor inicial para o SpinEdit1*)
SpinEdit1.Value := 7;
end;


No evento OnClick do BitBtn1 vamos ativar o componente em tempo de execução.


procedure TForm1.BitBtn1Click(Sender: TObject);
begin
( * definindo um novo valor para o intervalo *)
ZNDisplayTimerUltimate1.ResetTimeValue := SpinEdit1.Value;

(*Controlando o ZNDisplay ...*)
ZNDisplayTimerUltimate1.EnabledTimer :=
not (ZNDisplayTimerUltimate1.EnabledTimer);
end;


Execute a aplicação e teste .....
Segue o código completo da aplicação exemplo.

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, ZNDisplayTimerUltimate, Spin;

type
TForm1 = class(TForm)
ZNDisplayTimerUltimate1: TZNDisplayTimerUltimate;
BitBtn1: TBitBtn;
SpinEdit1: TSpinEdit;
procedure ZNDisplayTimerUltimate1Execute(Sender: TObject);
procedure BitBtn1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ZNDisplayTimerUltimate1Execute(Sender: TObject);
begin
ShowMessage('Bla!!!! ... Estação ZN ... Boladão')
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
ZNDisplayTimerUltimate1.ResetTimeValue := SpinEdit1.Value;
ZNDisplayTimerUltimate1.EnabledTimer :=
not (ZNDisplayTimerUltimate1.EnabledTimer);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
(*Vamos iniciar a aplicação com o Display desativado*)
ZNDisplayTimerUltimate1.EnabledTimer := False;
(*setando um valor inicial para o SpinEdit1*)
SpinEdit1.Value := 7;
end;

end.


O Código completo do componente ...

unit ZNDisplayTimerUltimate;

interface

uses
SysUtils, Classes, Controls, StdCtrls, ExtCtrls;

type
TExecuteEvent = procedure(Sender: TObject) of object;
TZNDisplayTimerUltimate = class(TCustomLabel)
private
FResetTimeValue: Integer;
FCurrentTimeValue: Integer;
FOnExecute: TExecuteEvent;
procedure SetResetTimeValue(const Value: Integer);

{ Private declarations }
protected
FTimer: TTimer;

function GetEnabledTimer: Boolean; virtual;
function GetInterval: Integer; virtual;
procedure SetEnabledTimer(const Value: Boolean); virtual;
procedure SetInterval(const Value: Integer); virtual;

procedure UpdateClock(Sender: TObject); virtual;
procedure UpdateCurrentTime(const Multiplicador: Integer); virtual;
public
constructor Create(AOwner: TComponent); override;

published
property Align;
property Alignment;
property Color;
property Font;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Transparent;
property Visible;
property ResetTimeValue: Integer read FResetTimeValue write SetResetTimeValue;
property CurrentTimeValue: Integer read FCurrentTimeValue default 0;
property EnabledTimer: Boolean read GetEnabledTimer write SetEnabledTimer default False;
property Interval: Integer read GetInterval write SetInterval;
property OnExecute: TExecuteEvent read FOnExecute write FOnExecute;
end;

procedure Register;

implementation

uses
DateUtils;
procedure Register;
begin
RegisterComponents('EstacaoZn', [TZNDisplayTimerUltimate]);
end;

{ TZNDisplayTimerUltimate }


constructor TZNDisplayTimerUltimate.Create(AOwner: TComponent);
begin
(* evocando o método do ancestral *)
inherited Create(AOwner);

(* Instanciando o componente interno*)
FTimer := TTimer.Create(Self);

(* Configurando a Classe com valores default *)
FTimer.Name := 'ZnDisplayTimer';
FTimer.Enabled := False;
FCurrentTimeValue := 0;
FResetTimeValue := 5;
(*Ao evento onTimer atribuimos o método que possui
a principal funcionalidade do nosso componente*)
FTimer.OnTimer := UpdateClock;

end;

function TZNDisplayTimerUltimate.GetEnabledTimer: Boolean;
begin
Result := FTimer.Enabled;
end;

function TZNDisplayTimerUltimate.GetInterval: Integer;
begin
Result := FTimer.Interval;
end;

procedure TZNDisplayTimerUltimate.SetEnabledTimer(const Value: Boolean);
begin
FTimer.Enabled := Value;
end;

procedure TZNDisplayTimerUltimate.SetInterval(const Value: Integer);
begin
FTimer.Interval := Value;
end;

procedure TZNDisplayTimerUltimate.SetResetTimeValue(const Value: Integer);
begin
FResetTimeValue := Value;
end;

procedure TZNDisplayTimerUltimate.UpdateClock(Sender: TObject);
begin
UpdateCurrentTime(1);
Self.Caption := IntToStr(FCurrentTimeValue);

if FCurrentTimeValue = 1 then
if Assigned(FOnExecute) then FOnExecute(Self);
end;

procedure TZNDisplayTimerUltimate.UpdateCurrentTime(
const Multiplicador: Integer);
var
AuxSecond: Integer;
begin
AuxSecond := SecondOf(Time);
if ((AuxSecond * Multiplicador) mod FResetTimeValue = 0) then
FCurrentTimeValue := 1
else
FCurrentTimeValue := FCurrentTimeValue + 1;

end;

end.

Você pode pesquisar mais sobre o assunto em:
Downloads nos artigos do Bruno (Cod Gear):

Mini Book About Components' Creation: http://cc.borland.com/Item/24302


Apostila sobre Criação de Componentes Passo a Passo by Bruno Lichot:
Apostila sobre Criação de Componentes Passo a Passo com exemplos e código fonte inclusos.
Mostra como Criar Compoenentes Simples e Componantes DBWare para Banco de Dados.

e-mail: bruno.lichot@gmail.com
lista: nddv@yahoogrupos.com.br

Atenciosamente,

Bruno Lichot
CodeGear Team Brazil




Amigos, com o tempo curto ... foi o que deu pra fazer dessa vez ...
[]’s ...


Artigo completo (View Full Post)

segunda-feira, 20 de agosto de 2007

Construção de componentes - I

Vamos trabalhar num exemplo de construção de componentes. Aplicar na prática os conceitos orientação a objetos, buscar alcançar um alto índice de coesão, e conseqüentemente de re-usabilidade. Sempre vc deve ter foco em alcançar esses objetivos nos módulos que está implementando. A construção de componentes é uma abordagem que se propõe a isso. Constantemente, nos temas que temos discutido aqui no Estação ZN, o paradigma OO tem ocupado um lugar de destaque em nossos artigos. Isso não é por acaso, tão pouco por partidarismo passional, nem mesmo por modismo. O principal e único motivo é porque temos experimentado soluções satisfatórias ao optarmos por essa abordagem no nosso trabalho.
Nos últimos meses temos estado ausentes do blog por conta de um projeto novo onde fomos alocados. Um projeto novo que surgiu da demanda suscitada mediante ao fracasso de um outro. Um projeto do tipo: “Salve minha empresa, pelo amor de Deus, porque esse fracassou!!!”. Pra você ter uma idéia, durante o processo de seleção, a pessoa do RH que me entrevistava, num dado momento, fez uma pergunta inusitada: “Você esta acostumado a salvar causas perdidas?” .... Caracas!! (eu pensei), virei santo (das causas perdidas do TI brasileiro ... creeeeeeeeddddoooooo ....). Bem, dessa história, o que realmente importa é a certeza que tenho adquirido na pratica do dia a dia de trabalho que o sucesso ou o fracasso de um projeto de software passa em grade parte pela abordagem de desenvolvimento que é adotada para seu empreendimento. Pois bem, a relação do projeto fracassado, ao qual me refiro, com o tema deste artigo é justamente a ausência das boas praticas de programação, a ausência do zelo por uma modularização eficiente, a ausência de um processo formal de engenharia de software (adequado a realidade atual do mercado de TI internacional), que caracteriza a forma pela qual o dito cujo fio implementado.
Não obstantemente, também tenho observado que:
Quem com marreta marretar, com marreta será marretado.

Os que pelo martelo de Thor lutam, pelo próprio martelo perecerão
.
Pra bom entendedor, um pingo é letra ....

Portanto, vamos prosseguindo .... neste artigo, o primeiro exemplo ....

VCL

A VCL (Visual Components Library – Biblioteca de componentes visuais) é um conjunto de classes, a qual é usada para definir todos os componentes no Delphi (Botões, Caixas de Texto, Janelas, etc..). Você pode criar novas classes extendidas da VCL e conseqüentemente, definir novos componentes. Estes por sua vez podem ser vinculados a um pacote (package) e instalados na IDE do Delphi.
O que é extender, ou derivar uma classe de outra? Isso é o conceito de herança da orientação a objetos. Uma classe herda (extende) de uma outra seus atributos e métodos, e se especializa acrescentando outras funcionalidades ou características exclusivas. Por exemplo, imagine uma classe automóvel, da qual eu extendo uma nova, a fim de especializa-la para automóvel esportivo. Que é acrescida de uma série de características e funcionalidades exclusivas que a definem como tal, embora inda conserve muito em comum com seu ancestral direto, a classe automóvel.
Note que não vemos nas ruas, tão pouco nas lojas, nenhum veículo que possa ser usado como exemplo da classe automóvel. Dentro da nossa linha de raciocínio, todos os veículos existentes já são extremante especializados, exemplo: Ou temos, automóvel de passeio, Ou automóvel de luxo, automóvel utilitário, automóvel esportivo e etc...
A classe “Automóvel” dentro do nosso contexto é o que conceituamos por classe abstrata. Um tipo de classe que não pode gerar nenhuma instância. Esse tipo de classe possui um propósito servir de modelo, de extensor para outras classes. Na VCL existem inúmeras classes, chamadas de intermediárias (Invariavelmente indicadas com o prefixo “TCustom” em seus nomes) que servem de ponto de partida para você construir seu novo componente a partir de um comportamento padrão pré-existente.
Permita-me fazer um paralelo com Husserl, que afirma que toda consciência é consciência de alguma coisa. Logo, o que você imaginaria construir se não lhe fosse dado conhecer nenhum objeto? Ou, você pode ter certeza que nunca irá precisar construir nada que já não tenha sido conceituado. Portanto, não tem lógica criar uma classe que não descenda de nenhuma outra.
A discussão sobre a importância do desenvolvimento de componentes para uma organização de fábrica de software é algo presente nos principais institutos voltados para área. Academicamente esse assunto é abordado em matérias como Engenharia de Software e Arquitetura de Software. Sou um dos engajados nesse tema, também levanto veementemente esta bandeira. Embora, particularmente, eu tenha algumas reservas em ralação de como a IDE do Delphi trabalha com componentes, acredito que o programador pode aprender muito sobre Delphi e como tirar máximo proveito dessa ferramenta, se empenhado na construção de componentes, buscando aperfeiçoar-se nessa técnica.

Dentre um leque de maneiras possíveis de iniciar um projeto de construção de componentes, no menu do Delphi: File ► New ► Other ► (na Aba New – Conforme figura abaixo) selecione “Component” e click “OK”.



Em seguida uma tela de dialogo do component wizard irá solicitar as informações a seguir:



Ancestror Type: Você define aqui a classe da qual herdará o componente que deseja criar.

Class Name: Onde você define o nome da classe que deseja criar. Ou seja, o nome da classe do novo componente que você está criando.

Pallete Page: A palheta de componente na qual você deseja que seu novo componentes esteja organizado na IDE do Delphi (Standard, Additional, etc). Podendo escolher dentre qualquer uma das existentes ou simplesmente criar uma exclusiva para sua própria biblioteca de componentes novos personalizados. Caso queira criar uma nova basta digitar o nome desejado nesta opção.

Unit File Name: o path, e o nome da unit que será o arquivo “.pas” onde seu componente estará codificado.

Search Path: É aconselhável que essa opção seja configurada automaticamente pelo Delphi, a não ser que você seja um programador avançado. Aqui fica configurado os diretórios onde o Delphi irá procurar as units referenciadas na uses da seu arquivo “.pas” para fazer a link edição.



OBS: A “procedure Register” é o único caso em que um identificador no Delphi tenha que obedecer a diferenciação de caixa. Ou seja, case sensitive. Obrigatoriamente deve iniciar com letra maiúscula. Caso contrário, embora o compilador não reclame, o registro do componente não será efetivado e nenhuma mensagem de erro será emitida.

Em artigos anteriores já expliquei a respeito das palavras reservadas private, protected, public e published. Também mencionei sobre a padronização de iniciar um classe com o prefixo “T” que indica “Type”.

O Componente que decidi construir, visando somente o aspecto didático, trata-se de uma caixa de seleção (que chamamos de combo box) possuidora de uma funcionalidade capaz de alterar o tipo de fonte dos componentes visuais exibidos num formulário. A idéia é permitir que o usuário possa selecionar um tipo de fonte desejado a partir de uma escolha na caixa de seleção. Componente este que vamos construir neste artigo. Estou usando um exemplo conhecido, o qual tomei como base usando como modelo um exemplo do capitulo 9 do livro, a Bíblia do Delphi, do Marco Cantú.

Seção Published
Vamos codificar as propriedades “Style”, “Items”, “ChangeFormFont”. Na seção “published”, dentro de “type” digite conforme trecho a baixo. Em seguida pressione “Ctrl + Shift + C”, simultaneamente.


type
TZnCombo = class(TCustomComboBox)
private
FChangeFormFont: Boolean;
procedure SetChangeFormFont(const Value: Boolean);
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
property Style default csDropDownList;
property Items stored False;
property ChangeFormFont: Boolean read FChangeFormFont write
SetChangeFormFont default True;
end;


Sua unit deverá ficar conforme imagem abaixo após pressionar “Ctrl + Shift + C”.



Seção Public
Agora vamos definir alguns dos comportamentos do nosso componente codificando os seguintes métodos de visibilidade pública. Digite os métodos, “procedure CreateWnd”, “constructor Create” e “procedure Change” . Em seguida pressione “Ctrl + Shift + C”, simultaneamente. Veja, trecho de código a segui:


type
TZnCombo = class(TCustomComboBox)
private
FChangeFormFont: Boolean;
procedure SetChangeFormFont(const Value: Boolean);
{ Private declarations }
protected
{ Protected declarations }
public
constructor Create (AOwner: TComponent); override;
procedure CreateWnd; override;
procedure Change; override;
published
property Style default csDropDownList;
property Items stored False;
property ChangeFormFont: Boolean
read FChangeFormFont write SetChangeFormFont default True;
end;


Codificando o corpo dos métodos públicos:

A) procedure CreateWnd;

procedure TZnCombo.CreateWnd;
begin
inherited CreateWnd;
Items.Assign (Screen.Fonts);

// setando a fonte do Form Owner como default
if FChangeFormFont and Assigned (Owner) and (Owner is TForm) then
ItemIndex := Items.IndexOf ((Owner as TForm).Font.Name);
end;


B) constructor

constructor TZnCombo.Create(AOwner: TComponent);
begin
inherited Create (AOwner);
Style := csDropDownList;
FChangeFormFont := True;
ParentFont := False;
end;


C) procedure Change;

procedure TZnCombo.Change;
begin
// Atribuindo a fonte ao Form Owner
if FChangeFormFont and Assigned (Owner) and (Owner is TForm) then
TForm (Owner).Font.Name := Text;
inherited;

end;


Criando uma Package

Agora, para instalar o TZnCombo na IDE e podermos fazer uso dele em um projeto, devemos criar o que traduzimos por pacote (package). No menu: Component ►Install Component ...



Você pode escolher adicionar seu componente a uma package preexistente, ou criar uma package nova. Neste Exemplo, preferi criar uma nova, a qual chamei de “ZnConstruindoComponenetes.dpk”. Em “Unit File Name”, aponto para o arquivo “.pas” no qual estou trabalhando. Onde estamos codificando nosso componente. Veja detalhamento sobre essa funcionalidade (instalar componentes) no artigo sobre Flash e Delphi ....



Click em “Compile”, em seguida em “Install” (Não esqueça de adicionar a unit "Forms" na seção uses).




Testando o componente que criamos

Inicie uma nova aplicação, antes de qualquer coisa feche o projeto da package. No menu do Delphi: File ►Close All. Em seguida, File ► New ►Application.

1 – Adicione um TZnCombo, um TMemo e um TLabel (No menu Vew ► Component List, digite .. TZn ...)


Com o Component List évc pode acessar mais rápido o componente que deseja ..... adicionando o Label.



Set a propriedade “ParentFont” do Label1 para “False”, conforme figura abaixo. Assim evitaremos que a font dele seja alterada quando selecionarmos um tipo no combo que contruimos.



Adicione o Memo ...



Pronto!!! Pouco código? Isso é um bom sinal. Salve a aplicação. Execute seu programa (pressione F9), ou clike sobre o executável gerado (Alt + P + B – para construir o Executável).



Digite alguma coisa na área de texto ....



Altere o tipo da fonte selecionando uma dentre as listadas na combo.


Note que o Label1 não altera sua fonte, visto que setamos sua propriedade “ParentFont” para False. Caso contrário, ele alteraria a font simultaneamente com os demais componentes.



A idéia é que esse artigo seja uma introdução ao assunto. Seguiremos, aprofundando, no próximo artigo ....

segue o código completo da Unit "ZnCombo.pas":

unit ZnCombo;

interface

uses
SysUtils, Classes, Controls, StdCtrls;

type
TZnCombo = class(TCustomComboBox)
private
FChangeFormFont: Boolean;
procedure SetChangeFormFont(const Value: Boolean);
{ Private declarations }
protected
{ Protected declarations }
public
constructor Create (AOwner: TComponent); override;
procedure CreateWnd; override;
procedure Change; override;
published
property Style default csDropDownList;
property Items stored False;
property ChangeFormFont: Boolean
read FChangeFormFont write SetChangeFormFont
default True;
end;

procedure Register;

implementation

uses
Forms;

procedure Register;
begin
RegisterComponents('EstacaoZn', [TZnCombo]);
end;

{ TZnCombo }

procedure TZnCombo.Change;
begin
// Atribuindo a fonte ao Form Owner
if FChangeFormFont and Assigned (Owner) and (Owner is TForm) then
TForm (Owner).Font.Name := Text;
inherited;

end;

constructor TZnCombo.Create(AOwner: TComponent);
begin
inherited Create (AOwner);
Style := csDropDownList;
FChangeFormFont := True;
ParentFont := False;
end;

procedure TZnCombo.CreateWnd;
begin
inherited CreateWnd;
Items.Assign (Screen.Fonts);

// setando a fonte do Form Owner como default
if FChangeFormFont and Assigned (Owner) and (Owner is TForm) then
ItemIndex := Items.IndexOf (
(Owner as TForm).Font.Name);
end;

procedure TZnCombo.SetChangeFormFont(const Value: Boolean);
begin
FChangeFormFont := Value;
// refresh - atualizando a nova fonte
if FChangeFormFont then
Change;
end;

end.


Artigo completo (View Full Post)

 
BlogBlogs.Com.Br