Imprimindo com um DataGrid Personalizado

Em muitas aplicações Windows, seria bom poder adicionar alguma funcionalidade de impressão amigável. O DataGrid incluido no Windows Forms não da suporte a esta característica. Neste artigo mostraremos como usar um DataGrid personalizado escrito com o Visual Basic .NET, que herda da classe System.Windows.Froms.DataGrid, para implementar uma aplicação com pré-visualização de impressão e obviamente com suporte a impressão.

O suporte a impressão foi escrito para funcionar quando o DataSource usado com o DataGrid for um DataTable ou um DataView. Não há suporte para o caso do DataGrid estar ligado a um Dataset ou algum outro tipo de objeto (Arrays ou outros). Não será muito difícil realizar as modificações necessárias no código fonte, para dar suporte a outros objetos DataSource.

O DataGrid personalizado também tem outras características úteis, tais como a possibilidade de imprimir cabeçalhos de coluna com mais de uma linha (habilitado por padrão), exportar dados em formulárioto XML e HTML e enviar dados por E-mail.

Este código foi escrito preferencialmente em Visual Basic, porque estava sendo desenvolvido para uma aplicação realem VB.NET. Pode ser usado sem problema em uma aplicação C# ou um J#. O seguinte código também foi escrito em VB.NET, porém com poucas alterações (principalmente porque a sintaxe é diferente). Também funcionará bem com outras linguagens .NET.

A Aplicação básica

A primeira coisa que temos a fazer é criar um novo Windows Application Project com um Form contendo um DataGrid padrão e um .NETMainMenu. A seguir, podemos adicionar alguns menus (usando o designer ou via código se preferirmos). Normalmente o formulário principal terá um Sizable FormBorderStyle. O DataGrid terá a propriedade Dock configurada como Fill.

Agora, podemos adicionar uma conexão a banco de dados, um DataAdapter e um Dataset (typed ou untyped), que serão usados para povoar o DataGrid (para efeitos de simplificação, este código foi omitido).

img 

Figura 1.

No projeto de demonstração, acharemos um arquivo teste chamado Demo.xml, que será utilizado para povoar o Dataset com os dados. Na aplicação de exemplo no constructor do Form, usamos diretamente o método ReadXml para ler uma tabela chamada orders, a partir de um Dataset chamado dsOrders.

 

Public Sub New()

 MyBase.New()

 'Esta chamada eh requerida pelo Windows Form Designer.

 InitializeComponent()

 'Codigo adicionado

 dsOrders.ReadXml("..\Demo.xml")

 objDataGrid.DataSource = dsOrders.Tables(0).DefaultView

End Sub

 

Compilando e rodando o projeto, obteremos uma aplicação Windows básica que mostra alguns dados em um DataGrid.

Usando o DataGrid Personalizado

A primeira coisa a fazer é adicionar ao projeto uma referência para a biblioteca que contém o DataGrid personalizado (customcontrols.dll) ou adicionar à nossa aplicação o projeto já existente com o DataGrid (customcontrols.vbproj). Se escolhermos esta segunda opção, teremos que adicionar uma referência ao projeto principal para o projeto adicionado.

 

Para transformar o DataGrid normalem um DataGrid personalizado, a maneira mais simples consiste em usar a funcionalidade Search and Replace do Visual Studio . Abrimos o Form em Code View e usamos a função Replace (CTRL+H). Como texto para substituição, inserimos System.Windows.Forms.DataGrid e como texto substituto CustomControls.DataGridEx.

img 

Figura 2.

Outro modo de usar o DataGrid personalizado, é começar do zero com um novo Form. Podemos simplesmente personalizar a caixa de ferramentas do Visual Studio, adicionando o DataGrid personalizado (o nome da classe é DataGridEx e se encontra em customcontrols.dll). Assim, em vez de arrastar e soltar o DataGrid padrão no designer, arrastaremos o controle DataGridEx. O resultado será o mesmo da abordagem anterior.

 

