sexta-feira, 27 de abril de 2007

Um pouco sobre a arquitetura dos aplicativos Delphi


The Architecture of Delphi Applications - little about

O Delphi gera um aplicativo (programa) na forma de um objeto. Na pratica são dois objetos: O Apllication, da classe TApllication, e o Screen, da classe TScreen.

TApplication

Nos programas que fazemos em Delphi, muitas vezes o objeto Application é referenciado em código. Suponho que muitos o fazem sem ter a mínima idéia do que se trata. Neste artigo vamos falar um pouco sobre esse objeto, e estudar algumas técnicas de como trabalhar com ele.

O Objeto Application, da classe TApplication, esta definido na Unit “Forms”. Veja na figura abaixo, a declaração de uma seção “var” onde é declarada uma série de objetos, dentre eles o Application, tratados pelos engenheiros do Delphi, na própria VCL, como objetos globais.


Só por curiosidade, eu fiz um busca no BDS, e encontrei 46 units da VCL usam a unit Forms. No D7 são 54. Só pra vc ter um idéia da visibilidade desses objetos dentro da VCL.



Embora seja definido aqui, ele só é criado, instanciado na unit “Controls”, especificamente na procedure “InitControls”. Veja um screenshot da Unit “Controls”.

“Application is a global object of the TApplication class, defined in the Forms unit and created in the Controls unit” (Mastering Delphi 7).




A classe TApplication, também está definida na unit “Forms”. Ela herda de TComponent, algumas de suas propriedades podem ser configuradas no menu Project ►Options (Shift + Ctrl + F11), Application (no D7, D6 ou D5 fica numa aba; No BDS item da treeview).



Em Project Options, Forms (no D7, D6 ou D5 fica numa aba; No BDS item da treeview).vc pode também configurar qual dos Forms em sua aplicação será o principal, bem como quais serão criados, alocados, imediatamente assim que a aplicação for executada, e os que ficarão disponíveis para serem instanciados dinamicamente.




O componente ApplicationEvents (TApplicationEvents, palheta Additional) nos permite manipular os eventos do objeto Application.

Vamos para nosso primeiro exemplo: Manipulando os evento do objeto Application com o “TApplicationEvent”.

1 - Inicie uma nova aplicação.

1.1 Adicione um outro Form, Form2.

Construindo o Form1

Adicione e configure os componentes no Form1 segundo listagem abaixo:

Form1: TForm1
Left = 200
Top = 280
Caption = 'Form1'
ClientHeight = 98
ClientWidth = 434
Position = poDesigned

Panel1: TPanel
Name = PnlForm
Left = 16
Top = 28
Width = 396
Height = 41

ApplicationEvents1: TApplicationEvents





Codificando o evento OnActivate do Application1, “ApplicationEvents1Activate”:

procedure TForm1.ApplicationEvents1Activate(Sender: TObject);
begin
PnlForm1.Caption := 'Application Active';
PnlForm1.Color := clYellow;
Self.Color := clBtnFace;
Beep;
end;



Codificando o evento OnDeactivate do Application1, “ApplicationEvents1Deactivate”:

procedure TForm1.ApplicationEvents1Deactivate(Sender: TObject);
begin
PnlForm1.Caption := 'Application Not Active';
PnlForm1.Color := clWhite;
end;


Codificando o evento OnException do Application1, “ApplicationEvents1Exception”:

procedure TForm1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
ARed, AGreen, ABlue: Integer;
begin

ARed := RandomCollor;
AGreen := RandomCollor;
ABlue := RandomCollor;
Self.Caption := E.Message +' - '+ E.ClassName;
Self.Color := RGB(ARed, AGreen, ABlue);
PnlForm1.Caption := E.ClassName + ' - Trate a exceção aqui.';
PnlForm1.Color := clAqua;


end;


Codificando o procedimento RandomCollor, declarado na seção “private” do Form1:

function TForm1.RandomCollor: Integer;
begin
(* retorna aleatoriamente um valor para colorir o form *)
Randomize;
Result := RandomRange(0, 255);
end;


Codificando o evento OnActivate do Form1, “FormActivate”:

