Mostrando postagens com marcador POO. Mostrar todas as postagens
Mostrando postagens com marcador POO. Mostrar todas as postagens

domingo, 5 de outubro de 2008

Java - Declarações e Modificadores de Classes - Declaração para Arquivos-Fontes

Os modificadores se dividem em duas categorias: “Modificadores de acesso” e “Modificadores que não se referem a acesso”.

1) Modificadores de acesso

Servem para você poder restringir ou permitir acesso a uma classe que você está criando (ou partes dessa classe). Ou seja, definem o escopo de visivilidade de uma classe. Os escopos devisibilidade definido pelos modificadores de acesso são: Público, Protegido e Privado.
Eles são definidos em código através do emprego das palavras reservadas: “public” (Escopo de visibilidade público), protected (Escopo de visibilidade protegido), private (Escopo de visibilidade privado).
É importante notar que, embora existam quatro controles de acesso (que funcionam como níveis de acesso), os modificadores de acesso são apenas três (public, protected, private). O quarto nível de controle de acesso (chamado de acesso “default” ou "de pacote") é o que você obtém quando não usa nenhum dos três modificadores de acesso na declaração realizada durante a codificação de uma classe. Ou seja, toda classe, método, ou/e variável de instância que você declarar possui um controle de acesso, independentemente disso ser declarado explicitamente ou não.
Todos os quatro controles de acesso são adequados para as declarações de métodos e variáveis. Contudo, para classses, somente dois deles são válidos: público ou default . Uma classe só pode ser declarada como pública ou default, os outros dois níveis de controle de acesso não se aplicam para classes.
Os modificadores de acesso são os responsáveis por outra característica fundamental para a O.O. o “encapsulamento

1.1) Acesso a Classes

O termo aceeso a classes refere-se ao fenômeno que através de um método próprio, uma classe (classe “ZnX”) acessar outra (classe “ZnY”). Isso significa que a classe “ZnX” pode fazer uma das seguintes coisas:
1) Criar uma instancia da classe “ZnY”.
2) Estender a classe “ZnY” (ou seja, tornar-se uma subclasse da classe “ZnY”).
3) Acessar certos métodos e variáveis dentro da classe “ZnY”, dependendo do controle de acesso desses métodos e variáveis.


Definindo Controle de Acesso em Classes

Na prática, acesso significa visibilidade. Suponha que temos duas classes: classe “ZnX” e classe “ZnY”. Se a classe “ZnX” não puder ver a classe “ZnY”, o nível de acesso dos métodos e variáveis dentro da classe “ZnY” não fará diferença. A classe“ZnX” não terá como acessar os métodos e propriedades de “ZnY”.

Acesso Default: Uma classe cujo modificador de acesso é o chamado “default”, é a quela que não tem nenhum modificador precedendo na sua declaração. Esse é o controle de acesso que você obtém quando não digita um modificador na declaração da classe. Pense no acesso defaultcomo um acesso de nível de pacote, porque a classe com acesso default só pode ser vista por classes de dentro do mesmo pacote. Por exemplo: Se a classe “ZnX” e a classe “ZnY” estiverem em pacotes diferentes, e a classe “ZnX” tiver acesso default, a classe “ZnY” não será capaz de criar uma instância da classe “ZnX”. Nem mesmo, se quer, declarar uma variável ou tipo de retorno da classe “ZnX”. Fataliticamente, para a classe “ZnY” a classe “ZnX” nem sequer existe. Do contrário, o compilador irá reclamar caso isso não seja repeitado. Observe o seguinte arquivo-fonte:



package fermentadosZN;

class VinhoZn { }


Agora observe o segundo arquivo-fonte:

package destiladosZN;

import fermentadosZN.VinhoZn;

class ConhacZn extends VinhoZn { }


Observe, nos trechos de código ilustrados acima que, a superclasse “VinhoZn” está em um pacote diferente da subclasse “ConhacZn”. Veja na linha 3 a declaração “import” possibilitando a package “destiladosZN” importar a classe “VinhoZn” para a ser usado n apackage “destiladosZN”. Supondo que tentássemos compilar, o arquivo “fermentadosZN” compilará sem problemas, contudo, quando tentamos compilar o arquivo “destiladosZN”, receberemos uma mensagem de exceção semelhante do tipo:



Can't access class destiladosZN. Class or interface must be public, in same package,or an
accessible member class.
import destiladosZN;


“destiladosZN” não compila porque a sua superclasse, “VinhoZn”, tem acesso default e está em um pacote diferente. Existem duas coisas que podemos pode fazer para nosso problema hipotético:

1) Poderiamos colocar ambas as classes no mesmo pacote,
2) Ou. poderiamos declarar “VinhoZn” como public. Exemplo:


Esse tipo de situação/problema costuma ser usado em prova de certificação. Portanto, quando você vir uma questão com lógica complexa, certifique-se de olhar os modificadores de acesso antes de qualquer coisa. Assim, caso identifique uma violação de acesso (por exemplo, uma classe do pacote “X” tentando acessar uma classe default do pacote “Y”), você saberá que o código não vai compilar, portanto não precisa nem se dar ao trabalho de tentar entender a suposta lógica complexa da questão. A qual está lá justamente para desviar sua atenção indizindo você ao erro. Além de economizar tempo e stress.

Acesso Público: Uma declaração de classe com a palavra reservada public torna ela visivél a todas as classes, de todos os pacotes. Todas as classes do Universo Java (JU) têm acesso a uma classe pública. Contudo, não se esqueça que mesmo sendo pública, uma classe para ser usada, se estiver em um pacote diferente, será obrigatório importar-lá.


2) Outros Modificadores de Classes (Não-referentes a Acesso)

Além dos modificadores vistos anteriormente ainda existem mais três, os quais são definidos pelas palavras reservadas “final”, “abstract” e “strictfp”. Esses modificadores existem em adição a qualquer controle de acesso existente na classe. Logo poderiamos, por exemplo, declarar uma classe como “public” e “final” ao mesmo tempo.
Todavia, isso não é adequado para todas as situações. Não é sempre que podemos misturar modificadores não-referentes a acesso. Por exemplo: É possível usar “strictfp” em combinação com “final”, contudo nunca (jamais, em tempo algum) marcar uma classe como “final” e “abstract” ao mesmo tempo.
Imagino que isso possa parecer óbvio demais para quem já conhece bem orientação a objetos. Entretando, muitos aprendem java sem necessáriamente aprofundar sobre OO. Quase sempre por isso, sentirá dificuldade em aprender e aplicar certos pontos sobre Java que estiver nesta situação.

Vejamos então sobre estes últimos modificadores ....

strictfp”: Palavra reservada que pode ser usada para modificar uma classeou um método, mas nunca uma variável. Marcar uma classe como “strictfp” significa que qualquer código de método na classe será compatível com às regras do padrão IEEE 754 para pontos flutuantes. Sem esse modificador, os pontos flutuantes usados nos métodos poderão se comportar de forma variante conforme a plataforma. Se não declarar a classe como strictfp, ainda assim você poderá obter um comportamento strictfp para métodos específicos, declarando o método como strictfp.

final”: Define as “Classes Finais” - a palavra-chave “final” significa que a classe em questão não pode ser subclassificada. Ou seja não pode ser estendida, herdada por nenhuma outra. Nenhuma outra classe jamais poderá estender uma classe final. Caso alguém tente fazê-Io receberá um erro de compilação.
Tenha cuiadado ao usar esse tipo de modificados, você só deve marcar uma classe como final se possuir certeza absoluta de que numca vai precisar especializá-la. Nunca nenhum dos métodos dessa classe jamais vai ser sobrescrito.
Por que, então, precisamos marcar uma classe como “final”. Visto que, isso restringe aplicação do conceito de herança presente nas linguagens orientadas a objetos? Suponhamos que o comportamento de sua classe seja profundamente dependente da implementação de certos métodos. Por isso você considere que se esses, certos, métodos forem alterados seja impossível garantir um comportamento adequado a classe. Logo, marcá-la como “final” lhe dará a segurança de que ninguém poderá modificar a implementação dos mesmos sem você saber.
Note que muitas classes das bibliotecas fundamentais Java são declaradas como “final”. Por exemplo, a classe String não pode ser estendida. Não é difícil considerar os riscos caso você não pudesse garantir a forma como um objeto String funcionaria em qualquer sistema que executasse a sua aplicação. Você concorda comigo que, se os programadores tivessem a liberdade de estender a classe String (e assim colocar as suas novas instâncias da subclasse String onde esperam-se instâncias de java.lang.String), dar manutenção em muitos sistemas Java seria a principal causa dos picicóticos maniácos depressivos internados nos centros psquiátricos da vida?
Portanto, use final para segurança, mas apenas quando tiver certeza de que a sua classe final de fato já disse tudo o que precisa ser dito nos seus métodos. Marcar uma classe como final significa, na prática,que a sua classe nunca será especializada para uso mais específico.

Asseguir vamos modificar nosso exemplo anterior colocando a palavra reservada “final” na declaração:

package fermentadosZN;

public final class VinhoZn {

public void fermentar(){}
}


Ok, supondo que tentassemos compilar a subclasse ConhacZn:


package destiladosZN;

import fermentadosZN.VinhoZn;

class ConhacZn extends VinhoZn { }


Então, receberemos um erro tipo:

Can't subclass final classes: class
fermentadosZN.VinhoZn class ConhacZn extends VinhoZn{
1 error}


OBS:
É de extrema importância entender que marcar uma classe como “final” elimina um benefício fundamental da orientação a objeto, a estensibilidade (herança). No dia-a-dia, tipo vida real, você raramente precisará criar uma classe final. Portanto, exceto que você tenha uma séria e justificada razão, presuma sempre que algum dia, em algum momento, outro desenvolvedor poderá precisar estender a sua classe.

abstract: Palavra reservado para marcar classes como “Abstratas”. Uma classe marcada com “abstract” não pode ser instanciada nunca. Isso é a primeira coisa mais importante que você precisa saber sobre classes abstratas. A segunda coisa mais importante sobre elas é que, o seu único propósito, sua principal missão no universo, é ser estendida (Herdada, especialisada, em algumas literaturas encontraremos o termo “subclassificada”). Imagino que alguém possa estar se perguntando: Se posso fazer herança a partir de qualquer classe (exceto as marcadas como “final”), por que preciso de uma classe abstrata? A resposta consiste na mesma idéia envolvida quando, por exemplo, ao nos referirmos sobre um determinado veículo o fazemos usando o termo "Automóvel".
Uma classe abstrata pode ser executada, mas não podemos criar uma instância dela. Caso essa explicação tenha deixado dúvidas procure ler outros artigos onde eu e Felipe explicamos sobre herança.