Se tentarmos compilar e rodar a aplicação agora, obteremos uma exceção de runtime. Isto acontece porque, para obtermos o suporte à pré-visualização de impressão e cabeçalhos de coluna com mais de uma linha, teremos que criar um TableStyle, com informações sobre todas as colunas (principalmente a largura das mesmas). No MSDN, poderemos achar este tipo de informação, porém este pode ser um processo longo e extremamente tedioso. Para contornarmos isto, no constructor do formulário podemos chamar o método AdjustColumnWidths do DataGrid (este método calcula uma largura de coluna ótima para cada coluna, com base nos dados da tabela). Alternativamente, poderíamos chamar o método AdjustColumnWidthToTitles (este método calcula as larguras das colunas com base no texto de cabeçalho das mesmas). Este código também pode ser colocado no manipulador de evento do form load. Se chamarmos qualquer um dos dois métodos, teremos algumas características extras interessantes, tais como a possibilidade de exibir colunas boolean com células coloridas ou usar controles da célula personalizados.

 

Public Sub New()

 ...

 objDataGrid.AdjustColumnWidths(dsOrders.Tables(0))

End Sub

 

Para usar colunas com textos de cabeçalho personalizados, podemos utilizar o método SetColumnName como no exemplo a seguir:

 

Public Sub New()

 ...

 objDataGrid.SetColumnName(dsOrders.Tables(0), "OrderID", "Order Number")

 objDataGrid.SetColumnName(dsOrders.Tables(0), "OrderDate", "Order Date")

End Sub

 

Para adicionar o código do Page Setup, chamamos o método PageSetup (um método static/shared) da classe PageSetup contido no namespace CustomControls.

 

Private Sub mnuPageSetup_Click(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) _

 Handles mnuPageSetup.Click

  CustomControls.PageSetup.PageSetup()

End Sub

 

Para adicionar suporte a Impressão e Pré-visualizção, podemos chamar os métodos Print e PrintPreview do DataGrid. O seguinte código é bastante mais complexo, porque também funciona mesmo que o DataSource utilizado seja um DataTable e não somente se for um DataView.

 

