19 de dezembro de 2012

Personalizando a apresentação de mapas com o Google Maps

Na maioria das vezes, as informações apresentadas num mapa produzido através da API do Google Maps são suficientes para que o usuário rapidamente consiga ver um endereço e seu entorno ou a rota a ser seguida para ir de um ponto a outro. Em certas situações, no entanto, pode ser necessário adicionar ao mapa outras informações para melhor transmitir a ideia correta que se deseja.

Esse tipo de necessidade foi exemplificado quando falei sobre o uso de marcações num mapa para apontar locais de interesse nas proximidades de um determinado endereço. O exemplo pode ser encontrado neste link.

A API do Google Maps nos permite acrescentar informações extras aos mapas através do conceito de camadas que são sobrepostas ao mapa, chamadas de overlays. Para isso, a API disponibiliza um conjunto de classes javascript. Além da já citada Marker, há classes para traçar linhas, delimitar áreas circulares ou retangulares e até mesmo para projetar uma imagem personalizada sobre uma área do mapa.

O overlay mais simples é o Polyline, usado para traçar linhas por sobre o mapa. O quadro abaixo traz código javascript que une com uma linha reta 2 pontos do mapa: a sede da ABC71 e a praça da Sé, em São Paulo:
var mapOptions = {zoom: 10,mapTypeId: google.maps.MapTypeId.ROADMAP};
map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
geocoder = new google.maps.Geocoder();

var endDe = new google.maps.LatLng(-23.566146,-46.652579);
var endAte = new google.maps.LatLng(-23.550563,-46.633101);
var request = { location: endDe, region: 'BR'};

geocoder.geocode (request, function(response, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.fitBounds (new google.maps.LatLngBounds(endDe, endAte));
/* ... */
var pontos = new google.maps.MVCArray ();
pontos.push (endDe);
pontos.push (endAte);

var rect = new google.maps.Polyline ({
path: pontos,
map:map,
strokeWeight: 5,
strokeColor: 'blue',
visible: true
});
}
});
Após as inicializações de praxe para o ambiente do Maps, o código solicita a geocodificação do endereço da ABC71 (na verdade, sua latitude/longitude).
Em resposta à conclusão da geocodificação, isto é, assim que o mapa com o endereço solicitado está pronto, eu crio uma lista MVCArray e adiciono a ela os dois pontos que definem a reta desejada.

Em seguida, instancio a classe Polyline propriamente dita, fornecendo-lhe a lista de pontos. Também indico o mapa onde a linha será traçada (map), a espessura da linha em pixels (strokeWeight) e a cor dela (strokeColor). As cores podem ser informadas tanto pelo nome HTML quanto pela notação RGB hexadecimal.

Posso, por exemplo, demarcar um bairro inteiro com esse recurso? Sim, mas você terá que montar a lista de pontos o mais completa possível para que o desenho resultante tenha uma precisão adequada. O Polyline produz linhas abertas; para traçar linhas fechadas com uma quantidade arbitrária de pontos use a classe Polygon.

A função fitBounds, usada no início da resposta, apenas aplica um nível de zoom apropriado para garantir que ambos os endereços sejam exibidos num único quadro, sem que o usuário precise interagir com ele.

O código analisado produz o mapa a seguir:
Imagine agora o caso de uma pizzaria que queira divulgar num mapa o raio de ação onde ela faz entregas. O overlay produzido pela classe Circle permite dar destaque a uma área circular, dado um centro e um raio em metros. Veja o exemplo:
var endDe = new google.maps.LatLng(-23.566146,-46.652579);
var request = { location: endDe, region: 'BR'};

geocoder.geocode (request, function(response, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(endDe);
/* ... */
var circleOpts = ({
map: map,
center: endDe,
editable: false,
fillColor: 'yellow',
fillOpacity: 0.4,
radius: 3500,
strokeColor: '#0099ff',
strokeWeight: 4,
visible: true });
var circle = new google.maps.Circle (circleOpts);
}
});
Como no caso da linha, para o círculo também valem as propriedades que indicam o mapa, a cor do traço e sua espessura. Mas, ao invés de uma lista de pontos, informamos apenas o centro; no código anterior, usei o próprio endereço da ABC71 para isso. A definição do círculo fica completa com a propriedade radius - o raio, fornecido em metros; no exemplo, o raio terá 3500 metros (ou 3,5 Km).

