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

29 de novembro de 2013

Convertendo objetos Delphi para o formato JSON

Damos o nome de serialização ao processo de pegar um objeto em memória numa linguagem de programação e convertê-lo para um formato padrão, compreensível para outras linguagens ou ambientes operacionais. A lista de tais formatos inclui sequência de bytes (stream), XML e, mais recentemente, JSON - sendo este último facilmente intercambiável através da internet. Falei sobre esse formato no post Lendo dados JSON em aplicações Delphi, onde mostro como transformar dados JSON em um objeto Delphi.

As classes descritas naquele post podem ser usadas manualmente para realizar o processo inverso, isso é, pegar um objeto no Delphi e representá-lo como um dado JSON. No entanto, o Delphi introduziu classes mais apropriadas para esse serviço, permitindo serializar objetos com facilidade.

Tomemos como exemplo um objeto da classe TWPedido, declarada no quadro abaixo. A classe proposta é complexa, composta tanto de propriedades de tipos atômicos quanto de tipos definidos pelo usuário, como a propriedade Produtos, que é um array dinâmico com instâncias de outra classe (TWProdutoPedido):
TWProdutoPedido = class
public
Codigo : String;
Descr : String;
Qtde: Double;
VrUnitario : Double;
end;

TWPedido = class
public
Numero: Longint;
Data: TDateTime;
VlrTotal: Double;
Produtos: array of TWProdutoPedido;

Constructor Create;
Destructor Destroy;override;

procedure AddProduto(AProd: TWProdutoPedido);
function GetQtdeProdutos : Integer;
end;

{ ... }

Constructor TWPedido.Create;
begin
SetLength (Produtos, 0);
end;

Destructor TWPedido.Destroy;
var lProd: TWProdutoPedido;
begin
for lProd in Produtos do
lProd.Free;

SetLength (Produtos, 0);
inherited;
end;

procedure TWPedido.AddProduto(AProd: TWProdutoPedido);
begin
{ Abre espaço para mais um produto }
SetLength (Produtos, Length (Produtos) + 1);
Produtos [Length (Produtos) - 1] := AProd;

VlrTotal := VlrTotal + (AProd.Qtde * AProd.VrUnitario);
end;

function TWPedido.GetQtdeProdutos : Integer;
begin
Result := Length (Produtos);
end;

function NovoPedido (ANro: Longint) : TWPedido;
var lProd: TWProdutoPedido;
begin
Result := TWPedido.Create;

Result.Numero := ANro;
Result.Data := Now;

lProd := TWProdutoPedido.Create;
lProd.Codigo := 'P001';
lProd.Descr := 'Computador';
lProd.Qtde := 1.000;
lProd.VrUnitario := 1500.00;
Result.AddProduto (lProd);

{ ... }

lProd := TWProdutoPedido.Create;
lProd.Codigo := 'P003';
lProd.Descr := 'Projetor';
lProd.Qtde := 1.000;
lProd.VrUnitario := 745.00;
Result.AddProduto (lProd);
end;

Serializar esse objeto em Delphi passa a ser questão de criarmos uma instância da classe TJSONMarshal. Tal classe se baseará nas informações adicionadas via RTTI (Run Time Type Information) para exportar automaticamente todos os campos públicos do objeto, mesmo estruturas e classes complexas criadas pelo próprio programador. Isso é feito num processo recursivo, de modo que toda a estrutura do objeto é considerada. O código a seguir mostra como usar a TJSONMarshal para fazer a serialização básica de uma instância da classe TWPedido:
var lMarshal : TJSONMarshal;
lPedido: TWPedido;
strJSON : string;
begin
{ Cria um novo pedido }
lPedido := NovoPedido (1234);

{ Realiza a serialização do pedido }
lMarshal := TJSONMarshal.Create (TJSONConverter.Create);

strJSON := lMarshal.Marshal(lPedido).ToString();

lMarshal.Free;
end;

O resultado será um texto no formato JSON semelhante ao publicado a seguir:
{"type":"WJsonObj.TWPedido","id":1,"fields":{"Numero":1234,"Data":41606.7632623727,"VlrTotal":2543,
"Produtos":[{"type":"WJsonObj.TWProdutoPedido","id":2,"fields":{"Codigo":"P001","Descr":"Computador","Qtde":1,"VrUnitario":1500}},

...

{"type":"WJsonObj.TWProdutoPedido","id":4,"fields":{"Codigo":"P003","Descr":"Projetor","Qtde":1,"VrUnitario":745}}]}}

Note que o mecanismo do Delphi inclui no resultado o nome do tipo de dado que originou o texto JSON, uma propriedade fields para armazenar todos os campos do objeto e uma identificação (id) para cada objeto. Esses valores são úteis no processo de transformação do texto JSON de volta para um objeto Delphi, o que pode ser conseguido através da classe TJSONUnMarshal.

O mecanismo possui ainda alguns truques para flexibilizar a serialização. Por exemplo, para impedir determinados campos sejam incluídos no resultado, podemos marcá-los com o atributo JSONMarshalled. Veja uma redeclaração da classe TWPedido solicitando a omissão do campo de valor total:
TWPedido = class
public
Numero: Longint;
Data: TDateTime;

{Marca VlrTotal para não ser incluído na serialização }
[JSONMarshalled(false)]
VlrTotal: Double;

Produtos: array of TWProdutoPedido;

