Dica para gerenciamento de formulários

(english)

Ao meu ver, uma parte mal elaborada do Delphi/Lazarus é o gerenciamento de formulários da aplicação. Tanto o fato de usar uma variável pública independente para controlar a instância quanto a isca ‘jogue tudo no form’ trazem problemas. A primeira abordagem exige que o programador, ou crie os formulários no início da aplicação e não os destrua mais, ou exige que ele cuide para não deixar a variável apontar para um objeto destruído. A segunda abordagem, entre outros problemas, exige que a primeira funcione perfeitamente para que ocorra comunicação entre formulários.

Quanto a segunda abordagem não há muito o que fazer. Ou convive-se com a limitação ou escreve-se um novo framework para contorná-la. Agora, para manter a variável pública atualizada, o programador pode beneficiar-se de pequenos truques como o que eu vou citar aqui.

A minha sugestão para manter a variável pública atualizada é passá-la para um método de classe. Este método cuida de inicializar o form caso necessário, e cuida de atualizar a variável quando o form for destruído. Para isto, implemente o seguinte método na classe base dos formulários, ou seja, aquela classe a partir da qual todos os forms irão herdar:

  TBaseForm = class(TForm)
  public
    destructor Destroy; override;
    class procedure Execute(var AForm; AModal: Boolean = False);
  end;

a implementação dos métodos fica assim:

class procedure TBaseForm.Execute(var AForm; AModal: Boolean);
var
  VIndex: Integer;
begin
  if not Assigned(_Forms) then
  begin
    _Forms := TStringList.Create;
    _Forms.Sorted := True;
  end;
  if not _Forms.Find(ClassName, VIndex) then
    VIndex := _Forms.Add(ClassName);
  if TForm(AForm) = nil then
    Application.CreateForm(Self, AForm);
  _Forms.Objects[VIndex] := TObject(@AForm);
  if AModal then
    TForm(AForm).ShowModal
  else
    TForm(AForm).Show;
end;

e associar nil à variável pública do form no momento em que ele for destruído:

destructor TBaseForm.Destroy;
type
  PForm = ^TForm;
var
  VIndex: Integer;
begin
  if Assigned(_Forms) and _Forms.Find(ClassName, VIndex) and
   (_Forms.Objects[VIndex] <> nil) then
  begin
    PForm(_Forms.Objects[VIndex])^ := nil;
    _Forms.Objects[VIndex] := nil;
  end;
  inherited;
end;

Por fim, falta declarar a StringList e destruí-la no encerramento da aplicação:

implementation

var
  _Forms: TStringList = nil;

...

initialization

finalization
  _Forms.Free;

end.

Pronto. Agora basta criar novos forms que descendam deste e chamar TSeuForm.Execute(SeuForm). Quando o form for destruído, a variável pública irá apontar para nil.

10 thoughts on “Dica para gerenciamento de formulários”

  1. Eu nunca uso a variavel global de forms que o delphi gera. A primeira coisa que eu faço é apagá-la quando crio uma nova unit, então esse problema simplesmente deixa de existir.

    se vc usar showmodal fica mais simples

    With Tform1.Create (nil) do
    begin
    ShowModal;
    Release;
    end;

    ou se naum usar showmodal vc pode criar uma referencia em um field no form que chama o form filho

    neste caso para limpar a referencia invalida do field vc poderia escutar o onclose do form filho.

    o uso destas variaveis globais que o delph encoraja promovem acoplamento.

  2. Eu concordo com você, e eu também apago estas variáveis pois o mvp do pressobjects cuida das instâncias por mim.

    E além do acoplamento, que vai ocorrer de qualquer forma ao acessar a classe do form, o modelo proposto e a forma que o delphi encoraja o uso dos forms imaginam uma única instância de cada form de cada vez.

    No entanto tenho visto pessoas procurarem meios de automatizar a atualização desta variável global, e é este o objetivo do artigo.

  3. Outra abordagem seria utilizar o mecanismo de notificação já presente na classe TComponent

    procedure Notification(AComponent: TComponent; Operation: TOperation); override;

    para remover a referencia do field que aponte para o sub-form.

  4. Que não seria mais eficiente do que sobrecarregar o destructor. E o maior problema ainda permaneceria: como localizar a variável que aponta para a instância do form a fim de setá-la para nil?

  5. Cara, essa dica me serviu muito bem.
    Estou reestruturando minhas aplicações que são SDI para MDI.
    Ontem tive uma ideia de criar uma classe base da qual seriam herdados todos os meus formularios child. A merda ocorria exatamente neste ponto. Na hora em que fechava o formulario filho, não conseguia setar a variavel de instancia para nil a partir dessa classe pai.
    Tentei de varias formas, fiquei umas 3 horas googando e naaada. Relaxei, tomei uma Pepsi, e encontrei seu blog. Show cara, serviu perfeitamente esta técnica para mim.
    Obrigado.

  6. Bom dia, o meu problema com gerenciamento de Formulários acontece da seguinte forma:
    Eu tenho um formulario de Busca, e um Formulario de Cadastro, No meu formulario de busca estão os componentes de Persistencia (TSqlQuery, TDataSetProvider, TClientDataSet) que serão utilizados, tanto para buscar os registros, quanto para fazer a inserção e edição dos mesmos. O Formulario de Cadastro não possue componentes de Persistencia, ou seja, Todos os “DbEdits” que estão na tela de cadastro, estão referenciados aos componentes de persistencia do Formulario de Busca.

    O Problema Ocorre no seguinte caso : Eu tenho um Formulario de Busca Instanciado. Eu quero criar outra Instancia deste mesmo Formulario Busca, o qual eu realizarei alterações no componente de Persistencia, e ao criar um Formulario de Cadastro, eu necessito que este esteja referenciada com essa instancia alterada, e não a primeira instancia que eu já o tinha criada… Em outras Palavras …. Os tais Componentes “DbEdits” presentes na tela de Cadastro estão ligados aos componentes que eu não alterei, isto quer dizer q o Fomulario de Cadastro que eu tenho esta vinculado a primeira instancia do Formulario de Busca, e não a Instancia que eu alterei os componentes…
    A duvida é, como referenciar o Formulario de Cadastro a segunda instancia do Formulario de Busca e não a primeira?

  7. João meu caro, estou sempre visitando seu blog e relendo esses artigos, pois são ótimos, os melhores.
    Uma coisa, só uma pequena correção no segundo bloco de código, colocar o “= False” no “Execute(var AForm; AModal: Boolean)”, para ficar igual a declaração. 😉

  8. João, esquece o ultimo comentário que fiz, executa normal aqui no Lazarus 🙂

    Quem quiser calar os hints chatos do FPC:

    (…)
    var
    VIndex: Integer;
    begin
    VIndex := 0;
    if not Assigned(_Forms) then
    begin
    (…)

    João, detona esse cara do “vigara medicine”, que spam fdp!

Comments are closed.