Outras questões sobre classes abstratas

Repare que você pode compilar e executar uma classe abstrata, desde que não tente criar uma instância dela. Por que criar uma classe se você não pode criar (instanciar) objetos dela? Antes de respondermos, gostaria de chamar a atenção que este entendimento é um elemento chave para quem pretende usar OO em seus projetos. Não acho fácil responder essa pergunta, mesmo assim tentarei apresentar uma linha de raciocínio que possa trazer mais luz sobre essa questão.
Pois bem, prossigamos: Considere um fenômeno comum o qual aplicamos recorrente mente no cotidiano em diversas situações. Esse fenômeno a que me refiro, consiste na capacidade que temos de abstrair ou especializar significados quando estamos nos relacionando com o mundo exterior: Ao tentarmos comunicar alguma mensagem, ou quando estamos do outro lado, interpretando um conteúdo comunicado, ou quando estamos construindo uma idéia sobre alguma coisa e precisamos fazer avaliações e comparações. Entendo que o grande benefício da utilização de classes abstratas, é permitir que possamos “importar” essa flexibilização para perceber significados, usada nos processos cognitivos (construção e interpretação de informações) para a computação (especificamente na construção de programas).
Analisemos uma situação hipotética, um exemplo: Num dado momento ao falar sobre música, uma pessoa, usa o termo “composições” para abstrair detalhes sobre autor, estilo, ou versão:

“Gostaria de conhecer mais sobre as composições musicais no século passado”.

Esse tipo de construção permitiu um grau de abstração grande sobre o significado “Música”. Isso lhe foi bastante útil porque ela poder expressar de forma eficaz seu desejo em conhecer qualquer estilo de música que tenha sido composta no século passado. Entretanto, se ela falar especificamente numa música o termo genérico não encontra significância. Ou seja, não é possível apresentar nenhum exemplo de música onde os detalhes sobre o estilo, gênero, autor, versão sejam ausentes. Por exemplo, em resposta a pergunta sobre uma música que atenda o critério de “composição do século passado” alguém cante “Born to Be Wild”, a resposta traz intrínseca as informações sobre os detalhes que a identificam univocamente como tal.

Outro exemplo, supondo que alguém cite genericamente o significado “veículo”. Logo, podemos presumir podermos contar com alguma outras coisas (atributos e ações), genéricas, comuns a todos os veículos. Como no mundo real não conseguimos encontrar um exemplo qualquer de veículo, pois, seguindo o raciocínio anterior, ao citar o exemplo pedido imediatamente os detalhes sobre ele se revelam exaurindo a generalização embutida na palavra “veículo”, trazendo a existência uma instância detalhada do significado pedido. Portanto, da mesma forma quando programando, definimos uma classe “Veiculo” ela não deve permitir ser instanciada. Pois, definitivamente não desejamos que alguém de fato crie um objeto “Veiculo” genérico. Caso isso não fosse respeitado o primeiro e principal problema encontrado é justamente que o mecanismo de generalização descrito na parágrafo anterior seria perdido. Conseqüentemente teríamos alguns problemas: Como inicializar o seu estado deste objeto “Veiculo”? De que cor ele seria? Quantos assentos? Potência do motor? Direção hidráulica ou não? Ainda, de que forma ele se comportaria? Como os métodos seriam implementados?
Isso demandaria um esforço considerável, o que tornaria o desenvolvimento do seu módulo bastante suscetível implementações confusas. Quando na verdade que você precisa é as instancias de sua classe “Veiculo”sejam tipos reais de carros, como BMW Boxster, ou Nisan 350Z. Observe a seguinte classe abstrata:


abstract class vehicle {
private double price;
private String model;
private String year;
private String manufacturer;
public abstract void goFast ();
public abstract void goUpHill ();
public abstract void impressNeighbors ();
// codificar o resto do código importante aqui

}


3)Regras de Declaração para Arquivos-Fontes

Já que estamos tratando de declarações e controles de acesso de classes, julgo então que este é um momento propício para fazermos uma breve revisão das regras pertinentes a declaração de classes, declarações import e declarações de package em um arquivo fonte:


1° - Só pode haver uma classe com escopo de visibilisdade “public” em cada arquivo de código-fonte.

2° - Os comentários podem aparecer no início ou no fim de qualquer linha no arquivo do código-fonte. Eles são independentes de qualquer das regras de posicionamento.

3° - Se houver uma classe pública em um arquivo, o nome do arquivo deve ser o mesmo desta classe . Por exemplo, uma classe declarada como “public class ZnVinho { }” precisa estar em um arquivo de código-fonte chamado “ZnVinho. java.”

4° - Se a classe fizer parte de um pacote, a declaração package deve estar na primeira linha do arquivo do código-fonte, antes de quaisquer declarações import que estejam presentes.

5° - Se houver declarações “import”, elas devem ficar entre a declaração package (se houver) e a declaração da classe. Se não houver uma declaração package, então a(s) declaração(ões) “import” deve(m) estar na(s) primeira(s) linha(s) do arquivo fonte. Se não houver declarações package nem import, então a declaração da classe deve estar na primeira linha do arquivo do fonte.

6° - As declarações import e package aplicam-se a todas as classes dentro de um arquivo fonte. Ou seja, não é possível declarar múltiplas classes em um arquivo e tê-la sem diferentes pacotes, ou usar diferentes importações.

7° - Um arquivo pode ter mais de uma classe não-pública.

8° - Arquivos que não tenham classes públicas podem ter um nome que não seja o mesmo de nenhuma das classesdo arquivo.


Posteriormente, aprofundaremos mais sobre as regras envolvidas com a declaração e o uso de importações, pacotes. Além disso, pretendo falar embreve sobre importações estáticas.

Referência:
SCJP: Certificação Sun para Programador Java 5 - Guia de Estudo

Artigo completo (View Full Post)

domingo, 15 de junho de 2008

Abordagem OO Java - Introdução

Recorrentemente temos abordado POO aqui no blog. Nosso foco maior tem sido Delphi e C#, contudo sempre que podemos procuramos ampliar nossos exemplos experimentando outras linguagens. Vamos falar um pouco desse contexto no mundo Java.

Breve revisão sobre Java

Um programa em Java é sempre uma coleção de objetos comunicando-se com outros. Comunicação essa que acontece mediante a chamada de métodos (evocação de métodos) uns dos outros. Nunca esquecendo, de ter sempre em mente que cada objeto é de um determinado tipo, definido por uma classe ou interface. Não desprezado o fato dessa coleção de objetos, a qual mencionei anteriormente, ser quase sempre composta por objetos de uma variedade diferente de tipos. Vejamos as seguintes definições sobre OO (Estou usando como referência para este artigo o livro da Kathy Sierra, certificação Sun (Java 5), primeiro capítulo):


Classe:
Um modelo que descreve os tipos, de estado e de comportamento, que os objetos do seu tipo podem ter.

Permita-me um comentário: Particularmente, eu acho essa definição satisfatória para quem já conhece bem orientação a objetos. Contudo, para quem esta aprendendo programação ou vem de uma cultura de programação estruturada, duvido que o parágrafo anterior agregue algum valor, no que tange o processo cognitivo de quem o está lendo. Em outros artigos eu e o Felipe usamos outras abordagens para este conceito. Sugiro que você complemente o entendimento lendo as demais definições, principalmente se você ainda esta dando os primeiros passos em OO.

Objeto:
No momento da execução do programa, quando a JVM (Java Virtual Machine) encontra a palavra reservada “new” ela cria uma instância da classe, referenciada no trecho de código corrente. A isso chamamos de criar um objeto (em outras palavras: instanciar um objeto). Esse objeto recém criado terá seu próprio estado, bem como um comportamento. O qual será expressado como reflexo de todas as funcionalidades, definidas por métodos na classe, que são dinamicamente acessíveis ao objeto.
Quem são essas classes acessíveis ao objeto??? Isso se refere a herança. Um objeto cujo tipo é, por exemplo, uma classe “X” terá acesso ao comportamento de todos os ancestrais dela. Ou seja, ele poderá se comportar como a classe “X”, a classe “XPai”, bem como a classe “XAvo” e assim sucessivamente até o ancestral mais alto (o qual chamamos de super classe). Falaremos mais sobre herança adiante.

Estado:
Variáveis de instância – Cada objeto (que por sua vez é uma instância de uma classe) terá seu conjunto único de variáveis de instância conforme definido na classe (definidos pelos atributos da classe). Em contra partida, coletivamente os valores atribuídos as variáveis de instância de um objeto, expressam o estado do mesmo. Ou, os valores persistidos pelas propriedades do objeto, expressam o estado do mesmo.
Ou seja, o conjunto de atributos do objeto definirá, a partir dos valores definidos neles, o estado desse objeto. Por exemplo: Suponha que temos um objeto “Lâmpada”, esse objeto é possuidor de um conjunto de atributos: Potência, Voltagem, Cor e etc. Devemos considerar que este objeto, além desses, possua outro atributo que definirá se “Lâmpada” esta acesa ou não. Esse atributo pode se chamar “Ativa”, ele indica se ela está acessa (ou seja, ativa) ou não. Logo, quando todos esses atributos possuírem valor, exemplo: Lâmpada.Cor = Amarela, Lâmpada.Voltagem = 110, Lâmpada.Potência = 100W e Lâmpada.Activa = Verdadeiro. Posso agora então conhecer o estado desse objeto.

Comportamento:
Quando codificamos uma classe, e implementamos os métodos dela, estamos definido a lógica da classe. “É nos métodos que a lógica da classe é armazenada” (Achei essa definição muito apropriada). Através dos métodos, o verdadeiro trabalho da classe é realizado. Ou seja, a razão de ser da classe, onde seu principal objetivo é definido. Neles e por meio deles, algoritmos são executados e os dados são manipulados.

Herança:
Característica da orientação a objetos que permite o reaproveitamento do código de uma classe por outra. Em Java é possível definir uma superclasse “genérica” (mais abstrata) e em seguida estendê-la criando outras classes mais específicas. A superclasse não tem conhecimento algum sobre as classes que descendem dela. Em contra partida, essas classes herdeiras precisam declarar explicitamente a relação de herança. Como efeito disso, uma subclasse que herda de uma superclasse “recebe automaticamente”, digamos assim, as variáveis de instância acessíveis, bem como os métodos definidos na superclasse. Além disso possui autonomia para redefinir os métodos herdados da superclasse, a fim de expressar um comportamento mais específico (este fenômeno chamamos de sobrescrita de método. Ou seja uma subclasse pode sobrescrever os métodos herdados).