{ ... }
end;

Um recurso mais elaborado consiste em registrar uma função anônima para realizar conversões personalizadas. É possível converter uma classe inteira para um outro tipo de dado mas também é permitido pinçar um único campo e tratá-lo conforme a necessidade. Por exemplo, o campo Data na classe de pedido é, por padrão, serializada como um double. Podemos interceptar sua conversão e forçá-lo a ser serializado como uma data textual:
lMarshal := TJSONMarshal.Create (TJSONConverter.Create);

{ Registra um conversor exclusivo para o campo Data do pedido }
lMarshal.RegisterConverter(TWPedido, 'Data',
function (Data: TObject; Field: string): string
var lProd : TWPedido;
begin
lProd := Data As TWPedido;
Result := DateToStr (lProd.Data);
end
);

strJSON := lMarshal.Marshal(lPedido).ToString();

lMarshal.Free;

Como podemos ver, a função RegisterConverter da classe de serialização deve ser chamada antes de fazermos a serialização em si. Após isso, o conversor que registramos é válido para quaisquer serializações que realizarmos com a instância do TJSONMarshal onde o registro foi feito. Embora o exemplo tenha registrado um único conversor, é permitido registrar tantos conversores quantos forem necessários para o trabalho. Há outras sobrecargas de RegisterConverter para atender diferentes demandas de particularização do processo; a documentação delas pode ser acessada neste link.

Para finalizar, uma última forma de interceptar a serialização é usando o TJSONConverter, cuja instância é passada ao construtor do TJSONMarshal. O TJSONConverter implementa uma série de eventos relativos aos diferentes tipos de dados tratados pela serialização e publica-os como métodos virtuais. Isto possibilita a criação de uma herança do conversor para modificar um ou mais desses eventos e personalizar o tratamento da serialização do respectivo tipo de dado.

19 de julho de 2013

Lendo dados JSON em aplicações Delphi

O JavaScript Object Notation - ou simplesmente JSON - é um formato padrão para troca de informações muito usado na construção de sites devido à sua estrutura baseada no JavaScript. Essa característica dá aos scripts em uma página HTML a flexibilidade para recuperar informações complexas e aplicá-las ao HTML conforme a necessidade, incrementando a usabilidade da página.

Assim como o XML, JSON é um formato texto aberto, isto é, que pode ser extendido para acrescentar novos valores quando for preciso; ambos também são fáceis de serem lidos por humanos. As informações nesse formato são construídas com pares nome/valor, sendo suportados 3 tipos básicos de dados: texto (string), números e booleanos. Os pares de valores, no entanto, podem ser rearranjados para construir objetos complexos e listas de objetos (arrays). O quadro a seguir mostra o exemplo de um objeto JSON complexo:
/* Objeto JSON complexo, incluindo um array com 3 outros objetos */
{
"empresa":"ABC71",
"ativa": true,
"fundacao": 1971,
"empregados": [
{"nome": "Jose", "sobrenome": "Silva" },
{"nome": "Beto", "sobrenome": "Marinho" },
{"nome": "Jim" , "sobrenome": "Jones" }
]
};

Há diversos serviços disponíveis na internet - alguns gratuitos - com os quais podemos recuperar diferentes tipos de informação (lista de países, previsão do tempo, cotações de moedas, etc). Além disso, é possível construirmos nossos próprios serviços para prover qualquer tipo de dado que necessitemos. Neste post, eu mostro como aplicações Delphi podem se beneficiar de serviços como esses, usando como exemplo um dos serviços publicados no site GitHub.

O retorno do serviço proposto é uma série de objetos descrevendo cada pais do mundo, incluindo seu nome, um id numérico e a sigla de 3 letras usada para o país. O quadro abaixo traz como exemplo o objeto para o Brasil:
{
"title":"Brazil",
"population":174468575,
"currency_code": "BRL",
"currency": "Brazilian Real ",
"nationality_plural": "Brazilians",
"nationality_singular": "Brazilian",
"map_reference": "South America ",
"_id": 33,
"country": "Brazil",
"fips104": "BR",
"iso2": "BR",
"iso3": "BRA",
"ison": 76,
"internet": "BR",
"capital": "Brasilia "
}

Como se trata de um serviço oferecido na internet, o primeiro passo é acessar o endereço do serviço para obter o conteúdo disponível. Isso pode ser conseguido com o componente TIdHTTP do Indy, conforme descrito no post Trabalhando com HTTP em Delphi. Para nosso cenário, isso se resume ao código do quadro abaixo:
var JsonStream: TStringStream;
begin
JsonStream:= TStringStream.Create('');

try
{ JsonStream conterá os dados JSON requisitados :}
idHttp1.Get('https://raw.github.com/nosql/data-refine/master/data/json/countries.json', JsonStream);
TrataJsonStream(JsonStream);
finally
JsonStream.Free();
end;

Após a chamada da função Get, a variável JsonStream contém a estrutura JSON completa com os dados dos paises. Essas informações estão em formato texto mas o Delphi possui um mecanismo de parse para carregá-los num objeto próprio, o TJSONObject:
var jso : TJSONObject;
jsop: TJSONPair;
begin
jso := TJsonObject.Create;
jso.Parse (JsonStream.Bytes, 0);