Outra configuração disponível diz respeito ao preenchimento da área delimitada pelo círculo. A propriedade fillColor determina a cor do preenchimento enquanto a fillOpacity controla o grau de transparência, sendo que o valor 1 significa totalmente opaco (o mapa sob o círculo não é visto) e o valor 0 suspende o preenchimento.

A propriedade editable, quando ativa, permite ao usuário redimensionar o círculo. Eu a desliguei pois ela não faz sentido no contexto do exemplo. O resultado é o mapa exibido no quadro abaixo:

Os mesmos conceitos valem para a classe Rectangle. Além disso, é permitido trabalhar com vários overlays num mesmo mapa, se for necessário. Os exemplos publicados nesse post podem ser acessados respectivamente nos seguintes links : Overlay com Linha e Overlay com Círculo.

23 de novembro de 2012

Integrando o Outlook em aplicações Delphi - Parte II

No último post, eu mostrei os conceitos básicos envolvidos na integração do Outlook com aplicações Delphi. O exemplo apresentado lá abordou a manipulação dos contatos cadastrados no Outlook bem como a criação de novos contatos. Neste post eu abordo o aspecto mais utilizado da ferramenta, que é lidar com envio e recebimento de emails.

Enviar um email com o Outlook de dentro de um programa Delphi é uma operação relativamente simples. Assim como no caso dos contatos, o ponto de partida é objeto Outlook.Application. Aqui, no entanto, não é necessário trabalhar com Namespaces. Basta criar uma instância de MailItem (este é o tipo de objeto do Outlook que representa uma nova mensagem), fornecer-lhe informações relevantes para o envio e, então, efetivamente enviá-la. O código a seguir exemplifica esses passos:
procedure TForm1.btnEnviarClick(Sender: TObject);
var OutlookApp: TOutlookApplication;
email : MailItem;
begin
OutlookApp := TOutlookApplication.Create(Nil);

email := OutlookApp.CreateItem(olMailItem) As MailItem;
email.Subject := 'Envio automático';
email.BodyFormat := olFormatHTML;
email.HTMLBody := 'Este email é um <b>teste</b> para envio <b><span style="color:maroon;">automático</span></b>.';
email.Importance := olImportanceNormal;

email.Recipients.Add('balaiotecnologico@gmail.com');
if (email.Recipients.ResolveAll) then
email.Send
else begin
Application.MessageBox('Um ou mais destinatários não puderam ser resolvidos.'#13+
'Reveja a informação e tente novamente.'
, 'Alerta', MB_ICONWARNING);
email.Display(true);
end;

OutlookApp.Disconnect;
OutlookApp.Free;
end;
Veja que a criação da instância da mensagem é feita diretamente no objeto que representa a aplicação Outlook. A função CreateItem usada para esse fim é genérica e permite criar qualquer item dentro do Outlook. Assim, o valor olMailItem passado como parâmetro garante que o que está sendo criado é uma nova mensagem.

Após a criação, eu formato a mensagem fornecendo valores para propriedades relevantes como o assunto (Subject), a prioridade da mensagem (Importance), o texto dela (Body ou HTMLBody, dependendo do formato escolhido) e os destinários que a receberão (Recipients).

Opcionalmente, o método ResolveAll da lista de destinatários é chamado para verificar se os endereços incluídos são válidos. Em caso negativo, a própria tela de envio de email do Outlook é apresentada para que o usuário faça as correções necessárias antes de enviar o email manualmente.

Uma vez que a verificação foi feita com sucesso, a função Send é chamada para efetivamente fazer o envio. Por padrão, uma cópia da mensagem é armazenada na pasta Sent Items. Se não quiser guardar a cópia, use a propriedade DeleteAfterSubmit, ajustando-a para true. É possível também indicar outra pasta para guardar a cópia, modificando-se o valor da propriedade SaveSentMessageFolder.

Repare que não informei quem é o remetente da mensagem. Normalmente, o Outlook é configurado para se logar automaticamente quando a aplicação é executada, fazendo com que o remetente seja este usuário que fez o logon. É possível forçar outro logon por código, usando a função Logon de forma explícita e passando a ela as credenciais a serem utilizadas.