Private Sub mnuPrintPreview_Click(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles mnuPrintPreview.Click

 Dim obj, obj2 As Object

 obj = objDataGrid.DataSource

 If TypeOf (obj) is DataView Then

  obj2 = CType(obj, DataView).Table

 Else

  obj2 = obj

  obj = Nothing

 End If

 

 Me.objDataGrid.PageSettings = CustomControls.PageSetup.PageSettings

 objDataGrid.PrintPreview(CType(obj, DataView), CType(obj2, DataTable), _

  CType(Me.BindingContext(objDataGrid.DataSource), CurrencyManager), _

  25, "Do you wish to continue?")

End Sub

 

Private Sub mnuPrint_Click(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles mnuPrint.Click

 Dim obj, obj2 As Object

 obj = objDataGrid.DataSource

 If TypeOf (obj) is DataView Then

  obj2 = CType(obj, DataView).Table

 Else

  obj2 = obj

  obj = Nothing

 End If

 

 Me.objDataGrid.PageSettings = CustomControls.PageSetup.PageSettings

 objDataGrid.Print(CType(obj, DataView), CType(obj2, DataTable), _

  CType(Me.BindingContext(objDataGrid.DataSource), CurrencyManager))

End Sub

Adicionando Colunas Personalizadas

Para adicionar colunas personalizadas em um DataGrid, temos que criar a própria classe ColumnStyle herdada da classe System.Windows.Forms.DataGridColumnStyle (ou de outras classes que herdam dela).

 

Na biblioteca CustomControls, podemos achar duas classes exemplo utilizáveis nas aplicações. A primeira, é o DataGridBoolColumnEx (uma coluna booleana normal com um fundo pintado com uma cor personalizada, caso o valor da célula for verdadeiro), e a segunda, é o DataGridPushPinColumn (uma coluna booleana com pintura personalizada). Na aplicação exemplo, depois de chamar AdjustColumnWidths, acrescentamos uma chamada para uma função private chamada AddOtherColumns.

 

Esta função obtém uma referência para a tabela carregada de um arquivo XML e adiciona duas novas colunas booleanas que inicializam os valores para as primeiras 11 colunas. A seguir, são criados e inicializados dois novos ColumnStyles para fornecer o cabeçalho, o nome no DataTable e a largura da coluna do DataGrid. Finalmente estes estilos de coluna são acrescentados ao estilo de tabela usado pelo DataGrid.

 

Public Sub New()

MyBase.New()

...

objDataGrid.AdjustColumnWidths(dsOrders.Tables(0))

AddOtherColumns()

...

End Sub

 

Private Sub AddOtherColumns()

 Dim myType As System.Type

 myType = System.Type.GetType("System.Boolean")

 

Dim tbl As DataTable

 tbl = dsOrders.Tables(0)

 tbl.Columns.Add(New System.Data.DataColumn("BoolColumn", myType))

 tbl.Columns.Add(New System.Data.DataColumn("PinnedColumn", myType))

 

 Dim i As Integer

 For i = 0 To tbl.Rows.Count - 1

  tbl.Rows(i).Item("BoolColumn") = IIf(i Mod 3 <> 0, True, False)

  tbl.Rows(i).Item("PinnedColumn") = IIf(i Mod 3 = 0, True, False)

 Next

 

 Dim tst As DataGridTableStyle

 tst = objDataGrid.GetTblStyle(tbl)

 Dim cs As New CustomControls.DataGridBoolColumnEx(Color.Blue)

 cs.HeaderText = "Sample Boolean Column"

 cs.MappingName = "BoolColumn"

 cs.Width = 50

 tst.GridColumnStyles.Add(cs)

 

 Dim cs2 As New CustomControls.DataGridPushPinColumn()

 cs2.HeaderText = "Pinned Column"

 cs2.MappingName = "PinnedColumn"

 cs2.Width = 100

 tst.GridColumnStyles.Add(cs2)

End Sub

 

As seguintes imagens, mostram o formulário quando a aplicação é iniciada e quando a pré-visualização de impressão é chamada.

 

img 

Figura 3.

 

img 

Figura 4.

Detalhes de Baixo Nível

Como anteriormente mencionado, a classe DataGridEx herda do DataGrid padrão .NET. Oferece algumas funcionalidades a mais e estas novas características estão disponíveis nas classes contidas em uma DLL chamada CustomControls. A classe DataGridEx oferece muitos métodos novos. Principalmente temos métodos utilizados para suporte de impressão, para estender as funcionalidades básicas e para trabalhar com estilos de tabela.

 

 Não explicaremos todos os métodos e suas funcionalidades, mas citaremos apenas os mais importantes e úteis. Se olharmos o código, acharemos muitas outras características interessantes.

 

Existe um método chamado HitCellTest utilizado para levantar um evento chamado CellHitTest. Este evento é disparado com base na posição do mouse e informa o usuário qual a célula por onde o ponteiro do mouse está passando. Uma propriedade chamada MouseOverNotificationEnabled é utilizada para habilitar ou desabilitar a notificação da célula atual, com base na posição do ponteiro do mouse. Caso a notificação for habilitada, a posição do mouse é verificada a cada segundo e um evento chamado MouseOverNotification poderá ser eventualmente disparado (pode ser útil para exibir tooltips personalizados, com base na posição do mouse).

 

Quando chamamos o método AdjustColumnWidthToTitles um novo TableStyle é criado e para cada coluna da tabela passada como parâmetro, é criado um ColumnStyle. A largura de coluna é calculada com base na fonte do cabeçalho. O método AdjustColumnWidths chama simplesmente AdjustColumnWidthToTitles e faz uma varredura na tabela para cada linha e cada coluna que eventualmente aumentar a largura da coluna. Estes métodos criam estilos de coluna baseados no datatype das mesmas. Eles tentam criar estilos de coluna estendidos (aqueles escritos por nós) e se, por exemplo, houver uma coluna booleana, um DataGridBoolColumnEx será criado.

 

A classe PrintPreviewDialogEx herda do diálogo de pré-visualização de impressão padrão. O código nesta classe é bastante interessante porque o PrintPreviewDialog padrão tem uma barra de ferramentas private. Se apenas herdarmos desta classe, não poderemos obter uma referência padrão para esta barra de ferramentas e assim não poderemos adicionar nenhum botão a mais à mesma. A solução para este problema foi usar as características de reflexão .NET para obter uma referência às variáveis private. O verdadeiro diálogo de pré-visualização de impressão disponível para os usuários é o TablePrintPreviewDialog. Herda de PrintPreviewDialogEx e adiciona um pouco de funcionalidade, tal como a exportação de dados em XML ou HTML e a possibilidade de enviar os dados por e-mail.