for jsop in jso do begin
if jsop.JsonString.Value = '_id' then
pais.id := ((jsop.JsonValue) As TJSONNumber).AsInt
else
if jsop.JsonString.Value = 'iso3' then
pais.ISO3 := jsop.JsonValue.Value
else
if jsop.JsonString.Value = 'country' then
pais.Pais := jsop.JsonValue.Value;
end;
jso.Free;
end;

Um TJSONObject armazena a lista dos pares nome/valor que constituem um objeto JSON. Como vemos no quadro anterior, a lista é um iterator para valores do tipo TJSONPair, com o qual podemos extrair tanto o nome (JasonString) quanto o valor (JasonValue) de cada propriedade do objeto JSON.

No entanto, há um problema com o código acima. Mencionei antes que o serviço retorna uma série de objetos mas o código mostrado trata apenas o primeiro objeto recuperado no stream. Para tratar os demais, temos que conhecer melhor a função Parse do objeto JSON.

O primeiro parâmetro da função Parse é o stream obtido junto ao serviço na web. O segundo parâmetro indica a partir de qual caractere do stream o Parse deve começar seu trabalho. A função retorna a posição do stream onde a leitura do objeto corrente foi concluída. Isso tudo é necessário porque não sabemos de antemão o tamanho de cada objeto. Mas, com essas informações que temos, podemos montar um laço capaz de ler todos os objetos incluídos no stream, como faz o código a seguir:
var i : Int64;
jso : TJSONObject;
jsop: TJSONPair;
begin
i := 0;
repeat
jso := TJsonObject.Create;
i := abs (jso.Parse (JsonStream.Bytes, i));

for jsop in jso do begin
{ Trata aqui cada JSON Pair }
end;
jso.Free;
until (i >= JsonStream.Size);
end;

O objeto JSON usado no exemplo é relativamente simples, não possuindo outros objetos aninhados ou mesmo um array de valores. Para esses casos, o laço que trata os pares de valores deve testar o tipo do valor e tratá-lo apropriadamente, reconstituindo o objeto complexo. Isso pode ser conseguido usando o ClassName do valor ou RTTI, como abaixo:
if jsop.JsonValue Is TJSONObject then
TrataObjeto (jsop.JsonValue As TJSONObject)
else
if> jsop.JsonValue Is TJSONArray then
TrataArray (jsop.JsonValue As TJSONArray)
else begin
{ Trata valores simples }
end;

A forma como transportei o conteúdo de um objeto JSON para uma estrutura equivalente do Delphi aqui neste post é o método mais simples. O Delphi disponibiliza mecanismos de serialização de informações usando JSON que são bem mais elaborados e flexíveis. Mostro tais mecanismos em outro ocasião.

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.

3 de maio de 2012

Trabalhando com HTTP em Delphi

O protocolo HTTP - HyperText Transfer Protocol ou Protocolo de Transferência de Hipertexto) - é certamente o mais utilizado na internet. É com ele que as páginas da internete são transferidas para seu computador antes de serem exibidas no navegador. O termo "hipertexto" diz respeito ao fato de que os documentos obtidos através do protocolo podem conter links remetendo a outros documentos.

Numa primeira olhada, a utilidade do HTTP não parece muito diferente do FTP usado para fazer downloads. O código abaixo, por exemplo, obtém o documento padrão do site do Google Brasil, isto é, a página HTML que é exibida quando se digita o endereço do Google Brasil num navegador. No exemplo, estou usando o componente TIdHTTP da paleta do projeto Indy:
var lResponse : TStringStream;
begin
lResponse := TStringStream.Create('');

try
{ lResponse conterá a página HTML requisitada :}
idHttp1.Get('http://www.google.com.br', lResponse);
finally
lResponse.Free();
end;
Apesar de não ser necessário configurar explicitamente um servidor/porta e usuário/senha como no FTP, o resultado final é o mesmo: o download de um arquivo remoto. Na verdade, a infraestrutura da internete mapeia automaticamente o endereço do Google para um servidor/porta; nesse caso, o usuário e senha é dispensável pois se trata de um site público que permite acesso anônimo.

Se olharmos mais a fundo, no entanto, veremos que o HTTP é mais flexível porque permite adicionar parâmetros à requisição enviada ao servidor. Na prática, isso dá ao servidor a chance de montar dinamicamente a resposta mais adequada à solicitação feita. As APIs de serviços publicados na internete normalmente são baseados no HTTP e se valem dos parâmetros pra flexibilizar as requisições. É o caso, por exemplo, do Google Docs e de outros serviços do Google, que usam a API de dados da empresa. Em outro post, eu mostro como se comunicar com o Google Docs usando a API de dados para ler os documentos salvos nesse serviço.

Se você já trabalhou com HTML, deve ter notado que a sintaxe dos Forms inclui uma "action", um método de envio e os diversos campos que serão preenchidos pelo usuário da página, sendo admitido até mesmo a existência de campos invisíveis contendo informações pré alimentadas.

Para relembrar o significado dessas propriedades do Form HTML : A "action" é o endereço na internete para onde a requisição será submetida. O "método" diz repespeito a como e quais informações serão enviadas; os valores mais comuns são GET (as informações são formatadas e acrescentadas ao endereço da "action", que então é enviado ao servidor) ou POST (as informações são transportadas à parte e não é possível enxergá-las na barra de endereço do navegador).

