Terça-feira, Novembro 30, 2004

Eliminando o botão da aplicação na barra de tarefas

Algumas vezes, não queremos que uma aplicação tenha seu botão na barra de tarefas (por exemplo, quando temos um ícone ao lado do relógio e mostramos a Form associada à aplicação). Para eliminar o botão da aplicação na barra de tarefas, basta colocar o seguinte código no evento OnCreate da Form:



SetWindowLong(Application.Handle,GWL_EXSTYLE,
WS_EX_TOOLWINDOW);

Sexta-feira, Novembro 26, 2004

Adicionando os nomes dos arquivos a um menu em .NET

Após terminar o programa da postagem anterior, pensei em portá-lo para .NET usando o Delphi 2005. Usando a VCL.NET não tem nenhum problema, praticamente não devem ser feitas modificações. O que qeria é mudar para WinForms, sem usar as rotinas da VCL ou da RTL do Delphi, apenas o Framework .NET.
Para isso, o programa deve ser inteiramente refeito, inclusive sua interface. A principal mudança está na função PreencheItem que, reescrita, fica assim:



procedure TWinForm.PreencheItem(const Diretorio : String;
AMenuItem : System.Windows.Forms.MenuItem);
var
DirInfo : System.IO.DirectoryInfo;
AFileInfo : Array of System.IO.FileInfo;
FilInfo : System.IO.FileInfo;
ADirInfo : Array of System.IO.DirectoryInfo;
DirecInfo : System.IO.DirectoryInfo;
MenuItem : System.Windows.Forms.MenuItem;
begin
// guarda arquivos no diretório
DirInfo := System.IO.DirectoryInfo.Create(Diretorio);
// AFileInfo tem os arquivos do diretório
AFileInfo := DirInfo.GetFiles('*.*');
// ADirInfo tem os subdiretórios
ADirInfo := DirInfo.GetDirectories;
for DirecInfo in ADirInfo do
// Processa os subdiretórios
if (DirecInfo.Name <> '.') and (DirecInfo.Name <> '..')
then begin
MenuItem := System.Windows.Forms.MenuItem.Create(
DirecInfo.Name);
AMenuItem.MenuItems.Add(MenuItem);
PreencheItem(direcInfo.Name,MenuItem);
end;
for FilInfo in AFileInfo do begin
// Processa os arquivos
MenuItem := System.Windows.Forms.MenuItem.Create(
FilInfo.Name);
Include(MenuItem.Click, MenuItemClick);
AMenuItem.MenuItems.Add(MenuItem);
end;
end;

Aqui o processamento é muito mais simples. Os métodos GetDirectories e GetFiles da classe DirectoryInfo guardam os arquivos e diretórios em matrizes. Então, com o novo for ... in ... do Delphi, processamos estas matrizes, adicionando os novos itens de menu. Para os diretórios, chamamos a função PreencheItem recursivamente.
Aqui, uma observação: os menus em WinForms não tem imagens. Como colocar imagens nos menus? Isto é tema para uma outra postagem :-). Só para efeito de comparação, o programa resultante (com mesma funcionalidade do anterior) tem apenas 30Kb.


Quinta-feira, Novembro 25, 2004

Adicionando os nomes dos arquivos a um menu

Às vezes, queremos adicionar o conteúdo de um diretório (arquivos e subdiretórios) a um menu, de maneira que possamos selecionar o arquivo lá. Para fazer isso, devemos pesquisar o diretório com as funções FindFirst e FindNext, adicionando os novos itens do menu. Como a pesquisa é recursiva, criamos uma função PreencheItem, que prechenche um item de menu com os arquivos encontrados no subdiretório:



procedure TForm2.PreencheItem(const Diretorio : String;
AMenuItem : TMenuItem);
var
SRec : TSearchRec;
Done : Integer;
MenuItem : TMenuItem;
begin
// Pesquisa primeiro arquivo no diretório
Done := FindFirst(Diretorio+'*.*',faAnyFile,SRec);
while Done = 0 do begin
// descarta os diretórios atual e pai
if (SRec.Name = '.') or (SRec.Name = '..') then begin
Done := FindNext(SRec);
continue;
end;
// Cria o novo item de menu com o nome do arquivo
MenuItem := TMenuItem.Create(AMenuItem);
MenuItem.Caption := SRec.Name;
// Se for diretório preenche o item de menu com o subdiretório
if (SRec.Attr and faDIRECTORY) <> 0 then begin
MenuItem.ImageIndex := 1;
PreencheItem(IncludeTrailingPathDelimiter(Diretorio+
SRec.Name),MenuItem);
end
// Se for arquivo cria evento OnClick
else begin
MenuItem.ImageIndex := 0;
MenuItem.OnClick := ItemClick;
end;
// Adiciona item de menu
AMenuItem.Add(MenuItem);
// Pesquisa próximo arquivo
Done := FindNext(SRec);
end;
FindClose(SRec);
end;

Este código pesquisa os arquivos do diretório, adicionando um novo item de menu para cada arquivo. Quando o arquivo é um subdiretório, a rotina é chamada recursivamente para preencher este novo item de menu. Colocamos uma imagem no item de menu, para difereciar os arquivos dos diretórios.

Sábado, Novembro 20, 2004

Fechando um menu quando o mouse não está sobre ele

Hoje recebi esta pergunta de um leitor:

Ao colocar um ícone na bandeja do Windows, ao dar um clique com o botão da direita no ícone, aparece um menu de opções. Como fazer este menu desaparecer se o mouse sair de dentro do perímetro dele?

Esta não é tão simples de se fazer, pois não temos controle sobre a janela do menu. A maneira que imaginei é colocar um TTimer e verificar a janela abaixo do Mouse. Se for o menu, continuamos com ele ativo. Se não for, fechamos o menu. Para isso, definimos uma variável FEntrou na parte privada da Form, que irá controlar se o mouse já entrou no menu (ao ativar o menu, o mouse deve estar sobre a bandeja do Windows):

private

FEntrou : Boolean;

Colocamos um TTimer na Form, configurando a propriedade Interval para 100, de maneira a verificar a posição a cada 100 ms e, no evento OnTimer, colocamos o seguinte:


procedure TForm1.Timer1Timer(Sender: TObject);
var
PosMenu: TPoint;
Wnd: HWND;
ClassName: Array [0..80] of Char;
begin
// Pega posição do Mouse
GetCursorPos(PosMenu);
// Pega a janela sob o mouse
Wnd := WindowFromPoint(PosMenu);
if Wnd <> 0 then begin
// Achou janela - pega o nome da classe;
GetClassname(Wnd, Classname, 80 );
if FEntrou and (String(ClassName) <> '#32768') then begin
// Se já entrou no menu e a janela é diferente da do menu
// (ClassName é diferente de #32768)
// Fecha o menu
EndMenu;
Timer1.Enabled := False;
FEntrou := False;
end
else if (not FEntrou) and (String(ClassName) = '#32768') then
// Ainda não entrou, mas a janela é a do menu,
// configura FEntrou para True
FEntrou := True;
end;
end;

Devemos deixar a propriedade Active para False, para que o evento não seja chamado quando o programa iniciar. Precisamos apenas ativar o Timer no evento OnPopup do PopuMenu:


procedure TForm1.PopupMenu1Popup(Sender: TObject);
begin
Timer1.Enabled := True;
end;
Assim, nosso menu é fechado quando o mouse estiver fora dele.

Enviando mensagens html com anexos

Ao executar o programa da postagem Enviando mensagens com imagens em anexo, notamos um problema: a mensagem é enviada corretamente, com a imagem em anexo, porém o html não é gerado corretamente, aparecendo como texto normal.
Analisando o cabeçalho da mensagem, vemos que o content-type da mensagem está como text/plain, mesmo tendo especificado text/html.
O que ocorre é que a mensagem, quando tem um anexo, é transformada em multipart e isso faz que o conteúdo seja enviado como texto.
Uma solução para isto é enviar o texto da mensagem como uma parte de texto, com conteúdo html. Isto é feito criando-se uma variável do tipo TIdText, que será preenchida com o texto que queremos. Na realidade, iremos criar duas partes de texto na mensagem: uma normal, não html, que será mostrada em leitores de e-mail não html, e outra html. Isto é feito da seguinte maneira:



// configura para multipart
ContentType := 'Multipart/Alternative';
// parte de texto normal
idText := TIdText.Create(MessageParts,nil);
idText.ContentType := 'text/plain';
idText.Body.Add('Esta parte é texto puro e não mostra a imagem');
// parte de texto html
idText := TIdText.Create(MessageParts,nil);
idText.ContentType := 'text/html';
idText.Body.Text := '<html><body>Esta mensagem html tem '+
'imagens<br />'+
'A imagem está como anexo, e não está fora da mensagem:<br />'+
'<img src="cid:MinhaImagem"></body></html>';
// Configura anexos
Imagem := TIdAttachment.Create(MessageParts,
'c:\windows\cafezinho.bmp');
Imagem.ExtraHeaders.Values['Content-ID'] := '<minhaimagem>';
Imagem.ContentType := 'image/jpeg';

Desta maneira, temos a mensagem com anexos formatada corretamente, podendo ser vista tanto em leitores texto como html.

Quarta-feira, Novembro 17, 2004

Lançamento do Delphi 2005

Hoje fui ao lançamento do Delphi 2005 em São Paulo. O salão estava cheio e a espectativa era grande, como em todo lançamento de nova versão do Delphi. Esta, em especial, pois une três produtos em um só: o Delphi para Win32, o Delphi para .NET e o C#.
Após as costumeiras introduções de marketing, o Daniel Politschuck carregou o Delphi 2005 em sua máquina. A IDE do Delphi 2005 é semelhante à do Delphi 8, com todas as janelas docadas numa só. Para quem está acostumado com o ambiente usual do Delphi, nem tudo está perdido: pode-se configurar a IDE para ficar com a cara antiga do Delphi, pelo menos para aplicações Win32.
Uma vez que você se acostuma com a IDE, ela fica muito interessante: tudo está lá, nas janelas - para criar um novo projetos, usa-se a Tool Palette, que tem os tipos de projetos disponíveis, por exemplo.
À medida que se muda de visão, as janelas mudam - na janela de código, uma das janelas é o Code Snippets, que tem pedaços de código que podem ser arrastados para o editor. A Structure View mostra os erros no código, à medida que vão sendo digitados. Como no Word, os erros de código são sublinhados e, ao colocar o mouse sobre o erro, uma hint mostra o que está errado. Ao colocar o mouse sobre uma classe ou função, o Help Insight mostra uma ajuda sobre ela.
Em seguida, ele começou a esnobar com as novas "features" de refactoring do Delphi 2005:


  • SyncEdit - ele selecionou um bloco de código - todas as variáveis passíveis de serem mudadas no bloco estavam sublinhadas. Ao mudar o nome de uma das variáveis todos os locais no bloco onde a variável era referenciada, mudaram de nome

  • Find Unit - ao clicar o botão direito do mouse no botão no nome de uma classe e selecionar Find Unit, o Delphi procura em que unit esta classe está definida e adiciona-a à cláusula Uses

  • Declaração de variável - usa-se a variável no código de uma função e este refactoring adiciona a declaração da variável na cláusula var da função

  • Extract method - seleciona-se u pedaço de código e ele extrai o código, criando um novo método para a classe - este refactoring verifica quais variáveis devem ser passadas como parâmetros e declara as variáveis locais. No lugar do código extraído fica apenas a chamada do método

  • Rename field e Rename type - pode-se renomear um campo ou mesmo uma classe e todas as referências a ela (mesmo em outras units) são renomeadas


