segunda-feira, 17 de outubro de 2011

Pequenos segredos do Delphi


Irei falar sobre algumas palavras que vemos no Delphi e às vezes não sabemos de onde a mesma vem e nem o que existe por trás da mesma.
Mas também, falarei de algumas palavras chaves do Delphi.

Result


A primeira delas que veremos é a palavra Result. Quando implementamos uma função (método):


{...}


type


TForm1 = class(TForm)


private


{ Private declarations }


function Soma(pnSoma1, pnSoma2: Double): Double;


public


{ Public declarations }


end;


{...}


function TForm1.Soma(pnSoma1, pnSoma2: Double): Double;


begin


Result := pnSoma1 + pnSoma2;


end;


Já parou para pensar de onde vem Result?


R. Eu já :-).


Result é uma variável. Cada função (ou método) declara implicitamente a variável Result, cujo, o tipo da variável é o mesmo tipo de retorno da função declarada. No exemplo acima, é do tipo Double. Result não é uma variável Local ela é semelhante a um parâmetro var oculto, já declarado no cabeçalho da function (isso ocorre nos bastidores).


Uma dica: O Delphi pode falhar ao inicializar a variável Result, principalmente em alguns casos envolvendo o retorno de String, Arrays Dinâmicos e Variant. Nesses casos, por segurança, deve-se inicializar a mesma com: String Vazia, Array Vazio e ou Unassigned (declarada na Uses Variants do Delphi 7).


Self


E Self?


R. Eu já (de novo) :-).


Self assim como Result é uma variável. Self só é visível em Métodos que pertencem a uma Classe e não é visível em Functions e Procedures normais. Self é um parâmetro oculto e tem como valor a Referência ao Objeto ou, se for o caso de um Método de Classe (Class methods) Self é a Referência da Classe.


No exemplo acima do método Soma, Self receberia o valor de: TForm1. Uma coisa interessante é, os métodos possuem um with Self do implícito no corpo do método, isto é, é por isso que quando fizemos o seguinte no corpo de um método não precisamos colocar Self:


function TForm1.Soma(pnSoma1, pnSoma2: Double): Double;


begin


Caption := 'Artigo escrito por AHR';


Close;


{...}


end;


Como o método está envolvido num with Self do implícito (como já explicado) não precisamos fazer:


Self.Caption, Self.Close, etc, etc.


Const


E const?


R. Você deve estar cansado de ler (Eu já)......


Const é uma palavra chave, não é uma variável inicializada implicitamente (pelo compilador) como Self e Result (explicados mais acima). A palavra Const não possui mistério algum para explicar. O que tem de mistério para explicar na palavra chave const é como a mesma se comporta conforme a sua declaração e se ligarmos (no Delphi 7) a diretiva de compilação {$J+} ou {$WRITEABLECONST ON}. Const pode ser de dois tipos:


Constantes (propriamente dita) ou Constantes Tipificadas.


Constantes (constantes mesmo!) é como estamos acostumados a trabalhar, isto é, não podemos modificar o seu valor dentro de rotinas, por que, o próprio compilador impõe essa restrição, onde, se você tentar mudar o valor da mesma, o compilador irá emitir uma mensagem de erro.


Constantes Tipificadas não é realmente uma constante, e sim, uma variável inicializada, isto é, o Delphi trata a mesma como sendo uma variável. Mas, existe uma diferença entre constantes tipificadas e variáveis normais locais, Constantes tipificadas mantém o seu valor entre as chamadas de rotinas, coisa que não acontece com uma variável local normal, onde, a mesma sempre será “inicializada” conforme o seu tipo.


Vamos aos exemplos para ficar mais claro:


implementation


{$J+}


// Ou {$WRITEABLECONST ON}


const constMesmo = 42;


constTipificada: Integer = 7;


consObjeto: TForm = nil;


{$J-}


// Ou {$WRITEABLECONST OFF}


{$R *.dfm}


No exemplo acima eu liguei a diretiva de compilação {$J+} (também poderia ser: {$WRITEABLECONST ON}), do qual indica que poderemos mudar o valor da “constante” (mas lembre-se, mesmo ligando a diretiva, ainda necessito especificar o Tipo para a mesma ser realmente Constante Tipificada):


procedure TForm1.Button1Click(Sender: TObject);


begin


//constMesmo := 10;


constTipificada := 10;


consObjeto := TForm1.Create(Self);


consObjeto.Name := 'TESTE';


ShowMessage(IntToStr(constTipificada));


ShowMessage(consObjeto.Name);


end;


Ao colocarmos o código no Evento OnClik do Botão (que foi adicionado no Form), podemos perceber que nem o compilador e muito menos em tempo de execução tivemos alguma mensagem de erro. Mas, como citado, só podemos modificar o valor de constTipificada e consObjeto, pois, ambas foram declaradas com o seu tipo, tornando assim, ambas como constantes tipificadas. Quis também usar um Objeto para vermos que também podemos fazer o uso de variáveis incializadas com Objetos.


Mesmo ligando as diretivas acima, constMesmo é constante mesmo, isto é, não podemos modificar o seu valor, se tentarmos, receberemos uma mensagem de erro do compilador do Delphi. Para fazermos o teste, basta retirarmos o comentário de constMesmo.


Vamos colocar um novo botão no Form para testarmos outro aspecto das constantes tipificadas, que é (como já citado) o fato de Constantes tipificadas manterem o seu valor entre as chamadas de rotinas.


Exemplo:


procedure TForm1.Button2Click(Sender: TObject);


begin


ShowMessage(IntToStr(constTipificada));


constTipificada := constTipificada + 10;


end;




Ao clicarmos novamente no Botão:




E se clicarmos novamente, irá sempre adicionar mais 10 (Dez) ao valor da constante tipificada, conforme o código do exemplo do Button2.


No Delphi 7 tanto {$J} como {$WRITEABLECONST OFF} por Default estão desligados, então (isso não acontecia em versões anteriores), sendo assim, constantes tipificadas são verdadeiramente constantes.


Class


A palavra chave Class também não tem mistérios. O que existe de diferente é a forma como a mesma pode ser declarada. Isto é, conforme a declaração podemos ter muitas nuances com a mesma palavra, exemplo:


type


TForm1 = class(TForm)


private


public


end;


{...}


É a forma normal que usamos no dia a dia, e estamos apenas indicando (a IDE fez este trabalho acima por nós) que TForm1 herda da Classe TForm.


Já:


TCustomForm = class;


É uma declaração antecipada (chamada de: Forward declarations), onde estamos apenas dizendo ao compilador que a classe será declarada mais adiante na rotina. Se olharmos o fonte da unit: Forms.pas veremos muitas declarações deste tipo.


Já:


TFormClass = class of TForm;


É uma declaração do tipo MetaClasse. Onde, uma MetaClasse é uma variável que pode armazenar uma referência da classe que poderá ser usada em qualquer expressão que exija a referência de classe, como ao chamar um construtor ou método de classe.


Observação: Citei em várias partes do Artigo Referência de Classe, não se preocupe, mais a frente eu explico com detalhes o que é uma Referência de Classe.


Já:


type


TAlgumaClasse = class


public


class function AlgumaFunction(Operation: string): Boolean; virtual;


class procedure AlgumaProcedure(var Info: TFigureInfo); virtual;


{...}


end;


O principal da declaração é:


class function AlgumaFunction(Operation: string): Boolean; virtual;


class procedure AlgumaProcedure(var Info: TFigureInfo); virtual;


O cabeçalho e o corpo da implementação devem começar com: class function.


Class method é um método que opera sobre a classe e não sobre a instância dessa classe. Eles podem ser executados sem a necessidade se criar o objeto da classe.


IS


O operador is não tem mistérios, is é uma palavra chave e ele apenas testa se o tipo de referência do objeto é igual a uma referência de classe e ou de uma das suas classes derivadas. Mas, nas internas o operador is chama o método InheritsFrom do Objeto para saber o seu tipo correto. A dica do operador is é, ao testar o objeto com is não é necessário usar o operador as, neste caso, faça um type casting simples para conseguir um melhor desempenho.


NIL


É um valor do tipo Pointer especial (Pointer(0)). Seu valor numérico é 0 (Zero). O uso mais comum para NIL é marcar variáveis do tipo Pointer e métodos para um valor desconhecido. Um exemplo seria o manipulador de eventos, onde, podemos atribuir o mesmo para nil:


Button1.OnClick := nil;