O form de pesquisa do Google, por exemplo, admite que você selecione a língua na qual deseja obter as repostas e, claro, os termos a serem pesquisados. O exemplo abaixo submete ao Google uma pesquisa em português com o nome do blog:
var lURL : String;
lResponse : TStringStream;
begin
lResponse := TStringStream.Create('');

try
lURL := 'http://www.google.com.br/search?' +
'hl=pt-BR&' +
'q=balaio%20tecnologico';
idHttp1.Get(lURL, lResponse);
lResponse.Position := 0;
{ Exemplo de uso do response : carregar o conteúdo num RichEdit : }
reResp.Lines.LoadFromStream(lResponse);
finally
lResponse.Free();
end;
Veja que cada parâmetro é composto de um nome (o mesmo configurado nas tags input do form) seguido por um sinal de igual (=) e o valor que o parâmetro deve assumir (normalmente, os próprios valores fornecidos pelo usuário). Cada parâmetro é concatenado ao anterior através de um "E Comercial" (&).

Ambos os parâmetros considerados pelo exemplo estão incluídos no form da pesquisa do Google. Ou seja, se você souber quais são os parâmetros de um form, poderá simular via Delphi a requisição que ele submeteria numa página da Web e, então, obter exatamente a mesma reposta, podendo tratá-la do modo que for mais apropriado para sua aplicação.

Por causa dessa facilidade de simular requisições via programação, muitos sites se protegem implementando um CAPTCHA. Trata-se de uma imagem com um texto embutido que é gerada dinamicamente pelo servidor de modo que apenas operadores humanos consigam ler. O texto deve, então, ser digitado num campo e enviado junto com as demais informações. A requisição só é atendida se o texto estiver correto.

Como podemos notar pelo exemplo, usar o GET para recuperar um documento pode ser problemático se certas informações sensíveis - como uma senha - tiverem que ser transmitidas direto na URL. Nestes casos, é recomendado usar o POST pois com ele as informações trafegam por outros meios.

O uso do POST também é simples. Basta passar os parâmetros num TStringList separado. A URL do "action" é informada sem quaisquer decorações extras:
var lParams :TStringList;
lResponse : TStringStream;
begin
lParams := TStringList.Create;
lResponse := TStringStream.Create('');

try
lParams.Add('filt=all');
lParams.Add('p=balaio%20tecnologico');
idHttp1.Post('http://br.search.yahoo.com/search', lParams, lResponse);

{ Exemplo de uso do response : carregar o conteúdo num RichEdit : }
lResponse.Position := 0;
reResp.Lines.LoadFromStream(lResponse);
finally
lParams.Free();
lResponse.Free();
end;
Neste exemplo, usei o Yahoo! porque o Google não implementa POST para pesquisas.

Ajustes ainda mais finos para submeter a requisição podem ser configurados na propriedade Request do TIdHTTP. Com ele, é possível ajustar o tipo de documento que se espera receber de volta (HTML, XML, etc.), o tipo de encoding, se há necessidade de passar por um proxy, etc.

14 de fevereiro de 2012

Aplicando recursos de localização em Asp.Net

por Tiago Oliveira*

Embora seja referenciado por uma palavra um tanto quanto estigmatizada - globalização - o fato é que o mundo ficou mesmo menor e os fabricantes de aplicações também passaram a levar isso em consideração. Ao planejar a construção de programas e sites, eles têm agora que incluir o conceito de localização na arquitetura, de uma forma que a aplicação se adeque à linguagem preferida pelo usuário sem que seja necessária uma reengenharia nas linhas de código.

O ASP.NET possui recursos de localização para que possamos fazer aplicações globalizadas, ou seja, para que a aplicação se adeque à região onde ela está sendo executada. A configuração utilizada pelo ASP.NET é a informada no navegador (no caso do IE, está no seguinte caminho : Opções de Internet/Geral/Idiomas), sendo considerado tanto o Idioma quanto a Região estipulados. Segue abaixo um exemplo de configuração:
en-US – Inglês região dos EUA.
pt-BR – Português região do Brasil.
As duas primeiras letras no código representam o Idioma e as duas últimas a região.

Agora vamos aplicar os recursos de localização disponíveis no ASP.NET para modificar esse comportamento padrão e fornecer um mecanismo para que o usuário opte pelo idioma de sua preferência na própria aplicação. Adicione um WebForm contendo 2 botões que, no caso, irão representar os possíveis idiomas suportados para visualização da aplicação : português e inglês.

Adicione um outro WebForm contendo alguns componentes Label, TextBox, etc. Para aplicar a localização às páginas da aplicação, o ASP.NET utiliza arquivos de Resources; por isso, vamos adicionar um arquivo de resource em nossa aplicação: clique no WebForm e selecione na guia Tools do Visual Studio a opção “Generate Local Resource”. Ao escolher essa opção. um arquivo de extensão .RESX com o mesmo nome da página será gerado dentro de uma pasta chamada App_LocalResources que será automaticamente adicionada ao seu projeto.

Dentro desse arquivo .RESX existe a relação de todos os componentes que comportam o recurso de localização, juntamente com a descrição dos campos. No nosso exemplo, esse arquivo irá representar a versão em português da aplicação; para gerarmos o arquivo contendo a versão em inglês, faça uma cópia do arquivo NomeDaSuaPagina.aspx.resx gerado pelo Visual Studio e altere o nome para NomeDaSuaPagina.aspx.en-US.resx. Nesse novo arquivo, faça a tradução dos campos para o Inglês.