O código finaliza desconectando do Outlook e liberando a memória que esta sendo usada por ele.

Neste exemplo, passei o valor olFormatHTML na propriedade BodyFormat para permitir a inserção de código HTML diretamente em HTMLBody, o que flexibiliza a formatação da mensagem.

Embora eu não tenha tratado no código, é possível também incluir anexos no email. Um MailItem possui a propriedade Attachments para conter a lista de anexos. Antes do envio, podemos adicionar um ou mais anexos:
email.Attachments.Add('c:\ERP\abc71.jpg', EmptyParam, EmptyParam, EmptyParam);
O primeiro parâmetro é o caminho completo do arquivo a ser anexado. Os demais parâmetros são opcionais; passei EmptyParam a eles para que a função assuma valores internos padronizados para eles.

Para encerrar esse tópico, falo de um problema recorrente pra quem automatiza envio de email com o Outlook: a inclusão da assinatura padrão do usuário no corpo da mensagem. Da forma como foi exposto até aqui, apenas o texto incluído em HTMLBody será enviado, sem qualquer decoração extra que o usuário tenha configurado - como a assinatura ou um papel de carta. Isso acontece porque esses recursos não são inerentes ao MailItem mas sim ao editor associado a ele. Para acionar o editor, precisamos apenas invocar o método GetInspector. Um inspector é o responsável pela edição do item atual e, ao invocar o método, todo o ambiente para edição é criado, incluindo a formatação padrão para o item - neste caso, um email.

Isso preencherá o HTMLBody com um HTML completo, englobando a assinatura e outras formatações. Então, teremos que localizar a tag BODY desse HTML para acrescentar nosso texto personalizado no local correto sem perder a formatação padrão. O quadro abaixo mostra como obter esse efeito:
var { ... }
email : MailItem;
insp : Inspector;
idx: integer;
begin
{ ... }
insp := email.GetInspector;

idx := Pos ('<body', email.HTMLBody);
idx := PosEx ('>', email.HTMLBody, idx);
email.HTMLBody := MidStr (email.HTMLBody, 1, idx) +
'Este email é um <b>teste</b> para envio <b><span style="color:maroon;">automático</span></b>.' +
MidStr (email.HTMLBody, idx+1, Length(email.HTMLBody));

if (email.Recipients.ResolveAll) then
email.Send;
{...}
Nessa abordagem, apesar de o editor ser criado e sua configuração padrão ser respeitada, ele não é exibido para usuário. Se for necessário, chame o Display do MailItem para exibí-lo.

Comentei no post anterior mas não custa lembrar: todas as operações descritas neste post exigem que o Outlook esteja instalado no computador que for executá-las.

6 de novembro de 2012

Integrando o Outlook em aplicações Delphi - Parte I

Embora eu não tenha números exatos, o Outlook é o programa de email mais utilizado em desktops. Parte desse sucesso se deve à ampla adoção da ferramenta no ambiente corporativo, já que ela também centraliza funções de calendário, acompanhamento de tarefas e agenda de contatos, permitindo compartilhar facilmente as informações.

Um outro aspecto positivo é que o Outlook é componentizado, o que abre caminho para criação de extensões e integrações com outros sistemas. Neste post, mostro como utilizar os recursos COM do Outlook para integrá-lo a uma aplicação Delphi.

Do mesmo modo que as outras aplicações do Microsoft Office (como o Excel), todos os recursos disponíveis do Outlook devem se acessados a partir de um objeto centralizador que representa a própria aplicação. Neste caso, precisamos obter uma instância de Outlook.Application, mapeada pelo Delphi na unit OutlookXP, junto com as demais interfaces existentes para se trabalhar com a ferramenta.

O quadro abaixo mostra um exemplo dos passos básicos necessários para interagir com o Outlook. O código apresentado percorre os contatos registrados na pasta local do Outlook, listando informações de cada um deles numa tabela:
procedure TForm1.btnListarContatosClick(Sender: TObject);
var i : integer;
OutlookApp: TOutlookApplication;
ns : NameSpace;
pasta: MAPIFolder;
contato : _ContactItem;
begin
StringGrid1.RowCount := 2;
OutlookApp := TOutlookApplication.Create(Nil);