Em seguida, o Daniel mostrou o histórico de atualizações. O histórico permite guardar e comparar as últimas atualizações do código e das Forms. Pode-se reverter ao estado de qualquer uma das versões disponíveis, ou ver quais as mudanças que foram feitas entre as versões.
Outra novidade no Delphi são os testes unitários. O DUnit e o NUnit já vem instalados no Delphi, com Wizards para criação de projetos e classes de teste. Finalmente foi mostrada a integração com o Caliber (ainda em testes) e a integração com o Starteam (uma licença do Starteam standard está incluída com o Delphi). Isto facilita bastante o gerenciamento de requisitos e rastreabilidade do código - o fato de não precisar sair da IDE para fazer o gerenciamento facilita bastante a utilização das ferramentas.
Ufa... Coffee Break. Pausa para respirar um pouco.
Segunda parte da apresentação. O Renato Quedas fala sobre Banco de dados e Web. O início é para dizer que foi portado o dbGo (ADO) para .NET, além de novos componentes para bancos de dados.
Em seguida, ele apresenta o Data explorer. Na IDE pode-se ver os bancos de dados definidos, abrir as tabelas, copiar tabelas (mesmo entre bancos de dados diferentes. Uma coisa muito interessante é o teste de stored procedures. Ao abrir uma stored procedure, o Data Explorer coloca os parâmetros numa grid. Podemos preencher estes valores e testar a procedure. Da mesma maneira, podemos usar o SQL Qindow, onde podemos entrar com sentenças SQL.
Como se isso não bastasse, ele faz uma aplicação cliente e uma servidora, que conversam entre si usando remoting. Para isso, ele usou os componentes DataSync e DataHub.
Ao apresentar a facilidade de desenvolvimento Web com Asp.Net, ele fez uma aplicação cliente Asp.Net, que acessava o mesmo servidor que havia criado anteriormente. Um componente muito interessante que ele mostrou é o DBNavigatorExtender: ao colocar um destes componentes na Form, os botões normais passam a poder ter funções de movimentação do Dataset, sem precisar escrever código.
O final da apresentação foi sobre ECO II, que faz a ligação Objeto/Relacional para a aplicação - ele mapeia o banco de dados para objetos e dá toda a infraestrutura para trabalhar com eles. Por exemplo, temos uma tabela de clientes. O ECO lê esta tabela e cria um objeto Cliente. Para criar um cliente e preencher seu nome, basta fazer:




NovoCliente := Cliente.Create;
Cliente.Nome := 'Novo nome';

Finalmente, a apresentação acabou com os sorteios (não ganhei nada :-()
Ao sair da palestra, estava realmente impressionado: esta versão está ótima e cheia de novidades. A palavra chave é integração. As ferramentas estão integradas à IDE e pode-se trabalhar com maior produtividade, mesmo usando ferramentas externas como o Caliber ou o StarTeam. As novas ferramentas de Refactoring, o DataBase Explorer e o ECO são a cereja do bolo e dão um toque especial à nova IDE.
Se você ainda não viu o Delphi 2005, recomendo ver a agenda e inscrever-se na apresentação mais próxima de você.

Terça-feira, Novembro 16, 2004

Enviando mensagens com imagens em anexo

Para se enviar uma mensagem html com os componentes Indy basta usar a propriedade ContentType de TIdMessage, configurando-a para text/html.
Quando se quer mandar uma imagem nesta mensagem, uma maneira que pode ser usada é colocar um link externo para ela: <img src="http://www.meusite.com/imagem.jpg">.
Isto, além de gerar um tráfego desnecessário no site, tende a ser desabilitado nos mailers, por ser um risco de segurança. Outra maneira é embutir a imagem na mensagem como anexo e ligar o link a ela.
Porém, como fazer isso ? Qual é o link para a mensagem anexa ?
Este link é dado pelo Content-Id da imagem. Ao chamar o link para a imagem, faz-se algo como <img src="cid:minhaimagem">. minhaimagem é o content-id da imagem, que é definido quando colocamos a mensagem como anexo:



Imagem := TIdAttachment.Create(MessageParts,
'c:\windows\cafezinho.bmp');
// Aqui estou preenchendo o Content-ID
Imagem.ExtraHeaders.Values['Content-ID'] := '<minhaimagem>';
Imagem.ContentType := 'image/jpeg';

Desta maneira, a imagem vai na mensagem e é aberta sem problemas.

Sábado, Novembro 13, 2004

Alterando dados do campo antes de gravar

Em alguns casos, você quer alterar os valores de um campo antes de gravar os dados. Para isso, você pode usar o evento BeforePost do Dataset e alterar os dados necessários, desta maneira:



procedure TForm1.ClientDataSet1BeforePost(DataSet:
TDataSet);
var
Valor : String;
begin
Valor := Dataset.FieldByName('Razao').AsString;
if Valor[Length(Valor)] <> ' ' then
Dataset.FieldByName('Razao').AsString := Valor+' ';
end;