No WebForm onde colocamos os 2 botões para identificar os idiomas, iremos passar a localização através de Query String, conforme código abaixo associado ao clique de cada um dos botões:
protected void ImageButton1_Click(object sender, ImageClickEventArgs e)
{
Response.Redirect("Conteudo.aspx?Language=pt-BR");
}

protected void ImageButton2_Click(object sender, ImageClickEventArgs e)
{
Response.Redirect("Conteudo.aspx?Language=en-US");
}
Na página Conteudo.aspx (representando a página principal da aplicação), precisamos inicializar a cultura de exibição para considerar a que passamos por parâmetro e não aquela que está configurada no navegador. Para isso precisamos sobrescrever o método InitializeCulture da página e ajustar a variável interna UICulture, que é a responsável por controlar o idioma em que a aplicação está sendo exibida:
protected override void InitializeCulture()
{
UICulture = Request.QueryString["Language"];
base.InitializeCulture();
}
Ao exibir os textos da página, o ASP.Net passará a considerar automaticamente o conteúdo do arquivo .RESX referente ao idioma e região escolhidos.

* Tiago Oliveira é desenvolvedor na ABC71, onde trabalha com os módulos fiscais, de faturamento e de vendas.


31 de janeiro de 2012

Informações sobre rotas com a versão 3 da API do Google Maps

No ano passado eu publiquei dois posts mostrando como traçar rotas usando a versão 3 da API do Google Maps. O primeiro tratava de um cenário básico onde consideramos um endereço de origem e outro de destino; o segundo avançava um passo ao permitir a inclusão de um ou mais pontos intermediários ao trajeto. Por razões distintas, várias pessoas me perguntaram como é possível obter a distância total entre a origem e o destino em uma rota, independendo se vamos efetivamente exibir a rota traçada.

Pra refrescar a memória, o quadro a seguir ilustra os comandos e estruturas básicos necessários pra trabalharmos com uma rota.
var directionsService, directionsRenderer;
var enderDe, enderAte;

directionsService = new google.maps.DirectionsService();
directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);

enderDe = 'ALAMEDA SANTOS, 1000, SÃO PAULO - SP, 01418-9028';
enderAte =
'AVENIDA NAÇÕES UNIDAS, 17-17, BAURU - SP, 17013-035';

var request = {
origin:endDe,
destination:endPara,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};

directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsRenderer.setDirections(response);
}
});)

A linha directionsRenderer.setDirections (response) usa o objeto response para traçar automaticamente a rota no mapa. Esse objeto é do tipo DirectionsResult e é montado pela função route antes de ser repassado como response à nossa função de callback. No entanto, você pode usar manualmente as informações contidas em response sem que a rota precise ser exibida.

De acordo com a documentação, o DirectionsResult possui como única propriedade um Array chamado routes contendo a lista de rotas encontradas pelo serviço para atender a requisição feita. Cada elemento dessa lista traz detalhes de uma rota específica, descrita na forma de um objeto DirectionsRoute, o que inclui detalhes de cada trecho do trajeto, como a distância de cada um (em metros) e o tempo estimado usando o tipo de locomoção escolhido.

Com isso, podemos facilmente obter a distância a ser percorrida para chegar ao destino usando a rota em questão:
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distancia;
var rota = response.routes[0]; /* Primeira rota */
var etapa = rota.legs[0]; /* única etapa dessa rota */

distancia = etapa.distance.value;

alert ('Distância Total => ' + distancia.toString() + ' metros.');
}
});)

A propriedade legs destrincha cada etapa do trajeto usando objetos do tipo DirectionsLeg para descrevê-las. Por etapa entende-se o deslocamento necessário entre dois endereços. Assim, se você estipulou apenas a origem e o destino, haverá uma única etapa e, por conseguinte, uma única posição em legs. Por outro lado, se você adicionou pontos de referência usando o waypoints na requisição (veja esse post para mais detalhes), haverá uma nova etapa descrevendo como chegar a cada ponto. Nesse caso, será preciso percorrer todas as posições, somando a distância de cada etapa para se obter a distância total da rota:
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distancia = 0;
var rota = response.routes[0]; /* Primeira rota */
var etapa, i;

for (i = 0; i < rota.legs.length; i++)
{
/* Obtem cada etapa */
etapa = rota.legs[i];
distancia += etapa.distance.value;
}

alert ('Distância Total => ' + distancia.toString() + ' metros.');
}
});)

O detalhamento do caminho a ser seguido dentro de cada etapa do trajeto pode ser acessado através da propriedade steps. Cada passo também possui sua distância mas a soma delas já está consolidada na própria etapa (leg). Tenha em mente que, para certos trajetos, pode acontecer de as informações de distância e duração não estarem disponíveis. Por questão de simplicidade, os scripts acima não levam isso em conta. Um exemplo completo dos recursos mostrados até agora pode ser visto na página abaixo, cujo código fonte está neste link:

Um outro ponto importante a ser lembrado é que a função route é assíncrona. Isto significa que a função de callback que passamos a ela poderá ser executada depois da linha de código imediatamente seguinte ao route. Assim, se tiver que comparar as distâncias de várias rotas, terá que montar uma forma de sincronizar os cálculos para que a comparação só ocorra depois da execução de todos os callbacks envolvidos. Esse efeito normalmente é conseguido através da função javascript setinterval, verificando a cada segundo, por exemplo, se as rotas já estão prontas.