Não fizemos nada de mais no exemplo acima, apenas “matamos” o endereço do método.


Uma característica e curiosidade, o Delphi armazena um Array Dinâmico e String longa como um ponteiro nil.


with


O que with faz é apenas acrescentar uma referência de registro, Objeto, Classe e ou Interface ao escopo para resolver nomes de símbolo.


Diferença entre TStrings e TStringList


Já parou para pensar qual a real diferença entre TStrings e TStringList?


R. Eu já ;-).


Listas de Strings estão por toda a parte, na VCL, no Mar, no Ar e até mesmo em Marte! ;-). Brincadeiras a parte, elas estão por todo o lugar mesmo: Caixa de Listas, Controles de Edições, Controle de Menu, Listas de Strings, etc, etc. Apesar de serem fáceis de usar e bastante funcional, ela vitima muitos desenvolvedores com a seguinte pergunta:


E agora? Uso TStrings ou TStringList? No fundo, qual a diferença entre elas?


R.: A combinação de ambas!


TStrings é uma classe que define todo o comportamento associado a lista de Strings. Entretanto, TStrings não tem qualquer método e/ou recursos para realmente manter a lista de Strings. Os descendentes de TStrings é que são responsáveis por isso, neste caso, TStringList. Ok, Ok.... então agora não necessito mais usar TStrings e apartir de agora só usarei TStringList! Certo?


R.: Errado!


Fazendo isso você perde um poderoso recurso da Classe TStrings, que é o recurso de cópia de uma Lista de String para outra Lista de String, usando o método Assign. Diante disso, você deve declarar a variável como TStrings e na criação da mesma, criar usando a referência de classe TStringList, já que TStringList é herdada de TStrings.


Algo como:


procedure TForm1.FormCreate(Sender: TObject);


var


sStrings: TStrings;


begin


sStrings := TStringList.Create;


{...}


end;


Um exemplo do que estou falando seria observar o comportamento da Classe TCustomRadioGroup (Extctrls.pas), onde, o campo de armazenamento interno FItems é declarado como TStrings, mas, ao observarmos o constructor do mesmo, podemos ver que FItems é atribuída a uma ocorrência de TStringList.


A dica de TStrings x TStringList pode servir mais para os criadores de componentes!


Diferença entre Objetos e Classes


Um outro fato que eu vejo em alguns colegas, é a dúvida do que é uma Classe, Um Objeto e até mesmo um Componente?


Classes