Identificando Classes (Análise)
Em concordância com o que mencionei anteriormente, é extremamente importante ser obstinado em alcançar um grau de coesão alto na classe que você está definindo. Isso implica diretamente que toda classe deve ter muito bem definido o conjunto de responsabilidades pertinentes ao conceito do mundo real o qual ela representará para um sistema. Por exemplo, imagine um programa cujo objetivo principal é simular uma orquestra: Certamente encontraremos a representação de instrumentos musicais por algumas classes, bem como músicos, partituras e composição musical por outras tantas. Concluir isso é bastante elementar. Contudo, um erro fatal (e na minha opinião muito freqüente) seria definir, em virtude de um equívoco de análise, o comportamento (ou parte dele) de uma classe em outra, ou vice-versa. Por exemplo: A classe instrumentista possuir um método que retorne o compasso corrente. Creeeeeddddooooo!??!?!?!? Visto que, compasso é um atributo da classe partitura. Um programa em Java é constituído tanto por classes construídas pelo programador, quanto por e outras que não (por exemplo, classes Java API). Em Java as classes são organizadas em pacotes, os quais devemos referenciar mediante a palavra reservada “import” a fim de usarmos suas classes em nossos programas. Dessa forma a linguagem fornece uma forma consistente do desenvolvedor nomear, bem como obter acesso, as classes que necessitar. No exame de certificação Java a avaliação aborda muitos dos conceitos relacionados aos pacotes e acesso a classes. Posteriormente detalharemos mais esses temas.

Interfaces:
É uma espécie de superclasse cem por cento abstrata que define os métodos que uma subclasse deve suportar, mas não define como esse método deve se comportar (ou seja, não define come deve ser implementado). Exemplificando, uma interface "Instrumento Musical" pode declarar que todas as as classes de implementação de "Instrumento Musical" (estendidas dela) possuirão um método "reproduzirSom()". Contudo, a interface "Instrumento Musical" não fornece nenhuma lógica para para este método. Isso implica que a classe que implementar "Instrumento Musical" terá a responsabilidade de definir o comportamento dos métodos definidos na interface. Determinando então, desta forma, como cada instrumento irá se comportar particularmente.
Venho sentindo a necessidade de escrever sobre interface não é de hoje, entretanto ainda não consegui preparar um material que me agrade. Esse assunto esta na fila e considero extremamente pertinente.

Artigo completo (View Full Post)

quarta-feira, 27 de fevereiro de 2008

MethodAddress

Método definido em TObject, retorna o endereço de um método published. Ou seja, um método definido na seção “published”. Entretanto, evocar o “MethodAddress” não é algo tão trivial. É preciso criar uma variável do tipo “procedure” que receberá o ponteiro para o método retornado por “MethodAddress”.
Exemplo 1:


unit Unit1;

interface

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

type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
procedure BitBtn1Click(Sender: TObject);
private

public

published
procedure MyZnMethod;
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject);
var
AuxProcedure: Procedure(Sender: TObject);
begin
AuxProcedure := Self.MethodAddress('MyZnMethod');
AuxProcedure(Self);
end;

procedure TForm1.MyZnMethod;
begin
Self.Caption := 'Estação ZN';
Self.Color := clAqua;
Self.Update;
Self.Canvas.TextOut(12, 130, 'Dummy MethodAddress Teste Estação ZN!!!');
end;

end.



Ok, muito simples! Só que se eu desejar evocar um método que está definido em outra classe terei de usar uma estratégia, pois o método MethodAddress, só conhece os métodos definidos na própria classe. Para exemplificar, vamos criar uma classe ‘TMyZnClassDummy’ e no mesmo evento OnClick do BitBtn1 evocar o método “Dummy” da nova classe, “TMyZnClassDummy”. Exemplo2:


unit Unit1;

interface

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

type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
procedure BitBtn1Click(Sender: TObject);
private

public

ppublished
procedure MyZnMethod;
end;

type
TMyZnClassDummy = Class(TComponent)
private

protected
published
procedure Dummy;
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject);
var
AuxProcedure: Procedure(Sender: TObject);
AuxProcedureBlah: Procedure(Sender: TObject);
begin
AuxProcedure := Self.MethodAddress('MyZnMethod');
AuxProcedure(Self);

(*Testando o Blah*)
ShowMessage('Testando o Blah!!');
AuxProcedureBlah := MethodAddress('Dummy');
AuxProcedureBlah(Self)
end;

procedure TForm1.MyZnMethod;
begin
Self.Caption := 'Estação ZN';
Self.Color := clAqua;
Self.Update;
Self.Canvas.TextOut(12, 130, 'Dummy MethodAddress Teste Estação ZN!!!');
end;

{ TMyZnClassDummy }

procedure TMyZnClassDummy.Dummy;
begin
ShowMessage(Self.Name + ' Blah!!!!');
end;

end.


Como eu posso fazer para conseguir executar o método “Dummy”? Eu preciso (primeiro, se for o caso fazer uses da unit onde a classe, cujo o método desejo evocar, se encontra) criar na classe que vai chamar através do “MethodAddress” um outro método pblished, este por sua vez chamara o método definido na outra classe. Vou Criar no Form1 um método que evocará o método “Dummy” da classe “TMyZnClassDummy”.Exemplo:

unit Unit1;

interface

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

type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
procedure BitBtn1Click(Sender: TObject);
private

public

published
procedure MyZnMethod;
procedure ExeucteDummy;
end;

type
TMyZnClassDummy = Class(TComponent)
private

protected
published
procedure Dummy;
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject);
var
AuxProcedure: Procedure(Sender: TObject);
AuxProcedureBlah: Procedure(Sender: TObject);
begin
AuxProcedure := Self.MethodAddress('MyZnMethod');
AuxProcedure(Self);

(*Testando o Blah*)
ShowMessage('Testando o Blah!!');
(* Executo o Método procedure do Form1 "ExeucteDummy" *)
AuxProcedureBlah := MethodAddress('ExeucteDummy');
AuxProcedureBlah(Self)
end;

procedure TForm1.ExeucteDummy;
var
MyZnClassDummy: TMyZnClassDummy;
begin
MyZnClassDummy := TMyZnClassDummy.Create(Self);
try
MyZnClassDummy.Dummy;
finally
MyZnClassDummy.Free;
end;

end;

procedure TForm1.MyZnMethod;
begin
Self.Caption := 'Estação ZN';
Self.Color := clAqua;
Self.Update;
Self.Canvas.TextOut(12, 130, 'Dummy MethodAddress Teste Estação ZN!!!');
end;

{ TMyZnClassDummy }

procedure TMyZnClassDummy.Dummy;
begin
ShowMessage(Self.ClassName + ' Blah!!!!');
end;

end.

Baseado nisso, podemos criar um catálogo de métodos de classes distintas, os quais poderão ser exibidos numa interface, permitindo ao usuário uma forma de setup da aplicação. De maneira que ele possa definir que funcionalidade será executada a partir de uma determinada ação na interface. Sei que esse tipo de abordagem não é nada convencional, contudo acredito ela permita um flexibilização poderosa para algumas situações.


"A mente que se abre a uma nova idéia jamais voltará ao seu tamanho original". Albert Estein

Artigo completo (View Full Post)

terça-feira, 26 de fevereiro de 2008

Construção de Componentes - VI

Seguindo a mesma linha do último exemplo de criação de componentes, para consolidar os pontos discutidos, construiremos outro exemplo bem semelhante ao anterior mudando um pouco a abordagem quanto a funcionalidade do componente.
O principal objetivo deste será oferecer um solução abstrata para uma funcionalidade tipo “Localizar” (tipo um “find”) para um conjunto de registros listados num DBGrid. Basta clickar com o botão direito do mouse na coluna a qual se deseja fazer a busca, o componente dinamicamente cria um menu popup, com um item cujo a descrição seja “Focalizar [título da coluna]”. Efetuado o click sobe o item de menu, o sistema exibe uma janela de diálogo solicitando digitação do valor para busca e em seguida efetua a busca. Localizando no Grid o dado desejado.


Descrição da funcionalidade:

  1. Usuário clicka com o botão direito do mouse sobre a coluna do Grid, a qual deseja localizar um registro.
  2. Sistema exibe o popup menu com um item de localizar.
  3. Usuário clicka na opção de localizar.
  4. Sistema Exibe tela de diálogo para usuário digitar o valor a ser localizado.
  5. Usuário digita o valor e clicka em “Ok”, confirmando a solicitação de localizar.
  6. Sistema localiza, focando a célula onde se encontra o registro desejado.
Construindo o Componente

Se você não sabe iniciar, no Delphi, a construção de um componente siga as instruções dos artigos anteriores.

Codifique conforme código abaixo a classe TFinderDataZN.


unit FinderDataZN;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, DbGrids, ADODb, Db, Menus;

type
(* Para indicar a Classe se o Popup Menu foi outo-criado, ou já estava associado
ao DbGrid que "foi" associado ao TFinderDataZN *)
TPopupExistent = (peExternalAssociation, peInternalCreated, peInexistent);

TFinderDataZN = class(TComponent)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
Property ReturnValueStr: String read FReturnValueStr;
Property ReturnValueNum: Double read FReturnValueNum;
property Grid: TDBGrid read FGrid write SetGrid;
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents('EstacaoZN', [TFinderDataZN]);
end;

end.


Codificando a principal propriedade “SetGrid”. Isto porque, essa propriedade vai preparar o Grid e configurar a classe para seu principal fim. Observe que no método “BuildMenuPopup” vou atribuir um evento ao OnClick do item de menu dinamicamente, da mesma forma que fizemos no artigo anterior, contudo neste momento vou deixar essa atribuição comentada. Pois, pretendo implementar a procedure “LocalizarClick” mais adiante. Preste a atenção na linha “/MyMenuitem.OnClick := LocalizarClick”, do método “BuildMenuPopup”, ela estará comentada. O mesmo serve para o método “SetPopupMenu”, visto que o evento PopupMenuPopup, o qual atribuirei ao evento “OnPopup” do Popup Menu associado ao Grid.


unit FinderDataZN;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, DbGrids, ADODb, Db, Menus;

type
(* Para indicar a Classe se o Popup Menu foi outo-criado, ou já estava associado
ao DbGrid que "foi" associado ao TFinderDataZN *)
TPopupExistent = (peExternalAssociation, peInternalCreated, peInexistent);

TFinderDataZN = class(TComponent)
private
FReturnValueNum: Double;
FReturnValueStr: String;
FGrid: TDBGrid;
FADataSet: TCustomADODataSet;
FPopupMenu: TPopupMenu;
FIsPopupExistent: TPopupExistent;
FSelectedColumn: TColumn;
FAFieldSearch: TField; // Será usado para setar foco

procedure SetGrid(const Value: TDBGrid);
procedure SetADataSet(const Value: TCustomADODataSet);
procedure SetPopupMenu(const Value: TPopupMenu;
const nAllowCreate: Boolean = False);