procedure TForm1.FormActivate(Sender: TObject);
begin
PnlForm1.Caption := 'Form1 Active';
PnlForm1.Color := clRed;
end;


Codificando o evento OnDeactivate do Form1, “FormDeactivate”:

procedure TForm1.FormDeactivate(Sender: TObject);
begin
PnlForm1.Caption := 'Form1 Not Active';
PnlForm1.Color := clBtnFace;
end;




Construindo o Form2.
Adicione e configure os componentes no Form2 segundo listagem abaixo:
OBS: Set a propriedade “Visible” do Form2 para “true”, isso fará com que os dois forms sejam exibidos quando a aplicação for executada.


Form2: TForm2
Left = 230
Top = 0
Caption = 'Form2'
ClientHeight = 236
ClientWidth = 426
Position = poDesigned
Visible = True

ListBox1: TListBox
Name = LstForm2
Left = 0
Top = 39
Width = 426
Height = 197
Align = alBottom
ItemHeight = 13

Button1: TButton
Name = BtnGeraExcecao
Left = 330
Top = 8
Width = 88
Height = 25
Caption = 'Gerar Exceção’

Edit1: TEdit
Name = EdtExcecao
Left = 4
Top = 12
Width = 317
Height = 21



Vamos criar um propriedade para o From2. Isso servirá para contarmos quantas vezes o Form2 for ativado. Além disso vou declarar tb uma exceção, aqual usaremos mais tarde, Na seção “type”, em “public” digite conforme exemplificado abaixo:

type
EZnException = class (Exception);
TForm2 = class(TForm)
LstForm2: TListBox;
BtnGeraExcecao: TButton;
EdtExcecao: TEdit;
private
{ Private declarations }
public
property ContAtivacao: Integer; // Após digitar, pressione “Ctrl+Shift+C”
end;


Ao pressionar “Ctrl+Shift+C” a IDE irá completar para vc a implementação da propriedade.

Codificando o evento OnActivate do Form2, “FormActivate”:

procedure TForm2.FormActivate(Sender: TObject);
const
TxtAtivacao = 'Form2 ativado pela %d° Vez.';
begin
SetContAtivacao(FContAtivacao + 1);
LstForm2.Items.Add(Format(TxtAtivacao, [FContAtivacao]));
end;


Codificando o evento OnDeactivate do Form2, “FormDeactivate”:

procedure TForm2.FormDeactivate(Sender: TObject);
begin
LstForm2.Items.Add('Form2 Not Active');
end;


Codificando o evento OnClick do BtnGeraExcecao, “BtnGeraExcecaoClick”:
Na seção type definimos anteriormente a exceção EZnException

procedure TForm2.BtnGeraExcecaoClick(Sender: TObject);
begin
if EdtExcecao.Text = '' then
raise Exception.Create('EdtExcecao vazio.')
else
raise EZnException.Create(EdtExcecao.Text);
end;


Segundo exemplo:
2 - Inicie uma nova aplicação.

2.1 - Adicione e configure os componentes no Form1 segundo listagem abaixo:

Form1: TForm1
Caption = 'Exemplo 2'
ClientHeight = 293
ClientWidth = 426

Label1: TLabel
Left = 120
Top = 12
Caption = 'Digite o Nome do Form'

RadioGroup1: TRadioGroup
Name = RdgOwner
Left = 8
Top = 12
Width = 101
Height = 73
Caption = 'Definir Owner'
Items.Strings = (
'Application'
'Self'
'Nil')

Edit1: TEdit
Name = EdtNameFrom
Left = 120
Top = 31
Text = ''

CheckBox1: TCheckBox
Name = ChkShow
Left = 120
Top = 68
Caption = 'Exibir'

Button1: TButton
Name = BtnCriar
Left = 344
Top = 29
Caption = 'Criar Form'

ListBox1: TListBox
Name = LstObjetos
Left = 0
Top = 95
Width = 426
Height = 179
Align = alBottom

StatusBar1: TStatusBar
Adicione dois Panels

Button1: TButton
Left = 343
Top = 64
Width = 75
Height = 25
Caption = 'Listar Forms'

ApplicationEvents1: TApplicationEvents



Codificando o evento OnActivate do Application1, “ApplicationEvents1Activate”:

procedure TForm1.ApplicationEvents1Activate(Sender: TObject);
begin
(* Ao ativar a aplicação, seta um valor para a propriedade name do objeto Application. Isso terá apenas valor didático para o nosso exemplo *)
Application.Name := 'ZnApplication';
end;


Codificando o evento OnClick do BtnCriar, “BtnCriarClick”:

procedure TForm1.BtnCriarClick(Sender: TObject);
var
SomeForm: TForm;
Begin
(* Cria dinâmicamente um form, de acordo com a escolha do usuário, num Owner específico.*)
case RdgOwner.ItemIndex of
0: Application.CreateForm(TForm, SomeForm);
1: SomeForm := TForm.Create(Self);
2: SomeForm := TForm.Create(Nil);
end;
// Atribuindo valores que permitirão identificarmos o form
SomeForm.Name := EdtNameFrom.Text;
SomeForm.Caption := EdtNameFrom.Text;
SomeForm.Visible := ChkShow.Checked;

//Atribuindo o procedimento FormActivate
SomeForm.OnActivate := FormActivate;

EdtNameFrom.SetFocus;
end;


Codificando o evento OnClick do Button1, “Button1Click”:

procedure TForm1.Button1Click(Sender: TObject);
const
FormNameAndOwner = 'ClassName: %s - Name: %s - Owner: %s';
var
I: Integer;
MyOwner: TComponent;
begin
(*Monta uma lista de todos os componentes associados ao objeto Application*)
for I := 0 to Application.ComponentCount - 1 do
begin
if Application.Components[i] is TComponent then
begin
MyOwner := TComponent(Application.Components[i]).Owner;
LstObjetos.Items.Add(Format(FormNameAndOwner,
[TComponent(Application.Components[i]).ClassName,
TComponent(Application.Components[i]).Name,
MyOwner.Name]));
if Application.Components[i] is TForm then
if NOT(Application.Components[i] = Application.MainForm) then
TForm(Application.Components[i]).Visible := ChkShow.Checked;
end;
end;

end;


Montando a listagem dos componentes associados ao Application estou tentando iluminar um pouco a arquitetura de um aplicativo Delphi. Os forms que forem criados na segunda opção do radiogroup (cria o form no “Self”), ou seja, o Owner dele é o form1, portanto, ele é “neto” do Application.
Outro ponto importante é que os forms criados com Owner nulo, terceira opção do radiogroup, implica que esses forms não serão desalocados quando o objeto Application for destruído. Eles ficarão perdidos, como lixo na memória. Outro ponto curioso que vc pode testar é que este é o único caso que é permitido criar várias instâncias cujos nomes sejam iguais. Depois, é possível que vc recebe alguns acessos violados por isso ... hehehhe ... Em suma, esses dois aspectos têm um impacto muito negativo.


Codificando o evento OnActivate do Form1, “FormActivate”:

procedure TForm1.FormActivate(Sender: TObject);
var
I: Integer;
Begin
(* Exibe na statusbar o nome do form ativo *)
for I := 0 to Application.MainForm.ComponentCount - 1 do
if Application.MainForm.Components[i] is TStatusBar then
TStatusBar(Application.MainForm.Components[i]).Panels[0].Text :=
'Form ativo: ' + TForm(Sender).Name;

end;

Quando vc executar o aplicativo, criar os forms, clickar sobre cada um deles e observar a statusbar ... clickar sobre o Button1 “Listar Forms”, que na verdade esta listando componentes associados ao Application, vc vai notar a presença de um “THintWindow” ... tchan, tchan, tchan ... Quem é ele?
THintWindow – Classe da janela que exibe o hint, dos controles.

“the small pop-up window that appears over a control at runtime when the control has its ShowHint property set to true”.

A classe TApplication tem um atributo privado que é um objeto, FHintWindow cujo o tipo é THintWindow. Quando é atribuído valor para a sua propriedade “ShowHint” (property ShowHint: Boolean read FShowHint write SetShowHint), no método privado SetShowHint, é instanciado, apontando para o outro objeto global “HintWindowClass”, o qual também é instanciado nesse momento. Veja o trecho de código que retirei da VCL:

procedure TApplication.SetShowHint(Value: Boolean);
begin
if FShowHint <> Value then
begin
FShowHint := Value;
if FShowHint then
begin
FHintWindow := HintWindowClass.Create(Self);
FHintWindow.Color := FHintColor;
end
else
begin
FHintWindow.Free;
FHintWindow := nil;
end;
end;
end;

Veja também a imagem com a declaração dos objetos globais na unit Forms. No screenshot abaixo, debuggando comprovo que acabei de afirmar no parágrafo acima.



Ok, por causa dessa associação com objeto HintWindowClass achei que seria interessante dar uma incrementada no exemplo que implementamos anteriormente. Os componentes no form1 são os mesmos apenas acrescentei duas funcionalidades novas para explorar o “HintWindowClass”. Além disso acho que dessa forma podemos jogar mais luz sobre como a VCL arquiteta o aplicativo Delphi. As funcionalidades são: A primeira visa, através do Application recuperar os hists exibidos de cada componentes visual presente no form. A outra, é a conseqüência dessa, para exibir os hints, preciso de componentes nos forms que são criados dinamicamente. Mas, se estou criando esses forms dessa forma, como vou adicionar componentes nele? Ou, quais componentes usarei para isso?
Simples, vamos criá-los dinamicamente, e ateatóriamente ... hehehheh ... vai ser divertido.
Na seção “private”, declararemos as seguintes rotinas:

  • procedure BuildSetHint;
  • procedure CreateComponent(AOwner: TComponent; AParent: TWinControl);
  • function ZnCreateControlByName(AClassName: string; AOwner: TComponent): TControl;
  • function BuidComponentName(const AClassName: string; const NumId: Integer): string;
veja como fica:
Antes preciso definir uma constante, um array de classes ....

const
ZnObjectLst: array[0..9] of TPersistentClass = (TEdit, TBitbtn,
TMonthCalendar, TSpinEdit, TSpinButton, TMemo, TShape,
TLabel, TComboBox, TListBox);


private
procedure BuildSetHint;
procedure CreateComponent(AOwner: TComponent;
AParent: TWinControl);
function ZnCreateControlByName(AClassName: string;
AOwner: TComponent): TControl;
function BuidComponentName(const AClassName: string;
const NumId: Integer): string;
public
{ Public declarations }
end;

O bom e velho “Ctrl+Shit+C” para partimos para implementação do corpo de cada uma das rotinas.


procedure TForm1.BuildSetHint;
const
ATxtHint = '%s teste hint Zn - Class: %s.';
var
I: Integer;
J: Integer;
MyControl: TControl;
begin
(* Monta e atribui um hint a cada componente vinculado, em algun nível (neste caso, somente dois níveis, mas poderíamos fezer essa busca recursivamente ...), ao objeto Application. *)
for I := 0 to (Application.ComponentCount - 1) do
begin
if Application.Components[i] is TForm then
for J := 0 to TForm(Application.Components[i]).ComponentCount - 1 do
if TForm(Application.Components[i]).Components[J] is TControl then
begin
MyControl :=
TControl(TForm(Application.Components[i]).Components[J]);
MyControl.Hint := Format(ATxtHint, [MyControl.Name,
MyControl.ClassName]);
MyControl.ShowHint := True;
end; // fim if then ...
end; // fim laço I
end;


Para criar os componentes dinamicamente:

function TForm1.ZnCreateControlByName(AClassName: string;
AOwner: TComponent): TControl;
var
Cls: TControlClass;
begin
(* Aqui a chapa esquenta um pouco ...
Essa função retorna uma classe dinamicamente. Para fazer isso,
vc tem que registra, também em tempo de execução, a classe.
Mais tarde explicaremos o pq disso ....*)

Result := nil;
RegisterClasses(ZnObjectLst);
Cls := TControlClass(GetClass(AClassName));
if Cls = nil then exit;
Result := Cls.Create(AOwner);


end;

Essa é molezinha,
peace of cake ...

function TForm1.BuidComponentName(const AClassName: string;
const NumId: Integer): string;
begin
Result := 'Zn' + Copy(AClassName, 2,
Length(AClassName) - 2) + IntToStr(NumId);
end;




