segunda-feira, 22 de outubro de 2012

Aplicando Zoom nas Fontes do Windows

Nessa última atualização do componente (1.5), além das novas funcionalidades que estava desenvolvendo há bastante tempo, me deparei com um problema, que a principio parecia ser simples, com o ajuste de fontes na tela de visualização do relatório e com a diferença na aparência entre a tela e o papel, quando precisava imprimir os textos que tinham configurado a altura automática (TamanhoAuto = True e WordWrap = True). Percebi que os dois problemas estavam relacionados com a diferença de densidade de pontos da tela e da impressora e o sistema de escala de fontes do Windows.

Ocorre que quando o Windows desenha um texto na tela, a fonte tem uma densidade de pontos por polegada diferente quando imprime para a impressora e para tela. Isso faz com que o GDI, que é o engine responsável por desenhar as letras, utilize mais ou menos pixels para um mesmo texto, e isso deixa de ser proporcional. Dessa forma, como o engine do relatório usava a tela como base para medir as larguras e alturas do textos, ocorria que alguns textos aparentemente corretos na tela de visualização, quando enviados para a impressora, as vezes, ocupavam mais espaço do que o "retângulo" que havia sido calculado para ele.
Esse cálculo baseado na tela, é importante, porque define a quantidade de texto que cabe por página e consequentemente, a quantidade de páginas do relatório.

Eu não entendia porque isso não funcionava porque embora estivesse usando a tela como referência, mantinha os valores em Twips, o que garantia a conversão para qualquer Device Context. Eu usava a API DrawText do Windows. Além dela fornecer um meio de calcular a altura que um texto ocupará, ela trabalha o texto com alinhamentos e quebras de linha automáticas. Porém, eu não tinha o controle sobre em qual palavra quebraria o texto de várias linhas. Quando eu passava ela para o Device Context da tela e para o Device Context da impressora, em alguns textos, ela imprimia as quebras de linha de forma diferente, exatamente por causa do problema da diferença entre as densidades dos pixels.

Uma forma de resolver foi deixar de usar essa API e começar a fazer o cálculo das quebras de linha manualmente, calculando, para cada parágrafo, o número de linhas. Para isso usei utilizei a API GetTextExtentExPoint. Depois imprimir, linha a linha, usando a API ExTextOut.
Até esse ponto estava aparentemente tudo resolvido, porque agora eu tinha o controle sobre cada linha do texto e ao enviar tanto para a tela como para a impressora, a quantidade de linhas sempre seria a mesma, de forma que ao fazer um cálculo do número de linhas e páginas não haveria diferenças.

Porém, surgiu outro problema. Durante o zoom no visualizador, para algumas situações, as larguras e as alturas das linhas ficam diferentes do apresentado quando estava em 100%. Mais tarde descobri que esse problema tinha a mesma origem da diferença de densidade de pixels entre a impressora e a tela. Porque na verdade, ao fazer o zoom, internamente o sistema de escalas estava oferecendo uma resolução maior ou menor para o GDI conforme o fator do zoom na tela. Isso porque o engine de visualização usa as APIs de mapeamento e escala do Windows. Depois de tentar entender e procurar alternativas para resolver essa diferença entre as escalas, achei essa discussão no stackoverflow. A discussão estava falando de um problema com uma rotina em Delphi, mas através dela, consegui entender melhor o que estava acontecendo e finalmente resolver o problema.

Para alguns fatores de zoom e também para algumas resoluções de impressão, uma fonte de texto pode ocupar mais ou menos espaço propositalmente. Assim, para o problema na largura dos textos, tive que verificar a diferença de pixels entre o texto que foi calculado para ser impresso numa largura pré-definida e a largura efetiva que o texto precisa para ser impresso na nova resolução ou densidade. A partir desse cálculo, precisava distribuir ou remover os espaços entre os caracteres (caracteres de espaço em branco, se possível) do texto daquela linha. Foi então que percebi porque o Windows colocou um parâmetro chamado "lpDx" na API ExtTextOut. Esse parâmetro serve para fazer exatamente esse "ajuste fino" entre resoluções diferentes. Já o problema da diferença na altura das linhas do texto quando altera o zoom da visualização, resolvi utilizando a altura da fonte quando o fator de zoom está em 100%.

Como toda solução, embora tenha entendido melhor o funcionamento do GDI, acredito que não seja a última e definitiva. :)

domingo, 30 de setembro de 2012

Configurar Impressão

A tela de configuração de impressão pode ser aberta a partir do ícone, na tela de visualização. A Figura 1 mostra a localização dos botões na tela do visualizador.
Figura 1 - Tela do Visualizador
Ao abrir a tela é apresentada a janela com as configurações. O componente busca as configurações que foram feitas para a impressora no painel de controle do Windows.
Figura 2 - Tela de Configuração de Impressão