protected
procedure CreatePopupMenuItem(const NameItem,
CaptionItem: String); virtual;
procedure BuildMenuPopup; virtual;
procedure SetSelectedColumn(const Value: TColumn); virtual;
function GetSelectedColunm: TColumn; virtual;
public
{ Public declarations }
published
Property ReturnValueStr: String read FReturnValueStr;
Property ReturnValueNum: Double read FReturnValueNum;
property Grid: TDBGrid read FGrid write SetGrid;
end;

procedure Register;

implementation

const
PopupMenuItemName: array [0..1] of String = ('ConfigurarGridSep',
'fdLocalizar');
AMenuItemCaption = 'Localizar %s';

procedure Register;
begin
RegisterComponents('EstacaoZN', [TFinderDataZN]);
end;

{ TFinderDataZN }

procedure TFinderDataZN.BuildMenuPopup;
var
i: Integer;
MyMenuitem: TMenuItem;
begin

(* Verifco se o componente encontra-se em run time*)
if not (csDesigning in Self.ComponentState) then
begin
if Assigned(FPopupMenu) then
begin
for i := 0 to High(PopupMenuItemName) do
begin
if (FPopupMenu.Items.Count > 0) and (i = 0) then
CreatePopupMenuItem(PopupMenuItemName[i], '-');

if (i = 1) then
CreatePopupMenuItem(PopupMenuItemName[i],
Format(AMenuItemCaption, [FGrid.Columns[0].Title.Caption]));
end;

MyMenuitem := TMenuItem(Self.FindComponent(PopupMenuItemName[1]));
if assigned(MyMenuitem) then
//MyMenuitem.OnClick := LocalizarClick;

end;
end;
end;

procedure TFinderDataZN.CreatePopupMenuItem(const NameItem,
CaptionItem: String);
var
MyMenuitem: TMenuItem;
begin
MyMenuitem := TMenuItem(Self.FindComponent(NameItem));
if not assigned(MyMenuitem) then
begin
MyMenuitem := TMenuItem.Create(Self);
with MyMenuitem do
begin
Name := NameItem;
Caption := CaptionItem;
end;
FPopupMenu.Items.Add(MyMenuitem);
end;
end;

function TFinderDataZN.GetSelectedColunm: TColumn;
var
i: Integer;
AuxField: TField;
begin
Result := nil;
for i := 0 to Pred(FGrid.Columns.Count) do
begin
AuxField := FGrid.Columns[i].Field;
if Assigned(AuxField) then
if AuxField.FieldName = FGrid.SelectedField.FieldName then
Result := FGrid.Columns[i];
if Assigned(Result) then Break;
end;

end;

procedure TFinderDataZN.SetADataSet(const Value: TCustomADODataSet);
begin
FADataSet := Value;
end;

procedure TFinderDataZN.SetGrid(const Value: TDBGrid);
begin
if FGrid <> Value then
begin
FGrid := Value;

if FGrid <> nil then
begin
FGrid.FreeNotification(Self);
SetSelectedColumn(GetSelectedColunm);
FADataSet := TCustomADODataSet(FGrid.DataSource.DataSet);
(* Cuida da associação, menu popup, associada ao DBGrid *)
SetPopupMenu(FGrid.PopupMenu);
(* Monta os itens de menu, os quais nos referimos
na especificação do componente *)
BuildMenuPopup;

end;
end;
end;

procedure TFinderDataZN.SetPopupMenu(const Value: TPopupMenu;
const nAllowCreate: Boolean);
begin
if (FPopupMenu = Value) and (Value <> nil) then Exit;

If not Assigned(Value) or (nAllowCreate) then
begin
FPopupMenu := TPopupMenu.Create(Self);
FGrid.PopupMenu := FPopupMenu;
FIsPopupExistent := peInternalCreated;
end
else
begin
FPopupMenu := Value;
FIsPopupExistent := peExternalAssociation;
end;

if FPopupMenu <> nil then
begin
FPopupMenu.FreeNotification(Self);
//FPopupMenu.OnPopup := PopupMenuPopup;
end;
end;

procedure TFinderDataZN.SetSelectedColumn(const Value: TColumn);
begin
(* Armazena a coluna do Grid aqual foi clickada com o
botão direito do mouse, para capturar informações como: Titúlo e etc.. *)
FSelectedColumn := Value;

if FSelectedColumn <> nil then
FAFieldSearch := FSelectedColumn.Field;
end;

end.




Codificando as procedures que serão atribuídas aos eventos :
“procedure LocalizarClick(Sender: TObject)” para efetuará a localização do registro desejado.

“procedure PopupMenuPopup(Sender: TObject) ”, para criar o caption, o título do item de menu, referente ao registro o qual será localizado.

protected
procedure CreatePopupMenuItem(const NameItem,
CaptionItem: String); virtual;
procedure BuildMenuPopup; virtual;
procedure SetSelectedColumn(const Value: TColumn); virtual;
function GetSelectedColunm: TColumn; virtual;
(* Eventos que serão atribuidos dinamicamente *)
procedure LocalizarClick(Sender: TObject); virtual;
procedure PopupMenuPopup(Sender: TObject);


Primeiro a implementação da nova procedure que vai setar o título o item de menu dinamicamente de acordo com o título da coluna do Grid:



procedure TFinderDataZN.PopupMenuPopup(Sender: TObject);
var
MyMenuitem: TMenuItem;
begin
MyMenuitem := TMenuItem(Self.FindComponent(PopupMenuItemName[1]));
if assigned(MyMenuitem) then
begin
SetSelectedColumn(GetSelectedColunm);
MyMenuitem.Caption := Format(AMenuItemCaption, [FSelectedColumn.Title.Caption]);
end;
end;


A funcionalidade de Localizar precisa exibir uma janela (tela) para que o usuário digite o valor desejado. Logo, precisamos, antes de codificar criarmos um novo form. Adicione a classe um novo Form, propriedade name = “FindDataDlgFormZN”, salve a unit com o nome de “FindDataDlgFrmZN”. Segue o código do dfm do form:

object FindDataDlgFormZN: TFindDataDlgFormZN
Left = 192
Top = 107
Width = 189
Height = 138
Caption = 'Localizar %s'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 12
Top = 12
Width = 126
Height = 13
Caption = 'Digite o Código do Serviço'
end
object EdtDado: TEdit
Left = 12
Top = 33
Width = 159
Height = 21
TabOrder = 0
end
object PnlBottom: TPanel
Left = 0
Top = 67
Width = 181
Height = 44
Align = alBottom
BevelOuter = bvNone
TabOrder = 1
object BtnCancel: TBitBtn
Left = 100
Top = 12
Width = 75
Height = 25
TabOrder = 0
Kind = bkCancel
end
object BtnOk: TBitBtn
Left = 12
Top = 12
Width = 75
Height = 25
TabOrder = 1
Kind = bkOK
end
end
end

No form codifique o evento OnCloseQuery e um método público “Execute” conforme exemplificado abaixo:

unit FindDataDlgFrmZN;

interface

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

type
TFindDataDlgFormZN = class(TForm)
Label1: TLabel;
EdtDado: TEdit;
PnlBottom: TPanel;
BtnCancel: TBitBtn;
BtnOk: TBitBtn;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
{ Private declarations }
public
function Execute: Boolean;
end;

var
FindDataDlgFormZN: TFindDataDlgFormZN;

implementation

{$R *.dfm}

procedure TFindDataDlgFormZN.FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
begin
if (Self.ModalResult = MrOk) and (Trim(EdtDado.Text) = '') then
ModalResult := mrCancel;
end;

function TFindDataDlgFormZN.Execute: Boolean;
begin
Self.BorderStyle := bsDialog;
Self.FormStyle := fsNormal;
Self.Position := poMainFormCenter;

Result := (Self.ShowModal = mrOk);

end;

end.


Implementado o procedimento que vai gerenciar a localização do registro “function InputSearchValue”.

protected
procedure CreatePopupMenuItem(const NameItem,
CaptionItem: String); virtual;
procedure BuildMenuPopup; virtual;
procedure SetSelectedColumn(const Value: TColumn); virtual;
function GetSelectedColunm: TColumn; virtual;
(* Eventos que serão atribuidos dinamicamente *)
procedure LocalizarClick(Sender: TObject); virtual;
procedure PopupMenuPopup(Sender: TObject);
(*método que vai gerenciar a localização do registro *)
function InputSearchValue: String; virtual;
public
{ Public declarations }
published
Property ReturnValueStr: String read FReturnValueStr;
Property ReturnValueNum: Double read FReturnValueNum;
property Grid: TDBGrid read FGrid write SetGrid;
end;


Adicione na clausula “uses” da seção “implamentation” a unit da classe “TFinderDataZN”, a unit do form que acabamos de criar. Na seção “private” defina um novo campo FAFieldSearch: TField;

procedure Register;

implementation

uses FindDataDlgFrmZN;

const
PopupMenuItemName: array [0..1] of String = ('ConfigurarGridSep',
'fdLocalizar');
AMenuItemCaption = 'Localizar %s';



var
FindDataDlgForm: TFindDataDlgForm;
AuxCaption: String;
begin

if (FADataSet.IsEmpty) then Exit;


try
FindDataDlgForm := TFindDataDlgForm.Create(Self);

FindDataDlgForm.Caption := Format(FindDataDlgForm.Caption,
[FSelectedColumn.Title.Caption]);

FindDataDlgForm.Label1.Caption := FSelectedColumn.Title.Caption;

if FindDataDlgForm.Execute then
begin
Result := FindDataDlgForm.EdtDado.Text;
if FADataSet.Locate(FSelectedColumn.Field.FieldName, FindDataDlgForm.EdtDado.Text, []) then
begin
FAFieldSearch.FocusControl;
if FGrid.CanFocus then
FGrid.SetFocus
end;
end;

finally
FindDataDlgForm.Free;
end;


Agora, para finalizar , o método “LocalizarClick”.


procedure TFinderDataZN.LocalizarClick(Sender: TObject);
var
AValue: String;
AValueNum: Double;
begin

try
AValue := InputSearchValue;
except
on Exception do
AValue := '';
end;

end;


Não esqueça de descomentar as linhas dos métodos “BuildMenuPopup” e “SetPopupMenu”.

Segue o código completo do componente:

unit FinderDataZN;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, DbGrids, ADODb, Db, Menus;

type
(* Para indicar a Classe se o Popup Menu foi outo-criado, ou já estava associado
ao DbGrid que "foi" associado ao TFinderDataZN *)
TPopupExistent = (peExternalAssociation, peInternalCreated, peInexistent);