procedure TForm1.CreateComponent(AOwner: TComponent; AParent: TWinControl);
var
OrdElement: Integer;
ZnControl: TControl;
begin
(* Determina, aleatóriamente que componente será criado,
seta noem, posição para o componente novo *)
Randomize;
OrdElement := RandomRange(0,9);
ZnControl :=
ZnCreateControlByName(ZnObjectLst[OrdElement].ClassName, AOwner);
OrdElement := 1;
with ZnControl.Create(AOwner) do
begin
ZnControl.Parent := AParent;
while AOwner.FindComponent(BuidComponentName(
ZnControl.ClassName, OrdElement)) <> nil do
Inc(OrdElement,1);

Name := BuidComponentName(ZnControl.ClassName, OrdElement);
Randomize;
Top := RandomRange(0, (AParent.Height - ZnControl.Height) - 12);
Randomize;
Left := RandomRange(0, (AParent.Width - ZnControl.Width) - 12);
end;
end;


Agora usaremos o evento OnShowHint do Applicatio, através do ApplicationEvents1, para capturarmos centralizadamente todos os hins exibidos na aplicação. Evento OnShowHint do ApplicationEvent1, “ApplicationEvents1ShowHint”:

procedure TForm1.ApplicationEvents1ShowHint(var HintStr: string;
var CanShow: Boolean; var HintInfo: THintInfo);
begin
StatusBar1.Panels[1].Text := Application.Hint;
end;

Pronto, acho que por aqui já da pra se ter uma noção da arquitetura ...
Estou exausto, não agüento mais falar sobre isso, o assunto é muito extenso. Estou louco de cansaço. Depois falaremos sobre o objeto Screen. Gostaria muito de montar um diagrama exemplificando a reação entre esses objetos e suas interfaces ... fica mais tarde ... quem sabe. Por garantia, segue o código da unit completo .... By


unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls, Spin, ExtCtrls, AppEvnts, Buttons;

const
ZnObjectLst: array[0..9] of TPersistentClass = (TEdit, TBitbtn,
TMonthCalendar, TSpinEdit, TSpinButton, TMemo, TShape,
TLabel, TComboBox, TListBox);


