Um cliente nosso está desenvolvendo uma app em WPF. A aplicação está linda, ele está usando diversos recursos de renderização e animação, e o resultado é realmente impressionante.

Só que, como a maioria das apps, ele precisa imprimir. No caso desta empresa, a impressão era de algo parecido com um recibo, frente e verso. Como fazer isso com WPF? Havia cogitado comprar componentes, trabalhar com PDFs intermediários, usar XPS intermediários, ou algo do tipo. Não precisa de nada disso, o WPF facilita muito o trabalho de impressão.

O trabalho todo é extremamente simples. Iniciamos exibindo uma janela de diálogo para impressão, baseado na classe System.Windows.Controls.PrintDialog (não confundir com a PrintDialog do Windows Forms). Ela vai nos dar os dados de impressão escolhidos pelo usuário, como o tamanho da área de impressão, e um método PrintDocument, que recebe um objeto capaz de gerar páginas. Esse objeto eu mesmo criei. O clique do botão ficou simples assim:

private void PrintClick(object sender, RoutedEventArgs e)
{
    var printDialog = new PrintDialog();
    if (printDialog.ShowDialog() != true) return;

    var paginator = new Paginador(new ServicoDados().ObterClientes(),
                                        new Size(printDialog.PrintableAreaWidth,
                                        printDialog.PrintableAreaHeight));
    printDialog.PrintDocument(paginator, "Impressão Wpf");
}

Esta classe Paginador herda de DocumentPaginator, do próprio WPF. Estou passando pra ela durante a construção o tamanho da área de impressão, que ela vai precisar pra calcular o tamanho da impressão. Na construção também ela também recebe os dados que serão impressos (obtidos via classe ServicoDados).

A classe paginador faz boa parte da mágica. O método GetPage é o mais importante e deve ser sobrescrito. Nele eu preciso entregar uma página para impressão. O WPF vai pedir páginas de acordo com o número do PageCount, que, como eu imprimo frente e verso, é igual ao número de clientes vezes 2:

public override int PageCount
{ get { return _clientes.Count * 2; } }

A cada 2 páginas eu mudo de cliente. Assim, fiz uma lógica para ver se estou imprimindo a frente ou o verso. Tanto a frente quanto o verso são controles WPF (nomeados Frente e Verso pra facilitar). Se estou na frente, pego um cliente novo e guardo ele como o cliente atual, que vai ser utilizado também na chamada seguinte, para impressão do verso. Passo o cliente então para o controle, como sendo seu contexto, acerto seu tamanho, chamo UpdateLayout para fazer o databinding. Depois, basta retornar uma DocumentPage referenciando o controle.

public override DocumentPage GetPage(int pageNumber)
{
    Control controleParaImprimir;
    if (_paginaAtual % 2 == 0)
    {
        controleParaImprimir = new Frente();
        _clienteAtual = _clientes[_paginaAtual / 2];
    }
    else
    {
        controleParaImprimir = new Verso();
    }
    _paginaAtual++;
    controleParaImprimir.DataContext = _clienteAtual;
    controleParaImprimir.Arrange(new Rect(new Point(0, 0), PageSize));
    controleParaImprimir.UpdateLayout();
    return new DocumentPage(controleParaImprimir);
}

Eu poderia ter construido o controle manualmente, mas fazê-lo com o designer gráfico facilita bastante, já que ele é muito bom, e eu poderia utilizar o Blend, por exemplo.

A frente tem algum databinding simples com o nome do cliente (vejam os elementos de binding):

<Image Margin="46,20,23,0" Name="image1" Stretch="None" Source="{Binding ImagemFrenteMarcaDAgua}" Height="359" VerticalAlignment="Top" />
<Grid Height="105" HorizontalAlignment="Left" Margin="46,53,0,0" Name="grid1" VerticalAlignment="Top" Width="218" Background="BlanchedAlmond">
    <TextBlock Height="23" HorizontalAlignment="Left" Margin="36,34,0,0" Name="txtNome" Text="{Binding Nome, Mode=TwoWay}" VerticalAlignment="Top" Width="159" />
</Grid>

Ela usa também binding em uma imagem. Mais ou menos assim:

Designer do Visual Studio mostrando o controle

Ao imprimir, o resultado é esse:

Resultado impresso

Note como é interessante o fato de que a imagem, um png vetorial, escala perfeitamente, e a transparência funciona conforme o esperado.

O verso é bem parecido, um pouco mais simples. Nem a frente nem o verso tem nada no codebehind, mas algum código que manipulasse a view seria útil (e justo) se fosse necessário.

O Print Preview também é muito simples. O WPF não faz tudo sozinho, mas ajuda muito. Ele tem um controle chamado DocumentViewer, que permite fazer a visualização de arquivos XPS. Colocando ele numa janela do WPF, basta passar uma instância de um documento XPS que ele exibe. Simple assim:

<DocumentViewer Name="viewer" Document="{Binding Path=Document}" />

Pra usá-lo é fácil. Todo o gerenciamento pra montagem do arquivo XPS é feito pela classe de paginador que já está pronta. Desta vez, em vez de usar o PrintDialog pra imprimir, usamos um XpsDocumentWriter para escrever no documento XPS usando o paginador (linha 10 abaixo). Com isso, basta pegar o documento e passar ele como contexto de binding da janela de Preview e exibi-la.

private void PreviewClick(object sender, RoutedEventArgs e)
{
    var printDialog = new PrintDialog();
    var paginator = new Paginador(new ServicoDados().ObterClientes(),
                                        new Size(printDialog.PrintableAreaWidth,
                                        printDialog.PrintableAreaHeight));
    using (var xpsDocument = new XpsDocument(Path.GetRandomFileName(), FileAccess.ReadWrite))
    {
        var writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
        writer.Write(paginator);
        var previewWindow = new PrintPreview
        {
            Document = xpsDocument.GetFixedDocumentSequence()
        };
        previewWindow.ShowDialog();
    }
}

O resultado fica ótimo:

Janela de print preview

Mais simples impossível.

O código fonte pode ser visto e baixado no BitBucket.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.