TFinderDataZN = class(TComponent)
private
FReturnValueNum: Double;
FReturnValueStr: String;
FGrid: TDBGrid;
FADataSet: TCustomADODataSet;
FPopupMenu: TPopupMenu;
FIsPopupExistent: TPopupExistent;
FSelectedColumn: TColumn;
FAFieldSearch: TField;

procedure SetGrid(const Value: TDBGrid);
procedure SetADataSet(const Value: TCustomADODataSet);
procedure SetPopupMenu(const Value: TPopupMenu;
const nAllowCreate: Boolean = False);

protected
procedure CreatePopupMenuItem(const NameItem,
CaptionItem: String); virtual;
procedure BuildMenuPopup; virtual;
procedure SetSelectedColumn(const Value: TColumn); virtual;
function GetSelectedColunm: TColumn; virtual;
(* Eventos que serão atribuidos dinamicamente *)
procedure LocalizarClick(Sender: TObject); virtual;
procedure PopupMenuPopup(Sender: TObject);
(*método que vai gerenciar a localização do registro *)
function InputSearchValue: String; virtual;
public
{ Public declarations }
published
Property ReturnValueStr: String read FReturnValueStr;
Property ReturnValueNum: Double read FReturnValueNum;
property Grid: TDBGrid read FGrid write SetGrid;
end;

procedure Register;

implementation

uses FindDataDlgFrmZN;

const
PopupMenuItemName: array [0..1] of String = ('ConfigurarGridSep',
'fdLocalizar');
AMenuItemCaption = 'Localizar %s';

procedure Register;
begin
RegisterComponents('EstacaoZN', [TFinderDataZN]);
end;

{ TFinderDataZN }

procedure TFinderDataZN.BuildMenuPopup;
var
i: Integer;
MyMenuitem: TMenuItem;
begin

(* Verifco se o componente encontra-se em run time*)
if not (csDesigning in Self.ComponentState) then
begin
if Assigned(FPopupMenu) then
begin
for i := 0 to High(PopupMenuItemName) do
begin
if (FPopupMenu.Items.Count > 0) and (i = 0) then
CreatePopupMenuItem(PopupMenuItemName[i], '-');

if (i = 1) then
CreatePopupMenuItem(PopupMenuItemName[i],
Format(AMenuItemCaption, [FGrid.Columns[0].Title.Caption]));
end;

MyMenuitem := TMenuItem(Self.FindComponent(PopupMenuItemName[1]));
if assigned(MyMenuitem) then
MyMenuitem.OnClick := LocalizarClick;

end;
end;
end;

procedure TFinderDataZN.CreatePopupMenuItem(const NameItem,
CaptionItem: String);
var
MyMenuitem: TMenuItem;
begin
MyMenuitem := TMenuItem(Self.FindComponent(NameItem));
if not assigned(MyMenuitem) then
begin
MyMenuitem := TMenuItem.Create(Self);
with MyMenuitem do
begin
Name := NameItem;
Caption := CaptionItem;
end;
FPopupMenu.Items.Add(MyMenuitem);
end;
end;

function TFinderDataZN.GetSelectedColunm: TColumn;
var
i: Integer;
AuxField: TField;
begin
Result := nil;
for i := 0 to Pred(FGrid.Columns.Count) do
begin
AuxField := FGrid.Columns[i].Field;
if Assigned(AuxField) then
if AuxField.FieldName = FGrid.SelectedField.FieldName then
Result := FGrid.Columns[i];
if Assigned(Result) then Break;
end;

end;

function TFinderDataZN.InputSearchValue: String;
var
FindDataDlgForm: TFindDataDlgFormZN;
AuxCaption: String;
begin

if (FADataSet.IsEmpty) then Exit;


try
FindDataDlgForm := TFindDataDlgFormZN.Create(Self);

FindDataDlgForm.Caption := Format(FindDataDlgForm.Caption,
[FSelectedColumn.Title.Caption]);

FindDataDlgForm.Label1.Caption := FSelectedColumn.Title.Caption;

if FindDataDlgForm.Execute then
begin
Result := FindDataDlgForm.EdtDado.Text;
if FADataSet.Locate(FSelectedColumn.Field.FieldName, FindDataDlgForm.EdtDado.Text, []) then
begin
FAFieldSearch.FocusControl;
if FGrid.CanFocus then
FGrid.SetFocus
end;
end;

finally
FindDataDlgForm.Free;
end;
end;

procedure TFinderDataZN.LocalizarClick(Sender: TObject);
var
AValue: String;
AValueNum: Double;
begin

try
AValue := InputSearchValue;
except
on Exception do
AValue := '';
end;

end;

procedure TFinderDataZN.PopupMenuPopup(Sender: TObject);
var
MyMenuitem: TMenuItem;
begin
MyMenuitem := TMenuItem(Self.FindComponent(PopupMenuItemName[1]));
if assigned(MyMenuitem) then
begin
SetSelectedColumn(GetSelectedColunm);
MyMenuitem.Caption := Format(AMenuItemCaption, [FSelectedColumn.Title.Caption]);
end;

end;

procedure TFinderDataZN.SetADataSet(const Value: TCustomADODataSet);
begin
FADataSet := Value;
end;

procedure TFinderDataZN.SetGrid(const Value: TDBGrid);
begin
if FGrid <> Value then
begin
FGrid := Value;

if FGrid <> nil then
begin
FGrid.FreeNotification(Self);
SetSelectedColumn(GetSelectedColunm);
FADataSet := TCustomADODataSet(FGrid.DataSource.DataSet);
(* Cuida da associação, menu popup, associada ao DBGrid *)
SetPopupMenu(FGrid.PopupMenu);
(* Monta os itens de menu, os quais nos referimos
na especificação do componente *)
BuildMenuPopup;

end;
end;
end;

procedure TFinderDataZN.SetPopupMenu(const Value: TPopupMenu;
const nAllowCreate: Boolean);
begin
if (FPopupMenu = Value) and (Value <> nil) then Exit;

If not Assigned(Value) or (nAllowCreate) then
begin
FPopupMenu := TPopupMenu.Create(Self);
FGrid.PopupMenu := FPopupMenu;
FIsPopupExistent := peInternalCreated;
end
else
begin
FPopupMenu := Value;
FIsPopupExistent := peExternalAssociation;
end;

if FPopupMenu <> nil then
begin
FPopupMenu.FreeNotification(Self);
FPopupMenu.OnPopup := PopupMenuPopup;
end;
end;

procedure TFinderDataZN.SetSelectedColumn(const Value: TColumn);
begin
(* Armazena a coluna do Grid aqual foi clickada com o
botão direito do mouse, para capturar informações como: Titúlo e etc.. *)
FSelectedColumn := Value;

if FSelectedColumn <> nil then
FAFieldSearch := FSelectedColumn.Field;
end;

end.


Artigo completo (View Full Post)

sábado, 8 de dezembro de 2007

Construção de Componentes V – Parte B

Gerenciador Dinâmico de Layout para DBGrids

Daremos continuidade ao artigo sobre a construção de um componente que permite ao usuário configurar várias propriedades de um DBGrid em tempo de execução. Anteriormente, desenvolvemos de um pequeno exemplo da utilização do commponetne, pudemos experimentar a facilidade de usabilidade, tanto do ponto de vista do usuário, quanto do desenvolvedor. Também comentamos as vantagens obtidas decorrente da abordagem de componentização em projetos de software.

Neste artigo, trataremos da funcionalidade de retornar a visibilidade as colunas que o usuário desejou torna-las invisíveis. Recapitulando:

Fluxo alternativo B

1 – Usuário, com o botão direto do mouse, sobre o DBGrid, seleciona
Ver Colunas Escondidas”.

2 - Gerenciador de Layout de Grid exibe interface listando apenas as colunas
do DGGrid, as quais foram, pelo usuário, definidas como invisíveis
previamente. A partir das configurações originais, definidas em tempo de
projeto pelo programador, o Gerenciador de Layout de Grid deverá identificar
quais foram originalmente definidas como invisíveis para nunca permitir que
estas sejam listadas nesta interface.

3 – Usuário seleciona dentre as colunas listadas a que deseja tornar visível.
Confirmando em seguida a operação.

4 - Gerenciador de Layout de Grid grava as alterações e re-carrega o grid
com o que foi modificado.


Form SetupVisibilityColumnFrm:

Adicione ao pakage mais um TForm e manipule as propriedades do mesmo segundo listagem abaixo:

Name = SetupVisibilityColumnFrm( :TSetupVisibilityColumnFrm)
Left = 526
Top = 266
Caption = 'Visibilidade das Colunas'
ClientHeight = 560
ClientWidth = 495

Salve a unit como “SetupVisibilityColumnForm”.

Adicione ao form “SetupVisibilityColumnFrm” os componentes listados abaixo, obedecendo os valores respectivamente defindos na listagem:





1 - Name = PnlBtn (:TPanel)
Align = alBottom
BevelOuter = bvNone

Dentro do PnlBtn

1.1 Name = BtnSave: TBitBtn
Left = 12
Top = 12
Width = 75
Height = 25
TabOrder = 0
Kind = bkOK

1.2 Name = btnCancel( :TBitBtn)
Left = 392
Top = 12
Width = 75
Height = 25
TabOrder = 1
Kind = bkCancel

2 – Name = AdsVisibilityColumn ( :TADODataSet)
CursorType = ctStatic
LockType = ltBatchOptimistic

Adicione os TFields abaixo ao AdsVisibilityColumn

2.1 Name = AdsVisibilityColumnTitleColumn (:TStringField)
DisplayLabel = 'Título da Coluna'
FieldName = 'TitleColumn'
Size = 80

2.2 NAme = AdsVisibilityColumnVisibleColumn (:TBooleanField)
DisplayLabel = 'Visível'
FieldName = 'VisibleColumn'

2.3 Name = AdsVisibilityColumnFieldName (:TStringField)
FieldName = 'FieldName'
Size = 90

3 – Name = AdsProFileClone (:TADODataSet)
LockType = ltBatchOptimistic

4 – Name = dsDados (:TDataSource)
DataSet = AdsVisibilityColumn



5 - Name = GrdConfigColumn (:TDBGrid)
Align = alClient
DataSource = dsDados
Uma importate configuração a ser feita para nosso exemplo funcionar, é setar a
propriedade dgEditing para “false” nas opções do GrdConfigColumn.
Options = [dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs,
dgConfirmDelete, dgCancelOnExit]
ReadOnly = True
TabOrder = 1

Adicione duas colunas ao GrdConfigColumn:

5. 1 Coluna [0]
FieldName = 'TitleColumn'
Width = 300
Visible = True

5. 2 Coluna [1]
FieldName = 'VisibleColumn'
Width = 52
Visible = True