type
TForm1 = class(TForm)
RdgOwner: TRadioGroup;
EdtNameFrom: TEdit;
Label1: TLabel;
ChkShow: TCheckBox;
BtnCriar: TButton;
LstObjetos: TListBox;
StatusBar1: TStatusBar;
Button1: TButton;
ApplicationEvents1: TApplicationEvents;
procedure ApplicationEvents1ShowHint(var HintStr: string;
var CanShow: Boolean; var HintInfo: THintInfo);
procedure ApplicationEvents1Activate(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure BtnCriarClick(Sender: TObject);
private
procedure BuildSetHint;
procedure CreateComponent(AOwner: TComponent;
AParent: TWinControl);
function ZnCreateControlByName(AClassName: string;
AOwner: TComponent): TControl;
function BuidComponentName(const AClassName: string;
const NumId: Integer): string;
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

uses Math, Strutils;

{$R *.dfm}

procedure TForm1.ApplicationEvents1Activate(Sender: TObject);
begin
Application.Name := 'ZnApplication';
BuildSetHint;
end;

procedure TForm1.ApplicationEvents1ShowHint(var HintStr: string;
var CanShow: Boolean; var HintInfo: THintInfo);
begin
StatusBar1.Panels[1].Text := Application.Hint;
end;

procedure TForm1.BtnCriarClick(Sender: TObject);
const
TextCaptionFrm = '%s - Owner: %s';
var
SomeForm: TForm;
AownerName: string;
begin
(*Cria dinamicamente um Form e, aleatóriamente, dois componentes
associados a ele. *)
case RdgOwner.ItemIndex of
0: Application.CreateForm(TForm, SomeForm);
1: SomeForm := TForm.Create(Self);
2: SomeForm := TForm.Create(Nil);
end;

SomeForm.Name := EdtNameFrom.Text;
if not Assigned(SomeForm.Owner) then
AownerName := 'Null'
else
AownerName := SomeForm.Owner.Name;
SomeForm.Caption :=
Format(TextCaptionFrm, [EdtNameFrom.Text, AownerName]);
SomeForm.Visible := ChkShow.Checked;
SomeForm.OnActivate := FormActivate;
CreateComponent(SomeForm, SomeForm);
CreateComponent(SomeForm, SomeForm);
EdtNameFrom.SetFocus;
BuildSetHint;
end;

function TForm1.BuidComponentName(const AClassName: string;
const NumId: Integer): string;
begin
Result := 'Zn' + Copy(AClassName, 2,
Length(AClassName) - 2) + IntToStr(NumId);
end;

procedure TForm1.BuildSetHint;
const
ATxtHint = '%s teste hint Zn - Class: %s.';
var
I: Integer;
J: Integer;
MyControl: TControl;
begin
(* Monta e atribui um hint a cada componente vinculado, em algun nível,
ao objeto Application. *)
for I := 0 to (Application.ComponentCount - 1) do
begin
if Application.Components[i] is TForm then
for J := 0 to TForm(Application.Components[i]).ComponentCount - 1 do
if TForm(Application.Components[i]).Components[J] is TControl then
begin
MyControl :=
TControl(TForm(Application.Components[i]).Components[J]);
MyControl.Hint := Format(ATxtHint, [MyControl.Name,
MyControl.ClassName]);
MyControl.ShowHint := True;
end; // fim if then ...
end; // fim laço I
end;

procedure TForm1.Button1Click(Sender: TObject);
const
FormNameAndOwner = 'ClassName: %s - Name: %s - Owner: %s';
var
I: Integer;
MyOwner: TComponent;
begin
for I := 0 to Application.ComponentCount - 1 do
begin
if Application.Components[i] is TComponent then
begin
MyOwner := TComponent(Application.Components[i]).Owner;
LstObjetos.Items.Add(Format(FormNameAndOwner,
[TComponent(Application.Components[i]).ClassName,
TComponent(Application.Components[i]).Name,
MyOwner.Name]));
if Application.Components[i] is TForm then
if Application.Components[i] <> Application.MainForm then
TForm(Application.Components[i]).Visible := ChkShow.Checked;
end;
end;

end;

procedure TForm1.CreateComponent(AOwner: TComponent; AParent: TWinControl);
var
OrdElement: Integer;
ZnControl: TControl;
begin
(* Determina, aleatóriamente que componente será criado,
seta noem, posição para o componente novo *)
Randomize;
OrdElement := RandomRange(0,9);
ZnControl :=
ZnCreateControlByName(ZnObjectLst[OrdElement].ClassName, AOwner);
OrdElement := 1;
with ZnControl.Create(AOwner) do
begin
ZnControl.Parent := AParent;
while AOwner.FindComponent(BuidComponentName(
ZnControl.ClassName, OrdElement)) <> nil do
Inc(OrdElement,1);

Name := BuidComponentName(ZnControl.ClassName, OrdElement);
Randomize;
Top := RandomRange(0, (AParent.Height - ZnControl.Height) - 12);
Randomize;
Left := RandomRange(0, (AParent.Width - ZnControl.Width) - 12);
end;
end;

procedure TForm1.FormActivate(Sender: TObject);
var
I: Integer;
begin
(* Atribui a statusbar do form1 o nome do Form ativo.
Usamos ess implementação pq aqui, agora, em tempo de desenvovimento,
é impossível saber em que Form estará sendo processado ess código. *)

for I := 0 to Application.MainForm.ComponentCount - 1 do
if Application.MainForm.Components[i] is TStatusBar then
TStatusBar(Application.MainForm.Components[i]).Panels[0].Text :=
'Form ativo: ' + TForm(Sender).Name;

end;

function TForm1.ZnCreateControlByName(AClassName: string;
AOwner: TComponent): TControl;
var
Cls: TControlClass;
begin
(* Aqui a chapa esquenta um pouco ...
Essa função retorna uma classe dinamicamente. Para fazer isso,
vc tem que registra, também em tempo de execução, a classe.
Mais tarde explicaremos o pq disso ....*)
Result := nil;
RegisterClasses(ZnObjectLst);
Cls := TControlClass(GetClass(AClassName));
if Cls = nil then exit;
Result := Cls.Create(AOwner);


end;

end.


Nenhum comentário:

Postar um comentário