ns := OutlookApp.GetNamespace('MAPI');
pasta := ns.GetDefaultFolder(olFolderContacts);

for i := 1 to pasta.Items.Count do
begin
if (i > 1) then
StringGrid1.RowCount := StringGrid1.RowCount + 1;

contato := pasta.Items.Item(i) As _ContactItem;

StringGrid1.Cells [1,i] := contato.FullName;
StringGrid1.Cells [2,i] := contato.Email1Address;
StringGrid1.Cells [3,i] := contato.EntryID;
StringGrid1.Cells [4,i] := contato.CompanyName;
StringGrid1.Cells [5,i] := contato.BusinessAddress;
end;

OutlookApp.Disconnect;
OutlookApp.Free;
end;
Vamos destrinchar esse código. Após criar uma instância para acessar a aplicação (classe TOutlookApplication), o trecho recupera um tipo de objeto denominado Namespace. Atualmente, apenas o namespace MAPI (API de Mensagens do Windows) está disponível, concentrando todos os registros, isto é, os emails, contatos, tarefas e entradas do calendário. Um Namespace é organizado em pastas dispostas hierarquicamente, podendo haver pastas dentro de pastas para armazenar os itens.

Há algumas formas de acessar as pastas do Outlook. A propriedade Folders do Namespace lista todas as pastas, incluindo as compartilhadas com outros usuários no servidor. As subpastas eventualmente existentes são acessíveis por meio de propriedade Folders própria. E como saber que tipo de item uma pasta contém? Em princípio, pode-se consultar a propriedade DefaultItemType para se ter uma ideia. Mas, certas pastas (como a DeletedItems) podem conter itens de tipos diferentes misturados. Neste caso, recomenda-se checar a propriedade Class do item ou fazer um cast dinâmico para o tipo desejado e ver se a conversão foi bem sucedida.