Observe que um dos fields do dataset associado ao Grid é bolleano, portanto vamos manipular o valor deles através de um check box. Um check box por linha do Grid. A princípio vamos criar estes checkboxes no muque, via código. Eu não gosto disso, detesto código extra, mas com o intuito de tornar nosso conteúdo mais acessível vou fazer assim por enquanto.

Antes de prosseguir apague a instância da classe do form, SetupVisibilityColumnFrm, criada automaticamante pelo Delphi. Ela deverá estar bem abaixo da primeira palavra reservada “var” na unit do form que criamos. Apague, tanto a palavra reservada “var”, quanto a variável “SetupVisibilityColumnFrm”.

Codificando os eventos do “GrdConfigColumn”

1) OnCellClick = GrdConfigColumnCellClick

procedure TSetupVisibilityColumnFrm.GrdConfigColumnCellClick(
Column: TColumn);
begin
if GrdConfigColumn.SelectedField.DataType = ftBoolean then
SaveBoolean;
end;

Não compile ainda, falta codificarmos o “SaveBoolean”:
1.1 Método privado SaveBoolean:

procedure TSetupVisibilityColumnFrm.SaveBoolean;
begin
with GrdConfigColumn do
begin
SelectedField.DataSet.Edit;
SelectedField.AsBoolean := not SelectedField.AsBoolean;
SelectedField.Dataset.Post;
end;
end;



Neste ponto a sua unit “.pas” deve estar semelhante ao trecho abaixo:

unit SetupVisibilityColumnForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, DB, ADODB, StdCtrls, Buttons, ExtCtrls, Grids, DBGrids;

TSetupVisibilityColumnFrm = class(TForm)
GrdConfigColumn: TDBGrid;
PnlBtn: TPanel;
BtnSave: TBitBtn;
btnCancel: TBitBtn;
AdsProFileClone: TADODataSet;
dsDados: TDataSource;
AdsVisibilityColumn: TADODataSet;
AdsVisibilityColumnTitleColumn: TStringField;
AdsVisibilityColumnVisibleColumn: TBooleanField;
AdsVisibilityColumnFieldName: TStringField;
procedure GrdConfigColumnCellClick(Column: TColumn);
private
procedure SaveBoolean;
public
{ Public declarations }
end;

(*apagamos a declaração da instância bem aqui ....*)

implementation

{$R *.dfm}

procedure TSetupVisibilityColumnFrm.GrdConfigColumnCellClick(Column: TColumn);
begin
if GrdConfigColumn.SelectedField.DataType = ftBoolean then
SaveBoolean;
end;

procedure TSetupVisibilityColumnFrm.SaveBoolean;
begin
with GrdConfigColumn do
begin
SelectedField.DataSet.Edit;
SelectedField.AsBoolean := not SelectedField.AsBoolean;
SelectedField.Dataset.Post;
end;
end;

end.



2) Evento OnDrawColumnCell do Grid (GrdConfigColumn): GrdConfigColumnDrawColumnCell


procedure TSetupVisibilityColumnFrm.GrdConfigColumnDrawColumnCell(
Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn;
State: TGridDrawState);
Const
CtrlState : array[Boolean] of Integer = (DFCS_BUTTONCHECK,
DFCS_BUTTONCHECK or DFCS_CHECKED);
var
CheckBoxRectangle : TRect;
begin
if Column.Field.DataType = ftBoolean then
begin
GrdConfigColumn.Canvas.FillRect(Rect);
CheckBoxRectangle.Left := Rect.Left + 2;
CheckBoxRectangle.Right := Rect.Right - 2;
CheckBoxRectangle.Top := Rect.Top + 2;
CheckBoxRectangle.Bottom := Rect.Bottom - 2;
DrawFrameControl(GrdConfigColumn.Canvas.Handle, CheckBoxRectangle,
DFC_BUTTON, CtrlState[Column.Field.AsBoolean]);
end;

end;


3) Criando Propriedades no From

Na seção “public” contruiremos o meio de comunicação deste módulo com a classe TMangerGridZn. Primeiro as propriedades, digite conforme o trecho abaixo:


public
property FieldEnabledVisibilityName: String
read FFieldEnabledVisibilityName write FFieldEnabledVisibilityName;
property TitleColumnFieldName: String
read FTitleColumnFieldName write FTitleColumnFieldName;
property VisibleFieldName: String
read FVisibleFieldName write FVisibleFieldName;
property FieldNameFieldName: String
read FFieldNameFieldName write FFieldNameFieldName;
property FieldNameGridName: String
read FFieldNameGridName write FFieldNameGridName;
end;


Use o class complete (Ctrl + shift + c) para autocampletar e criar os campos privados referentes a cada propriedade na classe TSetupVisibilityColumnFrm.

4) Recuperando as colunas do DBGrid associado a classe TManagerGridZn que previamente foram definadas como invisíveis pelo usuário. Estas colunas deverão ser listadas na inbterface para que o usuário possa selecionar qual delas desejará tornar visível.

Método privado “LoadAds”:
Este método Carrega o dataset que permitirá ao usuário visualizar as colunas que ele definiu como invisíveis. Podendo então, torná-las visíveis.

function TSetupVisibilityColumnFrm.LoadAds(AdsDadosOriginais,
AdsProfileGrid: TADODataSet): Boolean;
var
AuxValues: String;
AuxFieldsLocate: String;
begin
(* Retorna verdadeiro caso existam colunas a serem listadas.
AdsDadosOriginais:
armazena as cofigurações definidas em design time
pelo desenvolvedor no DBGrid associado a TManagerGridZn
AdsProfileGrid:
armazena as cofigurações definidas em run time
pelo usuário no DBGrid associado a TManagerGridZn*)

(* Este método Carrega o dataset que permitirá ao usuário visualizar as
colunas que ele definiu como invisíveis. Podendo então, torná-las visíveis. *)

Result := AdsVisibilityColumn.Active;
if not Result then Exit;

(* Limpando possível lixo*)
if not AdsVisibilityColumn.IsEmpty then
begin
AdsVisibilityColumn.First;
while not AdsVisibilityColumn.Eof do
AdsVisibilityColumn.Delete;
end;
AdsDadosOriginais.First;
while not (AdsDadosOriginais.Eof) do
begin

(* O campo FFieldEnabledVisibilityName corresponde ao informação original
do grid, se o desenvolvedor permitiu visibilidade ao usuário, ou não. *)
if AdsDadosOriginais.FieldByName(FFieldEnabledVisibilityName).AsBoolean then
begin
(* recuperando valores para o locate *)
AuxValues := AdsDadosOriginais.FieldByName(FFieldNameFieldName).AsString;

AuxFieldsLocate := FFieldNameFieldName;

if AdsProfileGrid.Locate(AuxFieldsLocate, AuxValues, []) then
begin
if not AdsProfileGrid.FieldByName(FVisibleFieldName).AsBoolean then
begin
AdsVisibilityColumn.Append;
AdsVisibilityColumnTitleColumn.AsString :=
AdsProfileGrid.FieldByName(FTitleColumnFieldName).AsString;
AdsVisibilityColumnVisibleColumn.AsBoolean :=
AdsProfileGrid.FieldByName(FVisibleFieldName).AsBoolean;
AdsVisibilityColumnFieldName.AsString :=
AdsProfileGrid.FieldByName(FFieldNameFieldName).AsString;
AdsVisibilityColumn.Post;
end;
end;
end;

AdsDadosOriginais.Next;
end;

Result := not AdsVisibilityColumn.IsEmpty;
end;



5 – Retornando as alterações efetuadas pelo usuário a partir do que foi listado na iterface:
Este método já é o output deste módulo, após implementa-lo (segundo trecho abaixo) cuidaremos do principal método de comunicação do nosso form.


procedure TSetupVisibilityColumnFrm.SetUpdates(AdsProfileGrid: TADODataSet);
begin
try
AdsVisibilityColumn.DisableControls;
AdsVisibilityColumn.First;
while not AdsVisibilityColumn.Eof do
begin
AdsProfileGrid.Filter := FFieldNameFieldName + ' = ' +
QuotedStr(AdsVisibilityColumnFieldName.AsString);
AdsProfileGrid.Filtered := True;
AdsProfileGrid.First;
while not AdsProfileGrid.Eof do
begin
AdsProfileGrid.Edit;
AdsProfileGrid.FieldByName(FVisibleFieldName).AsBoolean :=
AdsVisibilityColumnVisibleColumn.AsBoolean;
AdsProfileGrid.Post;
AdsProfileGrid.Next;
end;
AdsVisibilityColumn.Next;
end;
finally
AdsProfileGrid.Filter := '';
AdsProfileGrid.Filtered := False;
AdsVisibilityColumn.EnableControls;
end;
end;



6 – I/O do módulo TSetupVisibilityColumnFrm:
O método público “Execute” é o meio pelo qual implementaremos a lógica de entrada e saída do módulo que acabamos de construir.

function TSetupVisibilityColumnFrm.Execute(AdsSetupOriginal,
AdsProfileGrid: TADODataSet): Boolean;
begin
if FFieldEnabledVisibilityName = '' then
raise Exception.Create('O Campo FieldEnabledVisibilityName não foi atribuido.');
(* Connfigurando exibição do Form *)
Self.BorderStyle := bsDialog;
Self.Position := poScreenCenter;
Self.FormStyle := fsNormal;

(* Carrega o Dataset com as colunas que estão invisíveis *)
Result := LoadAds(AdsSetupOriginal, AdsProfileGrid);
(* Caso nenhum regitro de coluna invisível tenha sido carregado
cancela a execução do módulo, garantido que a propriedade "ModalResult"
do form seja igual a mrCancel *)
if not Result then
btnCancel.Click;
(* Copia os dados do Dataset que contem as configurações do profile*)
AdsProFileClone.Clone(AdsProfileGrid);
Result := (Self.ShowModal = mrOk);
if Result then
SetUpdates(AdsProfileGrid);

end;


7 – BtnOk OnClikc:
O BtnOk esta configurado como ModalResult = mrOk desta forma podemos recuperar ao término da execução do método ShowModal, se o usuário confirmou ou não a operação realizada.


procedure TSetupVisibilityColumnFrm.BtnSaveClick(Sender: TObject);
begin
if dsDados.DataSet.State in [dsEdit, dsInsert] then
dsDados.DataSet.Post;
end;


8 - BtnCancel OnClikc:
Da mesma forma, posso testar a se o usuário cancelou o processo porque o BtnCancel tem a propriedade ModalResult = mrCancel.


procedure TSetupVisibilityColumnFrm.btnCancelClick(Sender: TObject);
begin
if dsDados.DataSet.State in [dsEdit, dsInsert] then
dsDados.DataSet.Cancel;
end;