Mais Informações
Traçando rotas com a versão 3 da API do Google Maps - Parte I e Parte II, Google Maps JavaScript API V3

21 de outubro de 2011

Trabalhando com a tag Canvas do HTML5 - parte IV

Até agora , nos posts que escrevi sobre o uso do canvas do HTML5, quando precisei me referir a uma cor nos scripts de exemplo, eu sempre usei a representação tradicional para cores estabelecida pelo CSS (Cascading Style Sheet). Isto é, as cores sempre foram especificadas como uma mistura da intensidade de vermelho, verde e azul (o código RGB) que resultasse na cor desejada. Neste post, mostro que o Canvas permite aplicar um grau de transparência às cores e também trabalhar com gradientes de cores.

As regras para cores no canvas são as mesmas definidas para o CSS. Portanto, podemos usar aqui também a função rgba(), que introduz um quarto parâmetro para controlar o nível de transparência de cores. Os 3 primeiros parâmetros são os conhecidos níveis de intensidade do vermelho, verde e azul (representados por números de 0 a 255). O último valor – chamado de alpha – corresponde ao grau de transparência a ser aplicado aos desenhos subsequentes. Ele pode assumir valores entre 0 (completamente transparente) e 1 (desenha a imagem opaca, sobrepondo totalmente o fundo, sendo esse o efeito padrão obtido até aqui).
var myCanvas = document.getElementById("cnvCores2");
var ctx = myCanvas.getContext("2d");
var incrY = myCanvas.height / 10;
var grausFin = 360;
var radFin = (Math.PI/180)* grausFin;
var meioCompr = myCanvas.width / 2;

ctx.font = "11px Arial";

/* Primeiro, preenche com cor do fundo opaca */
ctx.fillStyle = 'rgb(20,230,20)';
ctx.fillRect(0, 0, meioCompr, myCanvas.height / 2);
ctx.fillRect(meioCompr, myCanvas.height / 2, meioCompr, myCanvas.height / 2);

for (var i = 0; i < 10;i++) { /* agora, pinta por cima usando branco mas variando a transparência */
ctx.fillStyle = 'rgba(255,255,255,' + (1.0 - (i / 10.0)) + ')';
ctx.fillRect(0, i*incrY, meioCompr, incrY);

ctx.fillStyle = "navy";
ctx.fillText (Math.ceil (10.0 - i) / 10.0, 8, (i+1)*incrY - 4);
}

/* preenche no centro um círculo marrom com alto grau de transparência */
ctx.beginPath();
ctx.fillStyle = 'rgba(128,0,0,0.4)';
ctx.arc(meioCompr, myCanvas.height / 4, meioCompr, 0, radFin, false);
ctx.fill();
ctx.closePath();

/* Como seria sem o parâmetro de transparência */
ctx.beginPath();
ctx.fillStyle = 'rgb(128,0,0)';
ctx.arc(meioCompr, 3 * myCanvas.height / 4, meioCompr, 0, radFin, false);
ctx.fill();
ctx.closePath();
Seu navegador (ou leitor de RSS) não suporta canvas ...
Parte do código é dedicada a escrever o grau de transparência de cada uma das faixas horizontais. Como o desenho de texto também é afetado pelo valor de fillStyle - incluindo a transparência configurada nele -, eu atribuo a ela uma outra cor sólida para que o texto esteja plenamente visível, sem qualquer interferência do fundo previamente desenhado.

Há uma propriedade do contexto de pintura chamada globalAlpha que permite mudar o grau de transparência dos desenhos, influenciando preenchimentos e contornos num único comando. Essa característica tem sua utilidade, mas é menos flexível do que controlar separadamente pelo tipo de desenho.

Uma outra forma de trabalhar com cores prevista no canvas do HTML5 é o preenchimento de áreas com um gradiente, isto é, a pintura é iniciada com uma cor e vai gradualmente se aproximando de uma segunda cor, que é finalmente usada para concluir a pintura. Estão previstos dois tipos de gradiente : um linear e outro radial. Para usar um deles, basta criar o tipo desejado, alimentar as cores que comporão o gradiente e então associá-lo à propriedade fillStyle do contexto do Canvas, exatamente como fizemos com cores e transparências.

O tipo linear é instanciado através da função createLinearGradient enquanto para o gradiente radial usamos createRadialGradient. Ambos os métodos criam objetos que respeitam a especificação da interface CanvasGradient, atualmente composta por apenas uma função cujo objetivo é adicionar pontos de parada para as cores no gradiente. Ou seja, essa função define quantas cores básicas haverão no gradiente resultante e quanto espaço cada uma ocupará nele. As cores intermediárias são calculadas automaticamente de modo a preencher toda a área definida pela função de criação.
/* Gradiente linear simples com 2 cores: uma vai do início do retângulo até o meio e a outra do meio até o fim */
var lineargradient = ctx.createLinearGradient(0,0,120,120);
lineargradient.addColorStop (0, 'navy');
lineargradient.addColorStop (0.5, 'rgb(50,50,180)');
lineargradient.addColorStop (1, 'rgb(120,120,220)');

/* Preenche o retângulo real com o padrão gradiente criado acima */
ctx.fillStyle = lineargradient
ctx.fillRect (0,0,180,180);