Outra forma de acesso é requerer ao Namespace a pasta padrão para um determinado tipo de item, como no código mostrado acima. Lá, uso GetDefaultFolder para recuperar exclusivamente a pasta que armazena meus contatos. Por fim, se eu possuir a identificação única da pasta (seu entryID, posso recuperá-la diretamente com a função GetItemFromID do Namespace.

Voltando ao código, agora tenho uma instância da pasta de contatos para trabalhar. Como toda pasta, esta possui uma lista Items que dá acesso ao conteúdo específico - no exemplo, os contatos cadastrados. Então, percorro a lista para prencher a tabela com as informações desejadas de cada um dos contatos.

Para concluir, desconecto minha instância do Outlook e removo-a da memória para liberar os recursos utilizados.

Note que não foi preciso informar usuário e senha para fazer logon. Isso acontece porque o Outlook pode ser configurado para se conectar automaticamente com o mesmo usuário do Windows. Se for preciso, pode-se logar manualmente usando a função Logon do Namespace.

Aproveitando os mesmos conceitos, podemos criar automaticamente um novo contato, conforme mostra o trecho de código a seguir:
contato := pasta.Items.Add(olContactItem) As _ContactItem;
contato.FirstName := 'Balaio';
contato.LastName := 'Tecnológico';
contato.CompanyName := 'ABC71 Soluções Em Informática';
contato.Save;
contato.Display(1);
Como se vê, a criação é bastante simples. Primeiro, chamo o método Add da lista de itens da pasta, fornecendo-lhe o tipo de item que deve ser criado - um item de contato, neste caso. Após prover algumas informações básicas do contato, salvo o registro. A última linha não é imprescindível; ela apenas exibe no próprio Outlook o contato recém-criado para que o usuário possa complementar as informações, se desejar.

De forma similar, conseguimos trabalhar com tarefas e compromissos no calendário. Também é possível fazer o envio de email aproveitando a infraestrutra já configurada do Outlook, operação que mostro no próximo post.

Obviamente, todos os recursos apresentados aqui exigem que haja uma cópia do Outlook instalado no computador local para funcionar.

2 de outubro de 2012

Trabalhando com marcações em mapas com a versão 3 da API do Google Maps

Além dos tradicionais recursos de exibir o mapa para um determinado endereço ou traçar rotas entre endereços fornecidos, a API do Google Maps disponibliza um outro recurso bastante interessante que permite incluir num mapa marcações customizadas. Com isso, você pode marcar pontos de interesse de acordo com as necessidades de sua aplicação. Um exemplo de aplicabilidade desse conceito seria indicar num mapa a localização de estabelecimentos comerciais nas proximidades de um endereço, tais como Caixas 24 Horas, Supermercados, escolas de inglês ou outro qualquer. Obviamente, isso demandará saber de antemão a posição de cada um dos locais passíveis de serem marcados no mapa.

O processo de marcação em si é bastante simples. Após termos ajustado a parametrização inicial do ambiente do Maps e requisitado a exibição de um endereço (veja post Usando a versão 3 da API do Google Maps), basta criar uma instância da classe google.maps.Marker, como no quadro abaixo:
function mostraEnderecoABC71 () {
var lRequest = { address: 'Alameda Santos, 1000, São Paulo - SP, 01418-902', region: 'BR'};
geocoder.geocode( lRequest, trataLocais);
}

function trataLocais (results, status){
if (status == google.maps.GeocoderStatus.OK)
{
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker(
{map: map,
position: results[0].geometry.location,
animation: google.maps.Animation.DROP,
icon: 'http://maps.google.com/mapfiles/kml/pal3/icon18.png',
shadow: 'http://maps.google.com/mapfiles/kml/pal3/icon18s.png'
});
/* ... */
}
}

A função trataLocais reproduzida acima é o callback passado para o Geocoder junto com a requisição do endereço a ser apresentado no mapa. No exemplo, a marcação é inserida no próprio endereço requisitado, razão pela qual foi usado o retorno da API results[0].geometry.location como local para fixar a marcação.

Dos parâmetros para criar o marcador, apenas 2 são obrigatórios : o map (representando a instância de Map que gerencia a apresentação do mapa na página) e o position (um par latitude/longitude onde o marcador deverá ser fixado). Os demais parâmetros são auxiliares, úteis para melhorar a apresentação. No exemplo, forneço outros 3 parâmetros : animation (uma animação dinâmica usada para cravar o marcador no mapa; o padrão é o marcador simplesmente aparecer), icon (uma URL apontando a imagem que será usada como marcador; o padrão é ) e shadow (URL com a imagem que representa uma sombra do marcador). Há uma lista com imagens padronizadas e suas respectivas sombras neste link mas pode-se trabalhar com qualquer URL personalisada que se queira.

O mapa abaixo usa esses conceitos pra incluir algumas marcações nas proximidades da sede da ABC71 em São Paulo:

A API prevê ainda a possibilidade de se abrir uma janela para apresentar outros detalhes a respeito de um ponto no mapa. A flexibilidade desse tipo de recurso é grande já que é permitido incluir código HTML para formatar as informações que são apresentadas - embora isso não seja obrigatório.

Do ponto de vista da programação, criar uma janela com informações adicionais sobre um ponto também é simples. Consiste em instanciar a classe google.maps.InfoWindow, providenciando um conteúdo para essa janela. Uma vez instanciada, podemos solicitar que a janela seja aberta num ponto específico do mapa. Complementando a função trataLocais :
function trataLocais (results, status){
if (status == google.maps.GeocoderStatus.OK)
{
/* ... */
google.maps.event.addListener(marker, 'click',
function () {
var infowindow = new google.maps.InfoWindow(
{
content: '<div style="text-align:justify;"><b>Ponto de Interesse</b><br/><br/>ABC71 Soluções em Informática.</div>'
});
infowindow.open(map, marker);
});
}
}
No código javascript do quadro anterior, a abertura da janela de informação é vinculada a um evento de "clique" no marcador. Isto é, ela só aparece se o usuário clicar no marcador em questão. Veja que o conteúdo (content) da janela foi formatado com tags HTML, possibilitando adequar a apresentação ao propósito de sua aplicação. A função open - que exibe a janela - aceita como parâmetros o mapa que estamos usando e o marcador que servirá de referência para a exibição da janela.

No mapa que aparece nesse post, os marcadores podem ser clicados, ação que cria uma janela simples com outras informações sobre o ponto marcado.

O arquivo HTML encontrado neste endereço traz o exemplo completo mostrado aqui no post. Ele simula a recepção em formato XML de informações sobre locais que devem ser marcados no mapa. Para isso, o exemplo utiliza também recursos da biblioteca JQuery.