9 - Ainda falta ativar o dataset que armazenará os dados sobre as colunas que serão listadas na interface (SetupVisibilityColumnFrm). Evento OnCreate do form:

procedure TSetupVisibilityColumnFrm.FormCreate(Sender: TObject);
begin
AdsVisibilityColumn.CreateDataSet;
end;


Neste ponto finalizamos a implementação da funcionalidade pretendida neste artigo. Resta agora retornarmos a classe TManagerGridZn para codificarmos a chamada a este módulo. Por garantia segue o código completo das units contruidas até agora.

10 – Fontes Completos

10.1 SetupVisibilityColumnForm.pas


unit SetupVisibilityColumnForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, DB, ADODB, StdCtrls, Buttons, ExtCtrls, Grids, DBGrids;

type
TSetupVisibilityColumnFrm = class(TForm)
GrdConfigColumn: TDBGrid;
PnlBtn: TPanel;
BtnSave: TBitBtn;
btnCancel: TBitBtn;
AdsProFileClone: TADODataSet;
dsDados: TDataSource;
AdsVisibilityColumn: TADODataSet;
AdsVisibilityColumnTitleColumn: TStringField;
AdsVisibilityColumnVisibleColumn: TBooleanField;
AdsVisibilityColumnFieldName: TStringField;
procedure FormCreate(Sender: TObject);
procedure btnCancelClick(Sender: TObject);
procedure BtnSaveClick(Sender: TObject);
procedure GrdConfigColumnDrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
procedure GrdConfigColumnCellClick(Column: TColumn);
private
FFieldNameGridName: String;
FFieldEnabledVisibilityName: String;
FTitleColumnFieldName: String;
FFieldNameFieldName: String;
FVisibleFieldName: String;
procedure SaveBoolean;
function LoadAds(AdsDadosOriginais, AdsProfileGrid: TADODataSet): Boolean;
procedure SetUpdates(AdsProfileGrid: TADODataSet);
public
function Execute(AdsSetupOriginal, AdsProfileGrid: TADODataSet): Boolean;

property FieldEnabledVisibilityName: String
read FFieldEnabledVisibilityName write FFieldEnabledVisibilityName;
property TitleColumnFieldName: String
read FTitleColumnFieldName write FTitleColumnFieldName;
property VisibleFieldName: String
read FVisibleFieldName write FVisibleFieldName;
property FieldNameFieldName: String
read FFieldNameFieldName write FFieldNameFieldName;
property FieldNameGridName: String
read FFieldNameGridName write FFieldNameGridName;
end;


implementation

{$R *.dfm}

procedure TSetupVisibilityColumnFrm.btnCancelClick(Sender: TObject);
begin
if dsDados.DataSet.State in [dsEdit, dsInsert] then
dsDados.DataSet.Cancel;
end;

procedure TSetupVisibilityColumnFrm.BtnSaveClick(Sender: TObject);
begin
if dsDados.DataSet.State in [dsEdit, dsInsert] then
dsDados.DataSet.Post;
end;

function TSetupVisibilityColumnFrm.Execute(AdsSetupOriginal,
AdsProfileGrid: TADODataSet): Boolean;
begin
if FFieldEnabledVisibilityName = '' then
raise Exception.Create('O Campo FieldEnabledVisibilityName não foi atribuido.');
(* Connfigurando exibição do Form *)
Self.BorderStyle := bsDialog;
Self.Position := poScreenCenter;
Self.FormStyle := fsNormal;

(* Carrega o Dataset com as colunas que estão invisíveis *)
Result := LoadAds(AdsSetupOriginal, AdsProfileGrid);
(* Caso nenhum regitro de coluna invisível tenha sido carregado
cancela a execução do módulo, garantido que a propriedade "ModalResult"
do form seja igual a mrCancel *)
if not Result then
btnCancel.Click;
(* Copia os dados do Dataset que contem as configurações do profile*)
AdsProFileClone.Clone(AdsProfileGrid);
Result := (Self.ShowModal = mrOk);
if Result then
SetUpdates(AdsProfileGrid);

end;

procedure TSetupVisibilityColumnFrm.FormCreate(Sender: TObject);
begin
AdsVisibilityColumn.CreateDataSet;
end;

procedure TSetupVisibilityColumnFrm.GrdConfigColumnCellClick(Column: TColumn);
begin
if GrdConfigColumn.SelectedField.DataType = ftBoolean then
SaveBoolean;
end;

procedure TSetupVisibilityColumnFrm.GrdConfigColumnDrawColumnCell(
Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn;
State: TGridDrawState);
Const
CtrlState : array[Boolean] of Integer = (DFCS_BUTTONCHECK,
DFCS_BUTTONCHECK or DFCS_CHECKED);
var
CheckBoxRectangle : TRect;
begin
if Column.Field.DataType = ftBoolean then
begin
GrdConfigColumn.Canvas.FillRect(Rect);
CheckBoxRectangle.Left := Rect.Left + 2;
CheckBoxRectangle.Right := Rect.Right - 2;
CheckBoxRectangle.Top := Rect.Top + 2;
CheckBoxRectangle.Bottom := Rect.Bottom - 2;
DrawFrameControl(GrdConfigColumn.Canvas.Handle, CheckBoxRectangle,
DFC_BUTTON, CtrlState[Column.Field.AsBoolean]);
end;

end;

function TSetupVisibilityColumnFrm.LoadAds(AdsDadosOriginais,
AdsProfileGrid: TADODataSet): Boolean;
var
AuxValues: String;
AuxFieldsLocate: String;
begin
(* Retorna verdadeiro caso existam colunas a serem listadas.
AdsDadosOriginais:
armazena as cofigurações definidas em design time
pelo desenvolvedor no DBGrid associado a TManagerGridZn
AdsProfileGrid:
armazena as cofigurações definidas em run time
pelo usuário no DBGrid associado a TManagerGridZn*)

(* Este método Carrega o dataset que permitirá ao usuário visualizar as
colunas que ele definiu como invisíveis. Podendo então, torná-las visíveis. *)

Result := AdsVisibilityColumn.Active;
if not Result then Exit;

(* Limpando possível lixo*)
if not AdsVisibilityColumn.IsEmpty then
begin
AdsVisibilityColumn.First;
while not AdsVisibilityColumn.Eof do
AdsVisibilityColumn.Delete;
end;
AdsDadosOriginais.First;
while not (AdsDadosOriginais.Eof) do
begin

(* O campo FFieldEnabledVisibilityName corresponde ao informação original
do grid, se o desenvolvedor permitiu visibilidade ao usuário, ou não. *)
if AdsDadosOriginais.FieldByName(FFieldEnabledVisibilityName).AsBoolean then
begin
(* recuperando valores para o locate *)
AuxValues := AdsDadosOriginais.FieldByName(FFieldNameFieldName).AsString;

AuxFieldsLocate := FFieldNameFieldName;

if AdsProfileGrid.Locate(AuxFieldsLocate, AuxValues, []) then
begin
if not AdsProfileGrid.FieldByName(FVisibleFieldName).AsBoolean then
begin
AdsVisibilityColumn.Append;
AdsVisibilityColumnTitleColumn.AsString :=
AdsProfileGrid.FieldByName(FTitleColumnFieldName).AsString;
AdsVisibilityColumnVisibleColumn.AsBoolean :=
AdsProfileGrid.FieldByName(FVisibleFieldName).AsBoolean;
AdsVisibilityColumnFieldName.AsString :=
AdsProfileGrid.FieldByName(FFieldNameFieldName).AsString;
AdsVisibilityColumn.Post;
end;
end;
end;

AdsDadosOriginais.Next;
end;

Result := not AdsVisibilityColumn.IsEmpty;
end;

procedure TSetupVisibilityColumnFrm.SaveBoolean;
begin
with GrdConfigColumn do
begin
SelectedField.DataSet.Edit;
SelectedField.AsBoolean := not SelectedField.AsBoolean;
SelectedField.Dataset.Post;
end;
end;

procedure TSetupVisibilityColumnFrm.SetUpdates(AdsProfileGrid: TADODataSet);
begin
try
AdsVisibilityColumn.DisableControls;
AdsVisibilityColumn.First;
while not AdsVisibilityColumn.Eof do
begin
AdsProfileGrid.Filter :=
FFieldNameFieldName + ' = ' + QuotedStr(AdsVisibilityColumnFieldName.AsString);
AdsProfileGrid.Filtered := True;
AdsProfileGrid.First;
while not AdsProfileGrid.Eof do
begin
AdsProfileGrid.Edit;
AdsProfileGrid.FieldByName(FVisibleFieldName).AsBoolean :=
AdsVisibilityColumnVisibleColumn.AsBoolean;
AdsProfileGrid.Post;
AdsProfileGrid.Next;
end;
AdsVisibilityColumn.Next;
end;
finally
AdsProfileGrid.Filter := '';
AdsProfileGrid.Filtered := False;
AdsVisibilityColumn.EnableControls;
end;
end;

end.


10.2SetupVisibilityColumnForm.dfm


object SetupVisibilityColumnFrm: TSetupVisibilityColumnFrm
Left = 526
Top = 266
Caption = 'Visibilidade das Colunas'
ClientHeight = 560
ClientWidth = 495
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object GrdConfigColumn: TDBGrid
Left = 0
Top = 0
Width = 495
Height = 509
Align = alClient
DataSource = dsDados
Options = [dgTitles, dgIndicator, dgColumnResize,
dgColLines, dgRowLines, dgTabs, dgConfirmDelete, dgCancelOnExit]
ReadOnly = True
TabOrder = 0
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText
TitleFont.Height = -11
TitleFont.Name = 'Tahoma'
TitleFont.Style = []
OnCellClick = GrdConfigColumnCellClick
OnDrawColumnCell = GrdConfigColumnDrawColumnCell
Columns = < expanded =" False" fieldname =" 'TitleColumn'"
charset =" DEFAULT_CHARSET" color =" clWindowText" height =" -11"
name =" 'MS" style =" [fsBold]" width =" 300" visible =" True"
expanded =" False" fieldname =" 'VisibleColumn'" alignment =" taCenter"
charset =" DEFAULT_CHARSET" color =" clWindowText" height =" -11"
name =" 'MS" style =" [fsBold]" width =" 52" visible =" True">
end
object PnlBtn: TPanel
Left = 0
Top = 509
Width = 495
Height = 51
Align = alBottom
BevelOuter = bvNone
TabOrder = 1
ExplicitTop = 482
ExplicitWidth = 487
object BtnSave: TBitBtn
Left = 12
Top = 12
Width = 75
Height = 25
TabOrder = 0
OnClick = BtnSaveClick
Kind = bkOK
end
object btnCancel: TBitBtn
Left = 392
Top = 12
Width = 75
Height = 25
TabOrder = 1
OnClick = btnCancelClick
Kind = bkCancel
end
end
object AdsProFileClone: TADODataSet
LockType = ltBatchOptimistic
Parameters = <>
Left = 96
Top = 204
end
object dsDados: TDataSource
DataSet = AdsVisibilityColumn
Left = 132
Top = 68
end
object AdsVisibilityColumn: TADODataSet
CursorType = ctStatic
LockType = ltBatchOptimistic
FieldDefs = <>
Parameters = <>
StoreDefs = True
Left = 212
Top = 68
object AdsVisibilityColumnTitleColumn: TStringField
DisplayLabel = 'Título da Coluna'
FieldName = 'TitleColumn'
Size = 80
end
object AdsVisibilityColumnVisibleColumn: TBooleanField
DisplayLabel = 'Visí'vel'
FieldName = 'VisibleColumn'
end
object AdsVisibilityColumnFieldName: TStringField
FieldName = 'FieldName'
Size = 90
end
end
end