/* Outro gradiente, agora com mais cores ... */
lineargradient = ctx.createLinearGradient(0,200,180,280);
lineargradient.addColorStop (0, 'navy');
lineargradient.addColorStop (0.2, 'red');
lineargradient.addColorStop (0.8, 'rgb(50,50,180)');
lineargradient.addColorStop (1, 'green');

ctx.fillStyle = lineargradient
ctx.fillRect (0,200,180, 280);
Seu navegador (ou leitor de RSS) não suporta canvas ...
A função createLinearGradient cria uma área retangular - definida por pontos que indicam o canto superior esquerdo e o inferior direito. O gradiente é criado nesse retângulo virtual e ele então é usado para preencher o retângulo real no canvas. Como o retângulo real pode ter tamanho diferente daquele usado para criar o gradiente, o canvas faz alguns ajustes para que caiba. Se o retângulo real for maior, a última cor de parada é usada para preencher o resto da área.

Veja que para adicionarmos uma cor de parada temos que informar um valor entre 0 e 1. Esse valor é chamado de offset e indica a que distância do início do retângulo a nova cor deve começar a aparecer. Uma cor prevalece até que a próxima cor de parada seja encontrada. Então, o tanto de espaço ocupado por cada uma é definido pelo offset da parada seguinte.

Para o gradiente radial, a principal diferença é que, ao invés do efeito ser aplicado num retângulo, ele é aplicado numa área delimitada por dois círculos Os círculos são definidos na criação do gradiente, sendo que os 3 primeiros parâmetros correspondem ao ponto central e raio do círculo inicial e os 3 parâmetros seguintes correspondem ao ponto central e raio do círculo final. A área fora dos círculos é mantida com a cor original da área preenchida.
/* Círculo com gradiente */ var radgrad = ctx.createRadialGradient (105, 115, 20, 112, 130, 50);
radgrad.addColorStop(0, '#FF5F98');
radgrad.addColorStop(0.75, '#FF0188');
radgrad.addColorStop(1, 'rgba(255,1,136,0)');
/* Ao preencher retângulo, somente a área com o gradiente circular é considerada !*/
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,250,500);

/* Outro círculo com gradiente */
radgrad = ctx.createRadialGradient(95,35,15,102,40,40);
radgrad.addColorStop(0, '#00C9FF');
radgrad.addColorStop(0.8, '#00B5E2');
radgrad.addColorStop(1, 'rgba(0,201,255,0)');
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,250,500);

/* Aqui , a área circular resultante é maior que o retângulo; então, o retângulo todo é preenchido */
radgrad = ctx.createRadialGradient (200, 350, 65, 200, 350, 140);
radgrad.addColorStop (0, 'navy');
radgrad.addColorStop (0.5, 'rgb(50,50,180)');
radgrad.addColorStop (1, 'rgb(120,120,220)');
ctx.fillStyle = radgrad
ctx.fillRect (50,200,150,150);
Seu navegador (ou leitor de RSS) não suporta canvas ...
Parte do exemplo acima foi retirada do tutorial de canvas da Fundação Mozilla. Veja que a criação do gradiente aceita a função rgba(), significando que podemos aplicar transparência neste momento também.

Mais Informações
Trabalhando com a tag Canvas do HTML5 - parte I, parte II e parte III, Tutorial de Canvas na Fundação Mozilla

23 de setembro de 2011

Trabalhando com a tag Canvas do HTML5 - parte III

Dando continuidade aos posts sobre uso da tag Canvas (veja parte I e parte II), falo aqui sobre o uso de textos no canvas e a manipulação de imagens.

As propriedades básicas disponíveis para formatarmos a exibição de textos no Canvas são o tipo de fonte e a cor para o desenho. O tipo do fonte pode ser modificado através da propriedade font. Como a interpretação do valor contido nessa propriedade é idêntica ao de folhas de estilo CSS, podemos configurar com ela a família do fonte (Arial, Tahoma, Courier, etc.), o tamanho das letras e estilos especiais (negrito, itálico, etc.).

Quando trabalhamos com figuras geométricas no outro post, vimos que há duas formas de comandar um desenho : traçando apenas a borda ou preenchendo também o interior da figura, sendo que as cores para um ou outro são configuradas em propriedades separadas. O mesmo vale para o desenho de textos.

Assim, a propriedade fillStyle conterá a cor para preencher o texto quando este for desenhado usando a função fillText. Por outro lado, a propriedade strokeStyle define a cor das bordas do texto vazado a ser desenhado usando a função strokeText. Ambas as propriedades aceitam valores que estejam em conformidade com as regras de definição de cores estabelecidas pelo padrão CSS. Veja um exemplo :
var myCanvas = document.getElementById("cnvText2");
var ctx = myCanvas.getContext("2d");

ctx.fillText ("Fonte e cores padrões", 5, 20);

ctx.font = "bold 16px Verdana";
ctx.fillStyle = "blue";
ctx.fillText ("Verdana 16px negrito", 5, 40);

ctx.font = "20px Courier";
ctx.strokeStyle = "#0FAF0F";
ctx.strokeText ("Courier 20px stroke", 5, 65);

ctx.font = "36px Arial";
ctx.strokeStyle = "maroon";
ctx.strokeText ("Arial 36px stroke", 5, 100);
Seu navegador (ou leitor de RSS) não suporta canvas ...