Classes são (pelo menos pense assim) como um Super Registro, onde podemos declarar e descrever métodos, propriedades e campos. Quando declaramos uma classe em Delphi (como em quase toda linguagem de programação que conheço, seja: C#, C++, etc) definimos níveis diferentes de acesso aos “dados” contidos na classe, através de Private, Protected, Public, Published (ou até mesmo Automated). Uma classe (em Delphi) herda de outra classe, onde desta forma, herda todos os campos, métodos e propriedades. Se não declaramos uma classe ancestral o Delphi automaticamente coloca TObject como sendo a classe ancestral.


Quando cito e falo em Referências de Classes (citado algumas vezes nesse artigo) ela é na realidade uma expressão que denomina uma classe específica. Referências de Classes são ponteiros para a tabela de informações de tempo de execução (VMT). Quando testamos uma classe com o operador is (explicado acima) você está testando a referência de classe. Classes são sempre apenas para leitura e residem na memória de Leitura.


Objetos


Um objeto é a instância dinâmica de uma classe. É no objeto é que podemos “escrever” e executar operações, isto é, Objetos não são apenas para Leitura conforme a Classe. Você cria o objeto usando a referência da classe, chamando o seu constructor. Exemplo:


oObjeto := TMinhaClasse.Create;


Já a referência de Objeto é o trecho de memória onde o Delphi armazena os valores para todos os campos do Objeto, a referência de objeto é um ponteiro para o objeto. Quando criamos um Objeto dinamicamente, somos os responsáveis por liberar tal objeto, usando:


oObjeto.Free;


sempre envolto num bloco try...finally...end; o Delphi não possui coleta de Lixo automática (Garbage Collector) como acontece no .NET, JAVA, SmallTalk, etc.


Componentes


Explicar o que é um componente é o mais simples. Componente é tudo aquilo (se falando em Delphi, esqueça o COM - Component Object Model) que você pode manipular num formulário Delphi. Todo componente tem que obrigatoriamente herdar da Classe TComponent no seu mais alto nível ancestral.


Conclusão


A intenção do artigo era apresentar algumas palavras usadas no dia a dia do Delphi do qual não tínhamos certeza de onde as mesmas vinham e como se comportavam. Espero ter esclarecido algumas dúvidas e estou aberto à sugestão e/ou troca de idéias.

quarta-feira, 12 de outubro de 2011

Definir o tamanho do papel em TPrinter

Esta procedure configura o tamanho do papel em Run-Time para ser utilizado com o objeto TPrinter; Esta procedure deve ser chamada antes de aplicar o método Printer.BeginDoc.

procedure TForm1.SetPrinterPage(Width, Height : LongInt);
var
  Device : array[0..255] of char;
  Driver : array[0..255] of char;
  Port : array[0..255] of char;
  hDMode : THandle;
  PDMode : PDEVMODE;
begin
  Printer.GetPrinter(Device, Driver, Port, hDMode);
  If hDMode <> 0 then
  begin
  pDMode := GlobalLock( hDMode );
  If pDMode <> nil then
  begin
  pDMode^.dmPaperSize := DMPAPER_USER;
  pDMode^.dmPaperWidth := Width;
  pDMode^.dmPaperLength := Height;
  pDMode^.dmFields := pDMode^.dmFields or DM_PAPERSIZE;
  GlobalUnlock( hDMode );
  end;
  end;
end;

Código usados pelas impressoaras HP

Veja abaixo alguns códigos usados pelas impressoras HP:

RESET = 027/069
BOLD1 = 027/040/115/051/066
BOLD0 = 027/040/115/048/066
ITALIC1 = 027/040/115/049/083
ITALIC0 = 027/040/115/048/083
UNDERLINE1 = 027/038/100/049/068
UNDERLINE0 = 027/038/100/064
LPI6 = 027/038/108/054/068
LPI8 = 027/038/108/056/068
CPI5 = 027/040/115/053/072
CPI6 = 027/040/115/054/072
CPI8 = 027/040/115/056/072
CPI10 = 027/040/115/049/048/072
CPI12 = 027/040/115/049/050/072
CPI17 = 027/040/115/049/054/046/054/055/072
CPI20 = 027/040/115/050/048/072

Adicionando a soma de Fields no QRExpression do QuickReport

QRExpr1.Expression := Sum(Field1);
QRExpr2.Expression := Sum(Field2);
QRExpr3.Expression := Sum(Field1+Field2);

segunda-feira, 3 de outubro de 2011

Formatando 0000780 para 7,80

Para converter o conteuro 0000780 para, escreva o seguinte código:

procedure TForm1.Button1Click(Sender: TObject);
var
     conteudo: string;
     Valor: currency;
begin
     conteudo := '0000780';
     Valor := StrToFloat(conteudo) / 100;
      ShowMessage(Format('%m', [Valor]));
end;


Também daria para jogar para um componete TEdit. Ficaria Assim:

procedure TForm1.Button1Click(Sender: TObject);
var
     conteudo: string;
      Valor: currency;
begin
     conteudo := '0000780';
     Valor := StrToFloat(conteudo) / 100;
      edit1.text := Format('%m', [Valor]);
end;

Como criar um form completo com botões dinamicamente

Você pode criar qualquer componente do delphi de forma dinãmica, incluindo todos os componentes visuais(buttons, textedits, maskedits, labels). Para que serviria isso? Para muitas coisas, uma delas é que vocÊ pode criar qualquer controle em tempo de execução e depois de utiliza-lo descarrega-lo da memória...

Exemplo:  Como Criar um botão quando o mouse for pressionado e associar eventos nele....

É só colar dentro da unit da form....

=========inicio do copia e cola==========

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  TForm1 = class(TForm)
  procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
  procedure FormCreate(Sender: TObject);
  private
  { Private declarations }
  public
  { Public declarations }
  end;

var
  Form1: TForm1;
  contador:integer;
implementation

{$R *.DFM}
uses stdctrls;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var b:tbutton;
begin
  b:=Tbutton.create(self);
  b.visible:=false;
  b.parent:=self;
  b.left:=x;
  b.top:=y;
  b.name:='Btn'+inttostr(contador);
  b.Caption:='Clique-me';
  inc(contador);
  b.visible:=true;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
contador:=1;
end;

end.
============fim do copia e cola==========

Se quiser associar um evento ao botao é só copiar os procedimento clicou e alterar

o procedmento mouse down para o que segue....

=======inicio do copia e cola

 procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var b:tbutton;
begin
  b:=Tbutton.create(self);
  b.visible:=false;
  b.parent:=self;
  b.left:=x;
  b.top:=y;
  b.name:='Btn'+inttostr(contador);
  b.Caption:='Clique-me';
  inc(contador);
  b.visible:=true;
  b.onclick:=clicou;
end;

procedure TForm1.clicou(sender: TObject);
begin
ShowMessage('Clicou!!!');
end;

=====fim do copia e cola======

Com isto concluimos que podemos criar qualquer coisa em tempo de execução com economia de memória. BAsta para isso dar o create da classe....

Para apagar o botao devemos ter cuidado para não apagar dentro do evento visto que

gerara uma exceção. Existe duas formas de se contornar isto, a primeira é fornecendo um atraso

e a segunda é atraves de uma API do windows mas isto fica para os proximos e-mails..

Como Criar Forms Em Tempo de Execução

Para você economizar memória, pode-se criar os forms de sua aplicação somente no momento da execução. Na criação do Form você define se ele é MODAL ou NÃO MODAL. Para Isso observe os seguintes códigos:

MODAL - Mostra form em modo exclusivo

procedure TForm1.Button1Click(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);{Carrega form na memória}
Form2.ShowModal;{Mostra form em modo exclusivo}
Form2.Free; {Libera Memória}
end;
NÃO MODAL - Mostra form em modo não exclusivo

procedure TForm1.Button1Click(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);{Carrega form na memória}
Form2.ShowModal;{Mostra form em modo exclusivo}
end;
No evento OnClose do Form2 coloque o seguinte código.

procedure TForm2.FormClose (Sender: Tobject; var Action : TCloseAction);
begin
Action:= caFree;
end;
Aliado a este código, deve deve alterar no delphi, no menu Options, opção Project. Mudando os forms a serem criados dinamicamente da coluna Auto-Create Forms para Avaliable Forms. a

A melhor maneira de liberar um form da memoria

Quando você usa Form.Free ou Form.Destroy, você está imediatamento solicitando a destruição do formulário. Com Form.Release, todas as mensagens pendentes no pool do formulário são postadas - exemplo: redesenho do formulário, movimento do mouse, pressionamento de tecla,...
 
Use assim:

FormXX := TFormXX.create ( application );
try
  FormXX.ShowModal
finally
  FormXX.Release;
  FormXX := nil;
end;

Adaptando para resoluções de video diferentes?

Sempre que procurei algo sobre esse tema, ia para no Tip da Borland #2861, que é a mesma informação do arquivo de help da Loyd’s. Esse texto tambem aparece nos bancos de dados da Q&A. Eu suponho que essa seja a informação definitiva. Encontreiuma informação que não foi mencionada aqui. Pela lista de correiros do Delphi-Talk havia mensagens de Brien King e MichaelNovak que discutiam esse assunto.

***

LOYD´S TIPS:

Resolução de Vídeo:

Quando criamos formulários, as vezes é útil escrever um código para que a tela e todos os seus objetos sejam mostrados nomesmo tamanho, não importando qual a resolução da tela. Aqui esta um código que mostra como isso é feito:

Implementation
const
ScreenWidth: LongInt = 800; {I designed my form in 800x600 mode.}
ScreenHeight: LongInt = 600;
{$R *.DFM}
procedure TForm1.FormCreate (Sender: Tobject);
begin
scaled := true;
if (screen.width <> ScreenWidth) then
begin
height := longint(height) * longint(screen.height) DIV ScreenHeight;
width := longint(width) * longint(screen.width) DIV ScreenWidth;
scaleyBy(screen.width, ScreenWidth);
end;
end;
Agora, você vai querer checar, se o tamanho dos fontes(de letra) estão OK. Antes de trocar p tamanho do fonte, você precisará ter

certeza de que o objeto realmente tem a propriedade fonte pela checagem da RTTI. Isso pode ser feito assim:

USES typinfo; {Add this to your USES statement.}
var
i:integer;
begin
for i := componentCount - 1 downto 0 do
with components[i] do
begin
if GetPropInfo(ClassInfo, ´font´) <> nil then
font.size := (NewFormWidth DIV OldFormWidth) * font.size;
end;
end;
{Esta é a maneira longa de fazer a mesma coisa}

var
i:integer;
p:PPropInfo;
begin
for i := componentCount - 1 downto 0 do
with components [i] do
begin
p := GetPropInfo (ClassInfo, ´font´);
if assigned (p) then
font.size := (NewFormWidth DIV OldFormWidth) * font.size;
end;
end;
Atenção: Nem todos os objetos tem a propriedade FONT. Isso deve ser o suficiente para você começar.

Atenção: A seguir, algumas dicas para ter em mente quando representar aplicações Delphi (formulários) em diferentes resoluçõesde Tela:

* Decida antecipadamente, na etapa de criação do formulário, se ele será escalável ou não. A vantagem de um não escalável é que nada muda em tempo de execução. A desvantagem é equivalente (seu formulário pode ser muito pequeno ou grande para alguns sistemas se nào for usada escala).

* Se você não for usar formulário escalável, configure o set scaled to False.

* Ou então, configure a propriedade scaled do formulário para True.

* Configure a propriedade AutoScroll para False. AutoScroll = True quer dizer "não mexa no tamanho do frame do formulário em tempo de execução", o que não parece bom quando o conteúdo do formulário muda de tamanho.

* Configure a fonte do formulário para uma True Type escalável, como a Arial MS. San Serif é uma boa alternativa, mas lembre que ainda é uma fonte bitmapped. Só a Arial dará uma fonte dentro de um pixel da altura desejada.ATENÇÃO: Se a fonte usada em uma aplicação não estiver instalada no computador, o Windows selecionará uma fonte alternativa da mesma família para utilizar. O tamanho dessa fonte pode não corresponder ao da fonte original, podendo causar problemas.

* Configure a propriedade position do formulário para uma opção diferente de poDesigned. poDesigneddeixa o formulário onde você o deixou ( no design Time), o que sempre termina fora da margem, à esquerda da minha tela 1280 x 1024 - e completamente fora da tela 640 x 480.

* Não amontoe controles no formulário - deixe pelo menos 4 pixels entre else, para que uma mudança de um pixel nas margens (devido a apresentação em escala) não mostre controles sobrepostos.

* Para labels de uma linha alinhadas ã esquerda ou à direita, configure o AutoSize para True. Para outras formas de alinhamento configure o AutoSize para False.

* Tenha certeza de que há espaço em branco suficiente num componente de labels para alterações no tamanho da fonte – um espaço de 25% do comprimento da linha de caracteres mostrada é um pouco a mais do que se precisa, mas é mais seguro. (Você vai precisar de um espaço equivalente a 30% de espansão para string labels se você pretende traduzir sua aplicação para outra linguagem). Se o Autosize estiver em False, tenha certeza de que realmente configurou o tamanho do label corretamente. Se o Autosize estiver em True, esteja certo de que há espaço suficiente para que o label se amplie.

* Em labels de múltiplas linhas ou de termos ocultos, deixe pelo menos uma linha em branco na base. Isso vai ser necessário para incluir o que estiver sobrando quando o texto for oculto de maneira diferente, pela mudança do tamanho da fonte com a escala. Não assuma isso porque está usando fontes grandes. Você não tem que deixar sobra de texto - as fontes (grandes) de outros usuários podem ser maiores que as suas!

* Tenha cuidado quando abrir um projeto em IDEs com resoluções diferentes. Assim que o formulário for aberto, sua propriedade Pixel per Inch será moditificada, e gravada para o DFM se você salvar o projeto. É melhor testar a aplicação rodando sozinho, e editar o formulário em apenas uma resolução. Editar em várias resoluções e tamanhos de fonte provoca problemas de fluxo e tamanho dos componentes.

*Falando em fluxo de componentes, não represente o formulário em escala muitas vezes, quando estiver sendo criado ou quando tiver sendo executado. Cada escala introduz erros de roundoff que se acumulam muito rapidamente, uma vez que as coordenadas são rigorosamente interias. Quando valores fracionários forem retirados das origens e tamanhos do controle com cada sucessiva representação em escala, os conttroles parecerão deslizar para noroeste e ficar menores. Se você quer deixar seus usuários representarem o formulários em escala quantas vezes quiserem, comece com um formulário recentemente criado para que erros de escala não se acumulem.

* Definitivamente, não mexa na propriedade Pixel pre Inch do formulário.

* Em geral, não é necessário criar formulários em uma resolução específica, mas é essencial que você os revise em 640 x 480 com fontes pequenas e/ou grandes, e em alta resolução com fontes pequenas e/ou grandes antes de liberar suas aplicações. Isso deverser parte de sua lista de conferência para testar a compatibilidade do sistema regularmente.

* Preste bastante atenção em todos os componentes que são basicamamente TMemo de uma linha - com oTDBLookupCombo. O controle de edição (multi-linhas) do Windows sempre mostra apenas linhas inteiras de texto. Se o controle for muito curto para sua fonte, um TMemo não mostrará coisa alguma, e um TEdit mostrará um pedaço do texto. É melhor fazer esses componentes um pouco maiores do que deixá-los um pixel menores e não aparecer nada do texto.

* Tenha em mente que toda representação em escala é proporcional à diferença da altura da fonte entre o modo de execução e o modo de desenho, NÃO à resolução ou ao tamanho do monitor. Lembre também que as origens dos seus controles serão alteradas quando o formulário for representado em escala. Você não pode aumentar componentes muito bem sem também movê-los um pouco, novamente.

Obtendo e modificando a posição do cursor em um TMemo

Modificando a posição:

ActiveControl:=Memo1;

MemoCursorTo(Memo1,2,3);

Obtendo a Posição:

GetMemoLineCol(Memo1,Linha,Coluna);

segunda-feira, 12 de setembro de 2011

Formatando Strings

A função Format requer como parâmetros uma string com o texto básico e alguns marcadores de lugar (usualmente indicadas pelo sínbolo %) e um array de valores, um de cada marcador de lugar. Por exemplo, para formatar dois números em uma string você pode escrever.
Format ('Primeiro %d, Segundo %d ', [n1, n2]);
Onde n1 e n2 são dois valores Integer.
Especificador
Descrição
d (decimal)
O valor inteiro correspondente é convertido para uma string de dígitos decimais
x (hexadecimal)
O valor inteiro correspondente é convertido para uma string de dígitos hexadecimais
p (ponteiro)
O valor inteiro correspondente é convertido para uma string expressa com dígitos decimais
s (string)
A string correnpondente, caractere, ou valor Pchar é copiado para uma string
e (hexadecimal)
O valor de ponto flutuante correspondente é convertido para uma string.
f (ponto flutuate)
O valor de ponto flutuante correspondente é convertido para uma string.
g (geral)
O valor de ponto flutuante correspondente é convertido para uma string decimal menor possível usando notação de ponto flutuante ou exponencial.
x (número)
O valor de ponto flutuante correspondente é convertido para uma string de ponto flutuante mas usa também separador de milhares.
m (moeda)
O valor de ponto flutuante correspondente é convertido para uma string representando uma quantidade em dinheiro. A conversão é baseada nas configurações regionais.

Para que servem OnGetEditMask, OnGetEditText e OnSetEditText do TStringGrid

O evento OnGetEditMask ocorre quando entramos no modo de edição. Neste momento podemos verificar em qual linha/coluna se encontra o cursor e então, se quiser, poderá especificar uma máscara de edição. Exemplo:

procedure TForm1.StringGrid1GetEditMask(Sender: TObject; ACol, ARow: Integer; var Value: String);
begin
  if (ARow = 1) and (ACol = 1) then
  Value := '(999) 999-9999;1;_'; // Telefone
end;
O evento OnGetEditText ocorre também quando entramos no modo de edição. Neste momento podemos manipularmos o texto da célula atual (linha/coluna) e então podemos simular algo tal como uma tabela onde opções podem ser digitadas através de números. Exemplo:

procedure TForm1.StringGrid1GetEditText(Sender: TObject; ACol, ARow: Integer; var Value: String);
begin
  if (ARow = 1) and (ACol = 2) then begin
  if StringGrid1.Cells[ACol, ARow] = 'Ótimo' then
  Value := '1'
  else if StringGrid1.Cells[ACol, ARow] = 'Regular' then
  Value := '2'
  else if StringGrid1.Cells[ACol, ARow] = 'Ruim' then
  Value := '3';
  end;
end;
 
O evento evento OnSetEditText ocorre quando saímos do modo de edição. Neste momento podemos manipular a entrada e trocar por um texto equivalente. Normalmente usamos este evento em conjunto com o evento OnGetEditText. Exemplo:

procedure TForm1.StringGrid1SetEditText(Sender: TObject; ACol, ARow: Integer; const Value: String);
begin
  if (ARow = 1) and (ACol = 2) then begin
  if Value = '1' then
  StringGrid1.Cells[ACol, ARow] := 'Ótimo'
  else if Value = '2' then
  StringGrid1.Cells[ACol, ARow] := 'Regular'
  else if Value = '3' then
  StringGrid1.Cells[ACol, ARow] := 'Ruim'
  end;
end;
Observações

Para testar o exemplo anterior crie um novo projeto e coloque no Form1 um TStringGrid. Mude os três eventos mencionados conforme os exemplos. Execute e experimente digitar nas céluas 1 e 2 da primeira linha (na parte não fixada, é claro!).

Usando MessageBox

Para que as mensagens apareçam em portugues (na lingua no sistema) não eh necessário a tradução das units.

Invés de usar a função messagedlg eh melhor usar a função MessageBox

Sintax:

MessageBox (Handle, Messagem, Caption, Botoes)
onde

Handle : Endereço do form na memória ; Sempre usu Application.Handle
Messagem : A messagem a ser mostrada
Caption : O titulo da messagem
Botoes : Os Botoes que irao ser mostrados. Na lingua do sistema


MB_ABORTRETRYIGNORE A messagem mostra os tres botoes: Abort, Retry, and Ignore.
MB_OK A messagem mostra um botoao: OK. This is the default.
MB_OKCANCEL A messagem mostra os dois botoes: OK and Cancel.
MB_RETRYCANCEL A messagem mostra os dois botoes: Retry and Cancel.
MB_YESNO A messagem mostra os dois botoes: Yes and No.
MB_YESNOCANCEL A messagem mostra os tres botoes: Yes, No, and Cancel.

Sons
MB_ICONEXCLAMATION, MB_ICONWARNING: Mostra o icone de exclamação e som conrrespondente. Analo aos demais
MB_ICONINFORMATION, MB_ICONASTERISK
MB_ICONQUESTION
MB_ICONSTOP,
MB_ICONERROR,
MB_ICONHAND
Botoes padrao
MB_DEFBUTTON1: Padrao nao precisa ser colocado.
MB_DEFBUTTON2: Coloca o segundo botao como padrao
MB_DEFBUTTON3: Coloca o terceiro botao como padrao
MB_DEFBUTTON4: Coloca o quarto botao como padrao
Respostas
IDABORT
IDCANCEL
IDIGNORE
IDNO
IDOK
IDRETRY
IDYES

Exemplo
Case MessageBox (Application.Handle, Pchar ('Deseja excluir o arquivo' + #13 + Label1.caption), 'Exclusao de arquivo', MB_YESNOCANCEL+MB_EXCLAMATION+MB_DEFBUTTON2) of
idYes: Procedimento
idNo: Procedimento
idCancel: Procedimento
end;

quinta-feira, 8 de setembro de 2011

Ocultar aplicação da lista de tarefas - CTRL+ALT+DEL

- Declare a função abaixo antes da palavra implementation:

function RegisterServiceProcess(dwProcessID, dwType: Integer): 
  Integer; stdcall; external 'KERNEL32.DLL';

- Coloque dois botões no Form;
- No evento OnClick do Button1 coloque:

RegisterServiceProcess(GetCurrentProcessID, 1);

- No evento OnClick do Button2 coloque:

RegisterServiceProcess(GetCurrentProcessID, 0);

=== Para testar ===

Clique no Button1 e pressione CTRL+ALT+DEL. O seu programa
não aparecerá na lista.

Clique no Button2 e pressione CTRL+ALT+DEL. Agora seu programa
aparecerá na lista.

quarta-feira, 7 de setembro de 2011

Mudar a cor do Edit ao receber o foco

Alguns programas mostram o Edit que está com o foco em uma cor diferente dos demais. 
Como fazer isto em Delphi?
Na seção private do form declare o procedimento abaixo:
  
  private
    procedure MudancaDeFoco(Sender: TObject);
  public
  end;

Na seção implementation, escreva o código do procedimento:

{ Esta rotina será chamada através do evento OnExit (perda do foco)
  de todos os componentes do tipo TEdit que existirem no form. }
procedure TForm1.MudancaDeFoco(Sender: TObject);
var
  I: integer;
  Ed: TEdit;
begin
  { Percorre a matriz de componentes do form }
  for I := 0 to ComponentCount - 1 do
    { Se o componente é do tipo TEdit... }
    if Components[I] is TEdit then
    begin
      { Faz um type-casting pata o tipo TEdit }
      Ed := Components[I] as TEdit;

      { Se o Edit está com o foco... }
      if Ed.Focused then
        Ed.Color := clYellow { Amarelo }
      else
        Ed.Color := clWhite; { Branco }
    end;
end;

No evento OnCreate do Form, coloque o código abaixo:

procedure TForm1.FormCreate(Sender: TObject);
var
  I: integer;
begin
  { Percorre a lista de componentes do form (matriz de componentes)
    e verifica cada componente para saber se é um TEdit. Se for,
    associa o evento OnExit do componente com a procedure
    "MudancaDeFoco". }
  for I := 0 to ComponentCount - 1 do
    if Components[I] is TEdit then
      (Components[I] as TEdit).OnExit := MudancaDeFoco;
end;

No evento OnActivate coloque:

procedure TForm1.FormActivate(Sender: TObject);
begin
  { Esta chamada é necessária para que o estado inicial seja
    controlado. }
  MudancaDeFoco(nil);
end;

Observações

Existem outras técnicas mais profissionais para resolver o problema proposto. Uma alternativa excelente é a criação de um novo componente herdado da classe TEdit (ou TCustomEdit) que implemente a mudança de cor no método DoEnter e DoExit.

Arredondamento financeiro

É muito comum encontrar programadores Delphi que têm dúvidas sobre como arredondar um valor real para "n" casas após o separador decimal. A princípio parece um problema simples, pois o próprio Delphi já possui uma função que arredonda para o inteiro mais próximo, a qual poderia facilmente ser utilizada para arredondar para qualquer quantidade de casas decimais. Exemplo:

{ x receberá o valor de y arredondado para 2 casas após o separador. }
x := Round(y * 100) / 100;

{ z receberá o valor de y arredondado para 3 casas após o separador. }
z := Round(y * 1000) / 1000;
No entanto dois problemas poderão aparecer com os exemplos acima:
  • O arredondamento feito pelo Delphi difere daquele feito pelas calculadores financeiras, bem como bancos de dados como InterBase e FireBird.
  • poderão ocorrer pequenos arredondamentos devido ao modo como o Delphi trata números reais, tais como aparecer 3.9999999... em vez de 4.
A função abaixo resolve estes dois problemas.
{ Esta função faz arredondamento de valores reais para "n" casas
  decimais após o separador decimal, seguindo os critérios das
  calculadoras financeiras e dos bancos de dados InterBase e FireBird.
}
function TBRound(Value: Extended; Decimals: integer): Extended;
var
  Factor, Fraction: Extended;
begin
  Factor := IntPower(10, Decimals);
  { A conversão para string e depois para float evita
    erros de arredondamentos indesejáveis. }
  Value := StrToFloat(FloatToStr(Value * Factor));
  Result := Int(Value);
  Fraction := Frac(Value);
  if Fraction >= 0.5 then
    Result := Result + 1
  else if Fraction <= -0.5 then
    Result := Result - 1;
  Result := Result / Factor;
end;

Consultar por mês de um campo data

Problema:

Tenho um cadastro de clientes com Codigo, Nome, DataNasc, etc.
Preciso fazer uma consulta onde apareceão apenas os clientes
que fazem aniversário em determinado mês. Como fazer?

Solução:

Use uma Query como abaixo:
- Coloque no form os seguintes componentes:
  * TQuery
  * TDataSource
  * TDBGrid
  * TEdit
  * TButton

- Altere as propriedades dos componentes como abaixo:
  * Query1.DatabaseName = (alias do BDE)
  * DataSource1.DataSet = Query1
  * DBGrid1.DataSource = DataSource1
  
- Coloque o código abaixo no evento OnClick de Button1:

  Query1.Close;
  Query1.SQL.Clear;
  Query1.SQL.Add('select * from dCli');
  Query1.SQL.Add('where extract(month from DataNasc) = :Mes');
  Query1.ParamByName('Mes').AsInteger := StrToInt(Edit1.Text);
  Query1.Open;

- Execute. Digite um número de 1 a 12 no Edit e clique no 
  botão.

Observações

Os números de 1 a 12 representam, respectivamente, os meses de Janeiro a Dezembro. Este exemplo foi testado com Delphi4, BDE5 e tabela Paradox7.

DBGrid zebrado

Programe o evento OnDrawColumnCell do DBGrid como abaixo:

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject;
  const Rect: TRect; DataCol: Integer; Column: TColumn;
  State: TGridDrawState);
begin
  if State = [] then
  begin
    if Table1.RecNo mod 2 = 1 then
      DBGrid1.Canvas.Brush.Color := clAqua
    else
      DBGrid1.Canvas.Brush.Color := clWhite;
  end;
  DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;

Observação:
O objeto Table1 é da classe TTable (relativa ao BDE), mas esta dica poderá ser usada com outros DataSet's, tais como IBDataSet, ClientDataSet, etc.

domingo, 4 de setembro de 2011

Usando funções no MySQL


Vou tentar passar aqui, um pequeno exemplo de como usar funções do MySQL em nossas instruções SQL.
Em primeiro lugar, certifique-se que você possua instalado uma versão a partir da 5.x do MySQL em seu micro.
No nosso exemplo, criaremos e utilizaremos uma função que retornará a idade do cliente
Crie uma pequena tabela com a seguinte estrutura:

CREATE TABLE `clientes` (
`ID` int(11) NOT NULL auto_increment,
`NOME` varchar(45) default NULL,
`DATANASC` date default NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;



Inclua as linhas abaixo na tabela, ou digite suas próprias informações:


INSERT I NTO `clientes` (`ID`, `NOME`, `DATANASC`) VALUES
(1, 'SILVIO SANTOS', '1971-09-18'),
(2, 'SILLAS SANTOS', '1993-04-27');


Agora, crie a função conforme o exemplo abaixo:


CREATE FUNCTION `idade`(pDATA DATE)
RETURNS varchar(15) CHARSET latin1
NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER
COMMENT ''
Begin
DECLARE vIDADE varchar(30);
IF DATE_FORMAT(NOW( ),'00-%m-%d') >= DATE_FORMAT(pDATA,'00-%m-%d') THEN
SET @idade=DATE_FORMAT(NOW( ),'%Y')-DATE_FORMAT(pDATA,'%Y');
ELSE
SET @idade=DATE_FORMAT(NOW( ),'%Y')-DATE_FORMAT(pDATA,'%Y')-1;
END IF;
RETURN(concat(@idade,' anos'));
end;


Esta função, retornará a idade da pessoa de acordo com o campo DATANASC, da tabela ‘clientes’.
Veja que a função receberá um valor (pDATA, que no nosso exemplo será o campo ‘DATANASC’ de nossa tabela) e fará a subtração da “dat a corrente” pela data do campo DATANASC.
Dentro da função, criamos uma variável (vIDADE), onde armazenaremos o valor a ser retornado pela função. Note também que a função possui retorno do tipo String (RETURNS varchar(30)), que deverá ter o tamanho suficiente para comportar o resultado retornado. Ou seja, se o valor retornado tiver 15 caracteres, o ‘RETURNS’ da função precisa ter tamanho de 15 ou mais caracteres, senão, pode acontecer de não retornar nenhum valor.

Após ter criado a tabela, inserido as linhas na mesma e criado a função, execute a seguinte instrução:


SELECT NOME, idade(DATANASC) IDADE FROM clientes;


No SQL acima, a consulta irá retornar o Nome, e a idade assim: “XX anos”, para cada cliente.

Como você viu, utilizar funções dentro do banco de dados por ser muito mais prático do que fazê-lo dentro da aplicação (me refiro a funções para retorno de dados, claro).

Lembrando que este exemplo é apenas uma base, pois podemos criar funções para qualquer necessdiade, como: calculo de preços, juros de mora, datas, horas, concatenação de campos complexos, junções de tabelas (como nos inner, left e right joins (desde que precise retornar apenas um valor), e muito mais.

JSON e Generics - Serializando Objetos


Desta vez vou demonstrar duas novas tecnologias que estão sendo muito melhoradas no Delphi desde a versão 2009 até a ultima versão o Delphi XE. Generics é uma forma de poder

...passar parâmetro de tipos para classes e métodos, possibilitando assim reutilização de código, por exemplo e, JSON (JavaScript Object Notation) é uma forma de troca de dados entre aplicações, semelhante ao XML, porém com uma sintaxe bem mais simples e leve. O Delphi XE, como algumas versões anteriores, dá suporte a implementação usando JSON, e em nosso exemplo vamos criar um método totalmente genêrico que recebe um objeto e um tipo e usaremos o JSON para formatar este objeto e gravar no disco usando a nova classe de IO (input/output) do Delphi, e um método de leitura deste arquivo no formato JSON para retornar o mesmo a um Objeto, novamente passando o tipo do objeto e o caminho onde se encontra o arquivo.

Primeiro vamos criar uma nova classe do tipo TPessoa:

type
  TPessoa = class
  private
    { Private declarations }
    FPessIden: Integer;
    FPessNome: string;

  public
    { Published declarations }
    property PessIden: Integer read FPessIden write FPessIden;
    property PessNome: string read FPessNome write FPessNome;

  end;

Para conseguirmos desenvolver nossos métodos, temos antes que adicionar ao uses as unit’s citadas abaixo. Acima de cada unit tem a descrição de sua função no projeto:

uses
  SysUtils,

  { Unit que contém os novos métodos de I/O }
  IOUtils,

  { Unit's necessárioas para usar JSON }
  DBXJSONReflect, DBXJSON,

  { Unit necessária para usar Generic }
  Generics.Collections;

Agora vamos criar nossa classe que irá conter os métos necessários para carregar e salvar os dados no disco. O class antes da function indica que estes métodos serão estáticos, não sendo necessário instânciar a classe TJSONTools para usar estes métodos, e a descrição T : class, indica que este método recebe um tipo e que a variável obj passada por parâmetro é deste tipo e, na outra função que ela retorna o tipo especificado.

type
  TJSONTools = class
    public
    { Public declarations }
    class function saveFileJSON(obj: T; const filePath: string): Boolean;
    class function loadFileJSON(const filePath: string): T;
  end;

Agora nosso método que irá gravar no disco o objeto da classe descrita acima.:

class function TJSONTools.saveFileJSON(obj: T; const filePath: string): Boolean;
var
  Marshal: TJSONMarshal;

begin
  Marshal := TJSONMarshal.Create(TJSONConverter.Create());
  try
    try
      TFile.WriteAllText(filePath, (Marshal.Marshal(obj) as TObject).ToString);

      Result := True;
    except
      Result := False;
    end;
  finally
    FreeAndNil(Marshal);
  end;
end;

Agora o método responsável por ler o arquivo e carregar novamente os dados para um objeto:

class function TJSONTools.loadFileJSON(const filePath: string): T;
var
  Unmarshal: TJSONUnMarshal;
  obj: TJSONObject;

begin
  Unmarshal := TJSONUnMarshal.Create();
  try
    try
      if not(FileExists(filePath)) then
        Exit(nil);

      obj := TJSONObject.ParseJSONValue(
        TEncoding.ASCII.GetBytes(TFile.ReadAllText(filePath)),
        0) as TJSONObject;

      Result := T(Unmarshal.Unmarshal(obj));
    except
      Exit(nil);
    end;
  finally
    FreeAndNil(Unmarshal);
  end;
end;

Agora uma forma de poder usar nossas classes:

var
  objPessoa: TPessoa;

begin
  objPessoa:= TPessoa.Create();
  objPessoa.PessIden := 1;
  objPessoa.PessNome := 'Active Delphi';

  if (TJSONTools.saveFileJSON&ltTPessoa&gt(objPessoa, 'C:\pessoa.txt')) then
    ShowMessage('Criou o arquivo no caminho: ' + #13 + ' C:\pessoa.txt');

  objPessoa := TJSONTools.loadFileJSON&ltTPessoa&lt('C:\pessoa.txt');
  if (Assigned(objPessoa)) then
  begin
    ShowMessage(
      'PessIden: ' + IntToStr(objPessoa.PessIden) + #13 +
      'PessNome: ' + objPessoa.PessNome
    );
  end
  else
    ShowMessage('Não foi possivel des-serializar o objeto');
end;

Este foi apenas um exemplo didático usando JSON e Generics no Delphi. Estes métodos ainda podem ser refatorados e até criado validações para tratar possíveis erros de parâmetros, que podem ser identificados criando testes unitários sobres estes métodos.

Obrigado pela leitura e até a próxima!

Clique aqui para fazer o download do exemplo, feito com Delphi XE

domingo, 14 de agosto de 2011

Dicas para melhorar a performance do servidor Firebird


Defina índices para todas as colunas que vão ser usadas para join’s ou ordenações. Os índices são estruturas de dados de árvores balanceadas que fornecem um grande aumento na velocidade da procura dos valores nas colunas em que os índices são definidos. Isto é especialmente útil quando ordenamos por uma coluna em particular ou juntamos duastabelas por uma coluna que as duas têm em comum.Lembre que um índice Firebird é específico para uma direção de ordenação, ascendente ou descendente (ASCENDING x DESCENDING), e a direção de um índice deve ser igual à direção escolhida quando se faz a ordenação. Se você for usar as duas direções, ascendente ou descendente, crie dois índices para as chaves de ordenação. As inclusões e alterações usam índices para serem gravadas, o que pode prejudicar a performance durante um INSERT ou UPDATE, mas alguns custos impostos durante a entrada de dados podem resultar em um grande ganho de performance mais tarde na pesquisa destes dados. Para minimizar a perda de performance durante um INSERT, considere em desabilitar os índices durante um grande volume de INSERTs. Isto irá ‘desligar’ os índices, tornando-os indisponíveis para ajudar a acelerar as pesquisas, mas também não vão ser atualizados durante os INSERTs.Então, reabilite-os depois de terminados os INSERTs, e os índices serão atualizados e balanceados uma vez para todos os dados incluídos.Periodicamente reconstrua os índices, desativando e ativando novamente (ALTER INDEX nome INACTIVE; seguido de ALTER INDEX nome ACTIVE;). Recalcule o índice seletivamente (SET STATISTICS INDEX nome;) se uma mudança na tabela afeta a distribuição regular dos valores e dados. Os índices também são reconstruídos do mesmo modo durante o restore.

Otimize as pesquisas 


O Firebird irá analisar uma pesquisa (query) e o otimizador fará a melhor estimativa de quais índices deverão ser empregados para acelerar a pesquisa. Mas nenhum otimizador automático é infalível. Para queries complexas que serão usadas freqüentemente, ou que serão aplicadas a um número elevado de dados, construa a pesquisa cuidadosamente. Usea opção SET PLAN da ferramenta ISQL para testar queries, ver sua performance e ver qual PLAN o otimizador escolheu. No Interbase v4.0, v4.1 e v4.2 existem alguns problemas conhecidos em tais queries usando a sintaxe JOIN na linguagem ANSI SQL-92, que não são analisados e otimizados corretamente. Evite esta sintaxe ou, se tiver que usá-la, especifique seu próprio PLAN.  


Ajuste o cache do Firebird 


O Firebird mantém seu próprio cache de páginas do banco de dados correntemente em uso na RAM do servidor. O tamanho padrão do cache na versão superserver (Windows) é 2048 páginas de banco de dados (isto é compartilhado por todos os clientes conectados a um dado banco de dados). Para um banco de dados com 1Kb de páginas, isto ainda ésomente 2048Kb. A maioria dos servidores tem muita memória, então ponha em uso! Experimente ajustar o cache de 256 até 10.000 páginas. Em algum ponto verá diminuir os retornos, mas alguma experimentação irá revelar este ponto. 


Faça Backup / Restore 


Existem vários benefícios em fazer periodicamente um backup/restorede um banco de dados Firebird usando o utilitário GBAK:-reconstruir índices;-eliminar versões obsoletas de registros (‘garbage’);-defragmentar páginas de dados;-regravar tabelas de dados contiguamente; e

-fazer uma cópia dos dados!

Use processamento Cliente-servidor 


Usando funções externas (UDFs), Triggers, Stored Procedures e Select Procedures fazem com que o banco de dados faça muito processamento no servidor, que provavelmente é um computador muito mais potente que a estação de trabalho. Esta técnica evita também tráfego de rede desnecessário.  


Use o cache de disco do Sistema Operacional 


A maioria dos sistemas operacionais oferece sistema de cache de disco para leitura/gravação. Com isto os programas podem fazer suas gravações na RAM do servidor, e o sistema operacional irá salvar este conteúdo fisicamente no disco periodicamente. Fazendo sempre pequenas gravações ou fazer de uma só vez numa operação maior podeaumentar bastante a performance de leitura/gravação de dados. Usar este sistema de cache de disco é algumas vezes chamado de leitura/gravação assíncrona (asynchronous I/O) ou gravação em cache (cached writes). Forçando a gravação direta dos dados no disco,sobrepondo o sistema de cache, é chamado de leitura/gravação síncrona (synchronous I/O) ou gravação forçada (forced writes). O sistema de leitura/gravação assíncrona tem o benefício de aumentar a performance, mas como a RAM é um sistema volátil de armazenamento, pode perder dados se houver uma queda de energia ou uma falha no servidor ou ainda uma reinicialização do servidor quando o cache ainda não tiver sidodescarregado no disco. A leitura/gravação síncrona garante que nada será perdido por estar no cache, mas ler ou gravar diretamente no disco é bem mais lento que fazê-lo na RAM.O Firebird permite fazer a leitura/gravação assíncrona ou síncrona. O comando GFIX –WRITE SYNC .GDB faz o banco de dados usar a gravação forçada (forced writes), e o comando GFIX –WRITE ASYNC .GDB faz o banco usar a gravação em cache (cached writes).A gravação em cache pode ganhar muito em performance no Firebird, mas devem ser tomadas medidas de proteção contra perda de dados. Por exemplo, usando espelhamento de disco para manter uma duplicação dos dados. Usando também no-break e estabilizadores para garantir uma energia estável e ininterrupta para o servidor. Com medidas como estas,a leitura/gravação assíncrona pode trazer benefícios sem sacrificar a segurança. 


Administre o protocolo de rede 


O protocolo TCP/IP é muito mais rápido que o SPX ou NetBEUI. Conectar sua rede usando TCP/IP resultará em uma melhor performance. O serviço NetBEUI tem um prioridade baixa em servidores NT, mas existe um meio no sistema operacional para aumentar a prioridade deste serviço. Alguns usuários dizem ter aumentado a performance depois de ter feito isto. Veja na documentação do Windows NT as instruções paraajustar a prioridade dos serviços NetBEUI. NOTA: A partir do Windows NT 4.0, a performance da implementação do Microsoft NetBEUI foi aumentada. É agora pelo menos tão boa quanto a implementação do Microsoft TCP/IP, entretanto, em uma rede ocupada, o NetBEUI é ainda limitado por ser um protocolo independente de conecção. Isto significa que cada host na rede deve checar cada pacote para ver seu conteúdo. Com mais hosts transmitindo na rede, cada host deve manipular uma crescente leitura de "números errados". 


Escolha o sistema operacional 


Sistemas operacionais basados no UNIX/Linux são sempre mais rápidos em multiprocessamento que sistemas Windows em um mesmo equipamento. Se velocidade é algo importante para você considere o uso desta plataforma. Pense sempre em utilizar o Firebird como servidor dedicado, que não funcione como servidor de arquivos, pois ele poderá ficar sentado no "banco de trás" nas questões de prioridade de processamento.No Windows NT o servidor é configurado para dar prioridade ao compartilhamento de arquivos. Você pode modificar esta configuração no servidor: Painel de Controle -> Rede -> Software de Rede. Modifique para "balancear" ou "servidor de banco de dados". Esta modificação irá resultar em uma melhora dramática na performance do Firebird.O uso de sistemas operacionais não específicos para uso de Servidores como os Windows 9x, Me ou XP Home podem prejudicar sua performance, além das fragilidades conhecidas. 


Considere a limpeza do lixo (garbage collection) 


O Firebird tem por padrão a função de automaticamente limpar as antigas versões dos registros quando estas se tornam muito numerosas. O problema deste método é que se um processo cliente que for sem sorte suficiente para iniciar uma transação que coincida com o início da limpeza automática, vai ter que esperar o trabalho de limpeza. O processo clienteatrasa enquanto o processo de limpeza é feito. Desabilite a limpeza automática (automatic garbage collection), usando GFIX –h 0, em favor da limpeza programada (scheduled database sweep), usando GFIX –s. Isto irá eliminar a perda na performance do cliente.Fazer um backup e restore efetivamente faz a mesma coisa do que uma limpeza completa no banco de dados. O backup somente copia a versão mais recente de cada registro, nunca copia versões anteriores, assim, quando os dados são restaurados, existe somente uma versão de cada registro. Existem também outros benefícios de fazer backup/restore,descritos na seção própria deste artigo. Falando em limpeza do lixo, muitos programadores não se dão conta da necessidade de iniciar e terminar (START e COMMIT) suas transaçõescom o banco de dados. Abrir transações impede que a varredura periódica complete a limpeza do lixo, e a performance irá sofrer progressivamente com o tempo. 


Normalize seu banco de dados 


Projete seu banco de dados com uma normalização adequada dos dados. Registros que têm muitos grupos de campos repetidos são maiores que deveriam ser, podem aumentar o custo de ordenação e ainda podem causar a ocupação de mais páginas do que o necessário,resultando em uma maior fragmentação de páginas e um banco de dados enorme sem necessidade.


Pense nas transações 


Projete sua aplicação para que resolva o mais rápido possível as transações depois de abertas. Freqüentemente os programadores de aplicação abrem uma transação e apresentam uma tela de entrada de dados para o usuário. Ao invés disso, pegue os dados com o usuárioantes, abra uma transação para dar o INSERT e então dê o COMMIT na transação imediatamente. O propósito disto é diminuir o tempo de duração das transações, que diminui a probabilidade de duas aplicações concorrentes conflitarem em um bloqueio de registro. Isto também ajuda a evitar muitos ROLLBACKs e tráfego de rede, já que a sua aplicação decide quando submeter os dados ao banco ou descartá-los.  


Programe com a API do Firebird 


Um programa escrito em C com SQL embutido responde melhor que uma aplicação cliente Firebird escrita em Paradox, Delphi ou outra ferramenta visual. A idéia é programar diretamente na API do Firebird ao invés de adicionar camadas intermediárias com mais um protocolo de rede sobre ele.

domingo, 31 de julho de 2011

Migrando aplicativos do Delphi 5 para o 6 e 7


Neste artigo, será demonstrado como solucionar alguns problemas comumente encontrados por desenvolvedores Delphi quando fazem migração de aplicativos e componentes de versões anteriores ao Delphi 6 (mais precisamente o Delphi 5) para as versões 6 e/ou 7. O que funcionar para o Delphi 6 provavelmente funcionará para o Delphi 7, já que as mudanças mais expressivas nas bibliotecas padrões para a criação de componentes foram realizadas no Delphi6, por motivos de compatibilidade.
A Borland realizou diversas mudanças em métodos e classes nas antigas bibliotecas (VCL) em decorrência da necessidade de compatibilizar o máximo possível o Delphi 6 com o Kylix, ou em outras palavras, a biblioteca de componentes VCL (Visual Component Library) com a biblioteca inter-plataforma CLX (Component Library for Cross-Plataform), utilizada mais especificamente no Kylix (apesar da mesma ser utilizada no Delphi também).

Serão listadas a seguir as principais mudanças que conheço e que devem ajudar o leitor a migrar seus aplicativos e componentes para as versões 6 e 7 do Delphi. São estas:

* A implementação de Classes e Métodos da unit "Dsgnintf.pas" foram divididos em duas units (pelo menos até onde conheço): a DesignIntf.pas e a DesignEditors.pas. Ambas estão localizadas em ($Delphi)\Source\ToolsAPI, onde ($Delphi) é o diretório onde o Delphi está instalado. Ao invés de incluir o caminho citado em seu "Enviroment Options\Library\LibraryPath" para utilizar as units, você pode optar por colocar na seção requires (e realmente acho preferível isto se estiver trabalhando com pacotes) o pacote "designide.dcp", localizado na pasta ($Delphi)\Lib. Ele já inclui estas bibliotecas e outras comumente utilizadas além de uma nova interface, a IComponentDesigner, contida em ComponentDesigner.pas.

* A procedure EditProperty da classe TDefaultEditor teve seus parâmetros alterados; no Delphi 5, a mesma era declarada desta forma:

  EditProperty(PropertyEditor: TPropertyEditor; var Continue, FreeEditor: Boolean); virtual;

e agora a mesma é declarada assim:

  procedure EditProperty(const Prop: IProperty; var Continue: Boolean); virtual;

Observe que o parâmetro PropertyEditor do tipo classe TPropertyEditor passou agora a se chamar Prop e ser do tipo interface IProperty e, o parâmetro FreeEditor deixou de existir. Essa nova implementação é realizada na unit "DesignEditors.pas".

* As rotinas de manipulação de objetos ou variáveis do tipo Variant passaram da unit System.pas para uma unit própria, a Variants.pas.

* Interfaces como a IFormDesigner , IDesignerSelection e IdesignerSelection sofreram alterações. Quanto as duas primeiras, as mudanças são respectivamente estas: mudança de nome para "IDesigner" e a adição de uma função GET que retorna um TPersistentequando indicado o índice do seu membro. Exemplo:

Nas versões anteriores ao Delphi 6, se você quisesse usar o TPersistent de um objeto, deveria escrever:

  var p: TPersistent;
  ...
  P := Selections[i] as TPersistent;

Agora basta escrever:

  var p: TPersistent;
  ...
  P := Selections.get[i];

* A função CollectionsEqual implementada na Unit Classes.pas teve seus parâmetros alterados; no Delphi 5, a mesma era declarada desta forma:

  function CollectionsEqual(C1, C2: Tcollection): Boolean;

e agora a mesma é declarada assim:

  function CollectionsEqual(C1, C2: TCollection; Owner1, Owner2: TComponent): Boolean;

* As constantes que estavam na unidade DesignConsts.pas, foram colocadas juntamente com os menus em DesignMenus.pas.

Dicas

Para aproveitar um mesmo código-fonte em diferentes versões do Delphi, você pode utilizar a diretiva de compilação {$IFDEF name}, que especifica que um determinado bloco de código compreendido pela cláusula só será compilado se o parâmetro "name" estiver definido.

Parâmetros de versão

  Delphi3 - VER80;
  Delphi5 - VER130;
  Delphi6 - VER140;
  Delphi7 - Ver150;

Como utilizar

Exemplo1:
No caso de um código que referencie a DsgnIntf.pas e seja utilizado no Delphi 6 ou posterior, você pode fazer o seguinte:

uses
  Windows, Messages, SysUtils, Classes, Graphics,
  {$IFDEF VER130} // se for Delphi5
    DsgnIntf
  {$ELSE}
    {$IFDEF VER140} // se for Delphi6
      DesignIntf, DesignEditors
    {$ELSE}
      {$IFDEF VER150} // se for Delphi7
        DesignIntf, DesignEditors
      {$ENDIF}
    {$ENDIF}
  {$ENDIF}

Observação: No lugar de você colocará o caractere adequado, "," para continuar a declaração de Units ou ";" para fechamento de declaração.

Exemplo 2:
No caso de um código que referencie a System.pas e seja utilizado no Delphi 6 ou posterior, você pode fazer o seguinte:

uses
  Windows, Messages, SysUtils, Classes, Graphics, System,
  {$IFDEF VER140} // se for Delphi6
    Variants
  {$ELSE}
    {$IFDEF VER150} // se for Delphi7
       Variants
    {$ENDIF}
  {$ENDIF}

Bem, desejo que isto possa ajudar todos aqueles que trabalhem com Delphi, seja usuário iniciante, intermediário ou avançado.

domingo, 24 de julho de 2011

Criando um Calendário Simples

Veja nesta dica como criar um calendário bastante simples, explorando o componente TMonthCalendar

    Crie um novo formulário em sua ara de trabalho(file > new > form).
    Altere suas propriedades de acordo com a tabela seguinte:

Propriedade         Valor

BordeStyle          bsDialog
Caption             Calendário
Color               clBtnFace
Font.Name           Arial
Font.Size           7
FormStyle           fsNormal
Height              437
Name                Calendario
Position            poScreenCenter
Width               703

    Insira no formulário do calendário um componente monthcalendar localizado na paleta win32.
    Ajuste suas propriedades para:

Propriedade         Valor
Aling               alClient
WeekNumbers         True (exibe o numero das semanas)

    Caso seja necessario, ajuste a altura e largura da janela até que o calendário apresente os 12 meses.

    O componente MonthCalendar possui a propriedade chamada Date, cuja função é definir a data a ser exibida. O problema é que esta propriedade fica ancorada com a data de criação do componente, ou seja, se você o inserir em um form em 16/12/08, sua data ficará fixada em 16/12/08, independente de qual seja a data em que você estiver executando o programa. Para resolver este problema, insira no evento onActivate da janela calendário:

procedure TCalendario.FormActive(Sender: TObject);
begin
  MONTHCALENDAR1.DATE := DATE;
end;

    Pressione F9 e veja o resultado.

Você também pode personalizar seu calendário através da propriedade CalColors. Se puder usar um form com uma maior extensão para visualização, sugiro a fonte arial 8 em negrito. Fica perfeito.

terça-feira, 19 de julho de 2011

Criação de DLL - Dynamic Link Libraries


Vou explicar em breves palavras como criar um sistema com a utilização de arquivos dll’s.
1º Regras para escrever uma DLL no Delphi : As funções e/ou procedimentos de DLL devem seguir estas regras:
- Precisam estar listados na cláusula exports da DLL. Isso possibilita que no momento de chamar a funções e/ou procedimentos, seja aceito pelo programa principal.
- As funções exportadas precisam ser declaradas como stdcall;
- Os tipos de parâmetros de uma DLL devem ser de tipos padrão do Windows, pelo menos se você quiser usar a DLL dentro de outros ambientes de desenvolvimento, como por exemplo, C++.
- Uma DLL pode usar dados globais que não serão compartilhadas pelos programas base. Sempre que uma aplicação carrega uma DLL, ele armazena os dados globais da DLL em seu próprio espaço de endereço
 
2º Neste passo vamos tirar a dúvida a uma pergunta muita frequente; “Como criar uma DLL?”.
Dando início ao desenvolvimento de DLLs no Delphi, vou mostrar a vocês uma biblioteca muito simples. O principal objetivo desse exemplo é mostrar a sintaxe utilizada para definir uma DLL no Delphi.
Para começar, selecione o comando File/New, escolha a opção DLL na página New do Object Repository. Isso cria um Codigo-Fonte muito simples.
Aqui está código fonte da nossa primeira DLL:
 
Library MINHADLL.DLL;
uses SysUtils,
Classes;
{$R *.res}
function Triple(N: Integer): Integer; stdcall;
begin
  Result := N * 3;
end;
function Double (N: Integer): Integer; stdcall;
begin Result := N * 2;
end;
exports Triple, Double;
end.

3ª E agora por fim vamos incluir a dll num projecto Win32.

Para fazer um formulário usando nossa DLL vamos abrir o Delphi e criar uma nova aplicação.
Colocar dois Edits e dois Buttons, no evento OnClick dos botões faremos as chamadas das funções. Para chamar um função de uma DLL é preciso apenas declarar as funções de forma igual a declarada na dll, ou seja, nome, parâmetros e tipos.
Aqui fica um exemplo:

unit Unit1;
  interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
  type TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
  procedure Button1Click(Sender: TObject);
  procedure Button2Click(Sender: TObject);
  private { Private declarations }
  public { Public declarations }
end;
var Form1: TForm1;
implementation
{$R *.dfm}
 
{funções da DLL Delphi}
  function Double (N: Integer): Integer; stdcall; external 'MINHADLL.DLL';
  function Triple (N: Integer): Integer; stdcall; external 'MINHADLL.DLL';
  procedure TForm1.Button1Click(Sender: TObject);
  var x, y: integer;
  begin
    x := StrToInt(Edit1.Text);
    y := Double(x);
    Edit1.Text := IntToStr(y);
  end;
 
  procedure TForm1.Button2Click(Sender: TObject);
  var x, y: integer;
  begin
    x := StrToInt(Edit2.Text);
    y := Triple(x);
    Edit2.Text := IntToStr(y);
  end;
end.