11 - Na classe TManagerGridZn codificaremos a chamada ao form “TSetupVisibilityColumnFrm”. Antes, declare na seção “uses” da “implementatio” a unit do módulo que criamos acima.




procedure TManagerGridZn.ConfigurarGridViewColumnLayoutClick(Sender: TObject);
var
FrmVisibilityConfig: TSetupVisibilityColumnFrm;
AuxPath: String;
AdsOrigConfigMemmento: TADODataSet;
begin
try
AdsOrigConfigMemmento :=
DmMangerGridZn.GetDefaultConfiguration(FGrid.Name, FGrid.Owner.Name);
FrmVisibilityConfig := TSetupVisibilityColumnFrm.Create(Self);
FrmVisibilityConfig.FieldEnabledVisibilityName :=
DmMangerGridZn.AdsOriginalConfigUpdateVisibility.FieldName;
FrmVisibilityConfig.TitleColumnFieldName :=
DmMangerGridZn.AdsOriginalConfigColumnTitleCaption.FieldName;
FrmVisibilityConfig.VisibleFieldName :=
DmMangerGridZn.AdsOriginalConfigColumnVisible.FieldName;
FrmVisibilityConfig.FieldNameFieldName :=
DmMangerGridZn.AdsOriginalConfigColumnFieldName.FieldName;
FrmVisibilityConfig.FieldNameGridName :=
DmMangerGridZn.AdsOriginalConfigGridName.FieldName;

if FrmVisibilityConfig.Execute(AdsOrigConfigMemmento,
DmMangerGridZn.AdsDados) then
begin
AuxPath := BuildFilePathComplete;
DmMangerGridZn.SaveToFile(AuxPath);
end;
finally
FrmVisibilityConfig.Free;
AdsOrigConfigMemmento.Close;
FreeAndNil(AdsOrigConfigMemmento);
LoadFileProfile;
end;

end;

O próximo passo é retornar a método que monta o menu popup para acrescentar a chamada ao método “ConfigurarLayoutGridClick” ao item de menu específico.


procedure TManagerGridZn.BuildMenuPopup;
var
i: Integer;
MyMenuitem: TMenuItem;
begin
(* Verifco se o componente encontra-se em run time*)
if not (csDesigning in Self.ComponentState) then
begin
if Assigned(FPopupMenu) then
begin
for i := 0 to High(PopupMenuItemName) do
begin
if (FPopupMenu.Items.Count > 0) and (i = 0) then
CreatePopupMenuItem(PopupMenuItemName[i], '-');

if (i = 1) then
CreatePopupMenuItem(PopupMenuItemName[i], 'Configurar Layuot do &Grid');

if (i = 2) then
CreatePopupMenuItem(PopupMenuItemName[i], '&Ver Colunas Invisíveis');

if (i = 3) then
CreatePopupMenuItem(PopupMenuItemName[i],
'&Restaurar Configuração Orignal');
end;

MyMenuitem := TMenuItem(Self.FindComponent(PopupMenuItemName[1]));
if assigned(MyMenuitem) then
MyMenuitem.OnClick := ConfigurarLayoutGridClick;
(* A linha comentada com "//" será implementada mais tarde *)
MyMenuitem := TMenuItem(Self.FindComponent(PopupMenuItemName[2]));
if assigned(MyMenuitem) then
MyMenuitem.OnClick := ConfigurarGridViewColumnLayoutClick;

end;
end;

end;


Por hora chega!!!! Recompile a package e execute o programa de teste que fizemos no artigo anterior. Teste o componente e experimente a nova funcionalidade. O Embreve teminaremos, espero, a funcionalidade para restaurar as configurações default.

Artigo completo (View Full Post)

terça-feira, 20 de novembro de 2007

Construção de Componentes V – Parte A

No artigo anterior começamos a construir o componente Gerenciador de Layout de Grid, TManagerGridZn. Vimos como fazer uma package no Delphi 2006, associar outras units a ela e etc. Como fazer isso no Delphi 7 também já foi exemplificado nos primeiros artigos sobre o assunto. Para programarmos um pequeno módulo a fim de testarmos o novo componente precisamos instalar a package, para fazer isso basta clickar com o botão direto sobre ela, no Project Manager e selecionar a opção “Install”.



Projeto teste Gerenciador de Layout de Grid (TesteGerencLayoutGrid.bdsproj): Inicie uma nova aplicação no Delphi, no From1 adicione os seguintes componentes:
1 – TClientDataSet
Name = CdsDados

1.1 Adicione dois TFields ao CdsDados;

TIntegerField
Name = CdsDadosID_Dado
Field Kind = fkData

TStringField
Name = CdsDadosDescDado
Field Kind = fkData


TStringField
Name = CdsDadosCoisas
Field Kind = fkData



2 – TdataSource (Data Access)
Name = DsDados
DataSet = CdsDados

3 - TDBGrid (Data Controls):
Name = DDGrid1
Align = alBottom
DataSource = DsDados

3.1 – Adicione duas colunas ao Grid, correspondentes aos três
TFields do CdsDados

4 -TManagerGridZn (Estação ZN)
Grid = DBGrid1

5 - TBitBtn (Addititional)
Name = BtnActivar
Caption = 'Ativar'

cerifique-se de que o ManagerGridZn (TManagerGridZn), esteja associado ao DBGrid1. O DBGrid tem que estar associado ao DsDados(TDataSource), que deve estar associado ao CdsDados(TClientDataSet).

No evento OnClick do BtnAtivar codifique:


procedure TForm1.BtnActivarClick(Sender: TObject);
var
i: Integer;
begin
(* Ativa o Dataset*)
if not CdsDados.Active then
CdsDados.CreateDataSet;
//Preenche o DataSet
for i := 0 to 30 do
begin
CdsDados.InsertRecord([i, Format('Dado %d', [i]), 'Bla!!!!']);
end;
end;


Pronto!! That’s it!!!
Chega de código .... tá bom??? Também acho ... hehehe

Note que para trabalhar com a classe que criamos nào foi necessário NENHUMA linha código.

Execute o programa e teste conforme os screenshots abaixo:

Primeiro carregue os dados clickando no botão “Ativar”. Com o botão direito do mouse, chame o menu popup que o componente TManagerGridZn criou e associou ao grid. Selecione a 1° opção.



Altere as propriedades da coluna sobre a qual vc clickou.



Ok, fácil? Agora você pode intuitivamente manter um layout personalizado para qualquer grid, em qualquer um dos seus projetos.





Podemos concluir, a partir deste exemplo, que um programa bem modularizado, onde o grau de acoplamento é reduzido ao máximo, propicia o reuso de maneira eficiente. Também, que é possível a construção de componentes de fácil consumo (mínima curva de aprendizado por parte do desenvolvedor que vai usar o componente em seus programas).
Outro ponto importante a destacar é que com essa abordagem podemos desenvolver novas funcionalidades para sistemas que foram construídos por outros (sistemas antigos, legados), que precisam sofrer manutenção, demandando para isso um grau mínimo de intervenção no código dos desenvolvedores originais. Sobretudo porque, a maioria deles são extremamente sensíveis a qualquer intervenção, mínima que seja, em seus fontes.

A seguir, finalizaremos a implementação do componente “TManagerGridZn”, pois ainda faltam ser codificadas as funcionalidades de recuperar as colunas que o usuário definir como invisíveis, “Ver Colunas Escondidas” (Invisíveis), e o restore default values “Restaurar configuração original”.

Código completo da unit1.pas



unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, DB, DBClient, Grids, DBGrids, ManagerGridZn;

type
TForm1 = class(TForm)
DBGrid1: TDBGrid;
CdsDados: TClientDataSet;
dsDados: TDataSource;
CdsDadosID_Dado: TIntegerField;
CdsDadosDescDado: TStringField;
BtnActivar: TBitBtn;
ManagerGridZn1: TManagerGridZn;
CdsDadosCoisas: TStringField;
procedure BtnActivarClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.BtnActivarClick(Sender: TObject);
var
i: Integer;
begin
if not CdsDados.Active then
CdsDados.CreateDataSet;

for i := 0 to 30 do
begin
CdsDados.InsertRecord([i, Format('Dado %d', [i]), 'Bla']);
end;

end;

end.



unit1.dfm


object Form1: TForm1
Left = 223
Top = 168
Caption = 'Form1'
ClientHeight = 375
ClientWidth = 531
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object DBGrid1: TDBGrid
Left = 0
Top = 80
Width = 531
Height = 295
Align = alBottom
DataSource = dsDados
TabOrder = 0
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText
TitleFont.Height = -11
TitleFont.Name = 'Tahoma'
TitleFont.Style = []
Columns = < expanded =" False" fieldname =" 'ID_Dado'" visible =" True" expanded =" False" fieldname =" 'DescDado'" visible =" True" expanded =" False" fieldname =" 'Coisas'" visible =" True">
end
object BtnActivar: TBitBtn
Left = 408
Top = 8
Width = 75
Height = 25
Caption = 'Ativar'
TabOrder = 1
OnClick = BtnActivarClick
end
object CdsDados: TClientDataSet
Aggregates = <>
Params = <>
Left = 316
Top = 40
object CdsDadosID_Dado: TIntegerField
FieldName = 'ID_Dado'
end
object CdsDadosDescDado: TStringField
FieldName = 'DescDado'
Size = 30
end
object CdsDadosCoisas: TStringField
FieldName = 'Coisas'
end
end
object dsDados: TDataSource
DataSet = CdsDados
Left = 388http://www.blogger.com/img/blank.gif
Top = 44
end
object ManagerGridZn1: TManagerGridZn
Grid = DBGrid1
Left = 80
Top = 12
end
end



Continuação da série Construção de Componentes

Artigo completo (View Full Post)

 
BlogBlogs.Com.Br