Usando fontes pequenas, o efeito de desenhar somente a borda acaba diluído, dando a impressão que o texto está todo preenchido. Portanto, ele funciona melhor com fontes maiores.

Conforme vemos no quadro anterior, as funções de desenho de texto aceitam 3 parâmetros : o próprio texto que será escrito e as coordenadas da posição no canvas onde ele deve ser renderizado. Normalmente, a posição indica o canto superior esquerdo de um retângulo virtual que conterá o texto. O significado dessa posição, no entanto, pode variar de acordo com o valor das propriedades textAlign e textBaseline. A primeira altera a interpretação do posicionamento horizontal do texto enquanto a segunda controla a coordenada vertical. Não é muito comum ter que alterar esses valores pois, no geral, os valores originais dão conta do recado.

Poderíamos ainda ter passado como 4o parâmetro o comprimento máximo que o texto deve ocupar - caso o tamanho real exceda esse valor, o tamanho do fonte é alterado automaticamente para que o texto caiba.

Um outro aspecto para incrementar a renderização de textos é a criação do efeito de sombra. Essa característica é controlada pelas propriedades shadowColor - que corresponde à cor da sombra - e o par shadowOffsetX e shadowOffsetY - respectivamente, o deslocamento horizontal e vertical da sombra a ser projetada em relação ao texto. É possível também esfumaçar a sombra, deixando o efeito mais sutil. Consegue-se isso usando a propriedade shadowBlur, cujo valor indica o quão borrada deve ser a sombra projetada. Seguem alguns exemplos:
ctx.font = "18pt Arial";
ctx.fillStyle = "#0F0FA0";
ctx.shadowColor = "gray";
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillText ("Texto com sombra comum", 5, 40);

ctx.fillStyle = "#7F0FA0";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 2;
ctx.fillText ("Texto com sombra borrada", 5, 70);

ctx.fillStyle = "green";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 8;
ctx.fillText ("Sombra muito borrada", 5, 100);
Seu navegador (ou leitor de RSS) não suporta canvas ...

De acordo com a padronização da W3C, para desenharmos uma imagem no canvas devemos usar a função drawImage. Com ela, podemos transferir para o canvas uma imagem inteira ou uma parte retangular dela. Também podemos modificar a proporção da imagem renderizada no canvas, indicando se ela é cópia exata da imagem original ou se será ajustada para um novo comprimento e altura. Neste link publicado pela W3C há uma explicação e um gráfico que ilustram o funcionamento dos parâmetros envolvidos na cópia da imagem original para o Canvas.

Em sua versão mais simples, o drawImage aceita como parâmetros a imagem em si e a coordenada do canto superior esquerdo no canvas a partir de onde a imagem será renderizada. Neste contexto, uma imagem pode ser tanto uma tag IMG quanto um outro canvas ou até mesmo a tag VIDEO, introduzida no HTML5. No caso da IMG, podemos recuperar uma referência à imagem usando a função getElementById do documento HTML (se a imagem já estiver embutida na página HTML) ou criar uma nova referência do zero, instanciando o objeto Image:
var img = new Image();
img.onload = function () {
ctx.drawImage (img, 10, 10);
ctx.drawImage (img, 110, 10, 70, 110);
ctx.drawImage (img, 10, 15, 45, 60, 220, 20, 45, 60);
}
img.src = '40anos.gif';

ctx.font = "10pt Tahoma";
ctx.fillText ("Normal", 30, 130);
ctx.fillText ("Novo tamanho", 110, 130);
ctx.fillText ("Cópia parcial",210, 130);
Seu navegador (ou leitor de RSS) não suporta canvas ...
É importante ressaltar que a carga da imagem é disparada de modo assíncrono, logo após a URL para a imagem ter sido informada. Isto significa que o script continua a executar enquanto o navegador localiza e carrega a imagem. Por isso, no script acima a imagem é desenhada no canvas dentro do evento onload da tag da imagem. Ou seja, o desenho só é feito quando efetivamente a imagem está disponível.

As propriedades para sombreamento de texto discutidas neste post também são aplicáveis ao desenho de imagens. Assim, podemos incrementar o resultado adicionando esse efeito conforme a necessidade:
var img = new Image();
img.onload = function () {
/* Remove o recurso de sombreamento para a 1a imagem */
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
ctx.drawImage (img, 10, 10);

/* Aplica o recurso de sombreamento para as demais imagens */
ctx.shadowColor = "#2D90E0";
ctx.shadowOffsetX = 7;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 8;

/* Copia a imagem completa para um local no canvas */
ctx.drawImage (img, 110, 10);
/* Copia parte da imagem para um local no canvas */
ctx.drawImage (img, 10, 15, 45, 60, 220, 20, 45, 60);
}
img.src = '40anos.gif';

/* Para os textos, remove os efeitos de sombreamento */
ctx.font = "10pt Tahoma";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
ctx.fillText ("Normal", 30, 130);
ctx.fillText ("Com Sombra", 110, 130);
ctx.fillText ("Cópia parcial", 210, 123);
ctx.fillText ("com sombra", 210, 137);
Seu navegador (ou leitor de RSS) não suporta canvas ...
Note pelo exemplo anterior que mesmo na cópia de apenas parte de uma imagem o efeito de sombreamento se aplica.

Em outra oportunidade mostrarei mais recursos do canvas associado a cores e imagens (como rotação e criação de gradientes) e como misturar isso tudo.