Através dessa tela, o usuário pode selecionar as seguintes opções de impressão:

  • Impressora: Apresenta uma lista com todas as impressoras disponíveis no painel de impressora do Windows.
  • Tamanho do Papel: Apresenta a lista dos tamanhos de papel disponíveis para a impressora selecionada. 
  • Origem do Papel: Permite escolher a bandeja de papel para a impressão, quando a impressora suporta mais de uma bandeja.
  • Páginas: Permite selecionar quais páginas devem ser impressas. Pode-se selecionar todas as páginas, a página atual ou uma faixa de páginas. Pode-se selecionar também a impressão apenas das páginas ímpares ou pares.
  • Cópias: Indica o número de cópias que devem ser impressas da mesma seleção de páginas do relatório.
  • Usar Recurso do Driver: Essa seleção permite escolher se o componente irá utilizar o recurso do driver da impressora. Se selecionado, indica que o relatório será enviado apenas uma vez para o driver e ele irá replicar as cópias. Caso contrário, o componente irá enviar cada cópia separadamente para a impressora dentro do mesmo Job de impressão. Essa opção é útil quando o driver não tem suporte de múltiplas cópias. A seleção que o usuário fizer para o uso ou não do driver para as cópias, é salva para a próxima vez que usar a impressora.
  • Cor: Permite escolher entre impressão colorida ou em preto e branco.
  • Orientação: Permite escolher entre orientação retrato ou paisagem. É importante que o relatório seja desenhado para orientação retrato para que possa ser impresso em paisagem. No caso do relatório ter sido escrito no modo paisagem, caso o usuário mudar a seleção para a impressão retrato, os dados podem ser cortados.
  • Imprimir Frente e Verso: Permite que o usuário escolha a impressão frente e verso nos drivers que suportam essa configuração. A maioria das impressoras tem suporte no modo manual, Isso significa que o usuário precisará "virar" o papel manualmente quando selecionar essa opção. O tipo de virada de página (horizontal ou vertical) é uma escolha do usuário de acordo com a sua necessidade.
  • Resolução: O usuário pode escolher a qualidade da impressão.
É importante lembrar que quando o usuário altera as opções de impressora ou tamanho de papel, o componente reinicia as rotinas internas de geração do relatório para o novo tamanho/driver. Por isso ;e importante programar o evento IniciarRelatorio do ReportMain para zerar as variáveis ou acumuladores internos usados no relatório. 

Salvar Diretamente para Arquivo

Com o Visual ReportX é possível salvar diretamente o relatório para arquivo sem passar pela visualização. Os tipos atualmente suportados são: PDF, VRX, XLS, HTM, TXT, JPG.
No caso do tipo PDF, no entanto, é que preciso que a máquina tenha instalado o PDF Creator. Ele é uma espécie de impressora virtual e é um freeware. Além de habilitar o componente, caso tenha outras rotinas de impressão no seu sistema que utilizem o objeto Printer, pode apontar para essa impressora virtual para que a impressão seja salva em arquivo PDF.
O exemplo abaixo mostra como salvar o relatório do exemplo utilizado no post Hello! ReportX em PDF.
Para programar o componente, basta configurar as propriedades NomeArquivo, Visualizar, e Salvar do ReportMain, antes de ativar, conforme mostra a figura abaixo.
Figura 1 - Janela do código
Dessa forma, ao executar o relatório, o visualizador não será aberto e será apresentada a tela do relatório sendo salvo.
Figura 2 - Janela enquanto salva o relatório
Além dessa opção, pode-se ligar a propriedade ModoSilencioso do ReportMain. Ela é útil quando não queremos mostrar essa janela da Figura 2 enquanto o relatório é salvo.
Para mudar o tipo de arquivo a ser salvo, basta mudar a extensão do nome do arquivo que é colocado na propriedade NomeArquivo

É possível definir também um nome de arquivo com a extensão EML. Ao definir dessa forma, além de salvar o arquivo no local indicado, o componente irá enviar o comando para abrir um novo email e anexar esse arquivo. Nesse caso, como o tipo definido foi EML, o tipo de arquivo gerado para o anexo, e as configurações para o email, serão obtidas da tela de opções que está disponível no visualizador, como mostra a figura abaixo. 
Figura 3 - Janela de opções do usuário para os relatórios
Para os tipos de arquivo VRX, XLS, HTM e JPG essa janela também define as configurações que são usadas quando os arquivos estão sendo gerados.

sábado, 8 de setembro de 2012

Hello ReportX!

A ideia desse exemplo, além de testar o formato das postagens para o blog, é construir um relatório simples. Não chega a ser o relatório mais simples que podemos escrever com o VRX, mas é um exemplo prático para ter uma noção geral do funcionamento dos objetos que o componente disponibiliza.
Nesse exemplo vou descrever como construir uma listagem de uma tabela de banco de dados usando o banco de exemplo do MS Access que é enviado no pacote de exemplos do componente.

Primeiro, vou mostrar a estrutura da tabela no banco para sabermos como desenhar o relatório.
Figura 1 - O banco de dados
Da tabela de "Estados", vamos imprimir uma listagem com os atributos "Estado" e "Nome".
Para isso, abrimos um novo projeto no VB, adicionamos as referências para a OCX do componente e para o ADO.
Depois inserimos um formulário em branco com o nome "SampleReport", onde desenhamos o relatório seguindo o layout abaixo. Para desenhar, pode arrastar os objetos a partir da barra de ferramentas. A atenção é apenas com os objetos ReportField porque eles precisam ser colocados dentro das seções (objeto ReportSection). A melhora sequência para desenhar é inserir um ReportMain, em seguida os objetos do tipo ReportSection e no final os objetos do tipo ReportField.
Figura 2 - O desenho do relatório
Para criar esse formulário, adicionamos o objeto do ReportMain, definimos a propriedade Name para  "Relatório", a propriedade Titulo para "Exemplo", ligamos a propriedade Regua e definimos a propriedade Divisao para 10. Depois inserimos 3 objetos do tipo ReportSection, definindo para cada um, a propriedade Tipo como secCabecalho, secDetalhe e secRodape respectivamente. Depois inserimos 3 objetos do tipo ReportField na seção de cabeçalho, programando a propriedade Caption com os textos de acordo com a Figura 2. Em seguida, adicionamos mais 2 objetos do tipo ReportField na seção de detalhe programando a propriedade Campo de acordo com o nome do atributo da tabela "Estados".

Agora, escrevemos o código do formulário do relatório conforme a figura abaixo:
Figura 3 - O código do relatório
No formulário, criamos um método público para que possa ser chamado por alguma rotina ou tela do sistema, onde esse método pode ter um filtro, se necessário ou outras opções do relatório.
Normalmente deixamos o código que tem relação com o relatório encapsulado no próprio formulário, assim podemos levar o formulário inteiro para outro projeto, se necessário. É possível até definir as propriedades e executar o relatório a partir de outra parte do sistema. Tudo depende de como o sistema está desenhado. A única restrição aqui é que o relatório não pode ser chamado a partir de um formulário que está sendo exibido de forma modal.
Na Figura 3, destaquei a propriedade Recordset porque através dela, dizemos ao componente qual a fonte de dados. Podemos usar um objeto recordset do ADO ou mesmo do DAO/RDO caso o sistema acesse banco de dados com esses engine. Como ela é uma propriedade do objeto ReportMain, no código que escrevermos nos eventos, que são disparados pelo componente durante a execução, podemos usar essa propriedade para acessar os valores dos campos do registro que estiver corrente, mover o registro para frente para pular alguma parte ou mesmo mover para trás para repetir a impressão de um registro.
Outra parte importante é o método Ativar que inicia a execução do relatório, e ao terminar, tendo o usuário impresso, fechado ou apenas visualizado, o controle volta ao código e fechamos o recordset.
Ao terminar a execução, como o formulário que desenhamos é apenas um container para o desenho do relatório, fazemos o Unload para que ele não fique ocupando memória.

Para executar esse relatório, criamos um formulário simples com um botão e uma caixa de parâmetros que usamos como filtro, e fazemos a chamada do método Config do formulário do relatório, conforme mostra a figura abaixo:
Figura 4 - O formulário de execução
Em seguida, executamos o projeto, e clicamos no botão "Ativar Relatório". Como deixamos as propriedades padrão do ReportMain, onde temos a propriedade Visualizar ligada, o componente irá exibir a tela do visualizador com a execução do relatório. Ao visualizar é mostrada a tela a seguir:
Figura 5 - A visualização do relatório
O componente irá funcionar da seguinte forma:

  • Primeiro todos os objetos da seção de cabeçalho são impressos. 
  • Em seguida, todos os registros do recordset são percorridos, e para cada um, são impressos os objetos da seção de detalhe, buscando o conteúdo de cada atributo do recordset pelo nome que foi colocado na propriedade Campo do ReportField. O componente irá imprimir cada seção de detalhe até que chegue ao final da página. 
  • Quando atingir o final, caso ainda existam registros para imprimir, é iniciada uma nova página e o processo é repetido, até que chegue ao final do recordset, imprimindo a seção de rodapé ao final de cada página.

Assim que terminam os registros do recordset, o visualizador fica em estado de espera para o usuário efetuar as ações. Nesse momento o usuário pode enviar para impressora, salvar o relatório em arquivo, modificar o tamanho de página, ampliar ou reduzir a visualização, enviar o relatório por email, dentre outras funções.
O mecanismo de navegação das páginas é liberado assim que a primeira página é gerada internamente. Assim, quando relatórios mais complexos ou de muitas páginas são gerados, o usuário pode visualizar as páginas já construídas enquanto as restantes são geradas internamente.
Esse é o processo básico. A ideia desse post e dar uma noção básica do mecanismo de forma que possam ser explicadas as outras propriedades, métodos e eventos de cada objeto.

sexta-feira, 7 de setembro de 2012

Início 2.0

Essa postagem dá início ao blog do Visual ReportX. Espero que esse canal de comunicação possa contribuir com mais informações sobre o funcionamento do componente.
A ideia também é reunir as dúvidas dos usuários de forma mais sintética e permitir uma interação maior com as pessoas que utilizam o componente, sejam usuários registrados ou que utilizam a versão free.

Espero que todos participem. Sejam bem-vindos!