16 11 2016
Como imprimir um formulário no Windows Forms com C# e VB.NET?
Esses dias atrás eu recebi um comentário no meu artigo sobre impressão direta com C# perguntando se seria possível imprimirmos um formulário no Windows Forms. Eu sabia que isso era possível, então, dei uma pesquisada rápida e encaminhei este link para ele. Porém, como eu já tinha recebido esse tipo de pergunta mais de uma vez (além das diversas vezes que já me deparei com essa dúvida nos fóruns da MSDN), percebi que essa era uma demanda recorrente. Então, eu pensei: que tal escrever um artigo sobre essa funcionalidade? Quer conferir o resultado? Então, vamos lá!
Capturando uma imagem do formulário
Imprimir o formulário no Windows Forms não é uma tarefa nada difícil, principalmente se pudermos considerar a borda na do formulário. Um simples objeto do tipo Graphics em conjunto com a chamada do seu método CopyFromScreen atende facilmente essa demanda. Esse método faz a captura de uma determinada área da tela. Dessa forma, se nós simplesmente passarmos as coordenadas do formulário, teremos o resultado esperado. Vamos ver como podemos fazer isso?
Primeiramente, vamos criar um novo projeto do tipo “Windows Forms Application“. Dentro do formulário, vamos adicionar um botão (“imprimirButton“) e um componente do tipo PrintDocument (“printDocument“):
Em seguida, no evento “Click” do botão, vamos chamar o método “Print” do nosso PrintDocument:
// C# private void imprimirButton_Click(object sender, EventArgs e) { printDocument.Print(); }
' VB.NET Private Sub ImprimirButton_Click(sender As Object, e As EventArgs) Handles ImprimirButton.Click PrintDocument.Print() End Sub
A definição do que será impresso pelo componente PrintDocument é feita através do seu evento “PrintPage“. Para implementarmos o código nesse evento, vamos até o designer do formulário, clicamos no componente PrintDocument e, na janela de propriedades, nós temos que encontrar o evento “PrintPage” e, em seguida, clicamos duas vezes para criarmos um novo manipulador para esse evento:
Dentro desse evento, vamos capturar o desktop, limitando para a área onde o formulário está localizado:
// C# private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e) { var image = new Bitmap(this.Width, this.Height); var graphics = Graphics.FromImage(image); graphics.CopyFromScreen(this.Location.X, this.Location.Y, 0, 0, this.Size); e.Graphics.DrawImage(image, 20, 20); }
' VB.NET Private Sub PrintDocument_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument.PrintPage Dim Image = New Bitmap(Me.Width, Me.Height) Dim graphics = System.Drawing.Graphics.FromImage(Image) graphics.CopyFromScreen(Me.Location.X, Me.Location.Y, 0, 0, Me.Size) e.Graphics.DrawImage(Image, 20, 20) End Sub
Nota: a imagem do formulário será impressa na coordenada 20,20 da página. Se você quiser imprimir o formulário em uma outra coordenada, basta alterar os dois últimos valores dos parâmetros do método “DrawImage”.
Agora execute a aplicação e clique no botão “Imprimir“. A impressão será feita na impressora “padrão“. Para alterar a impressora, você pode configurar o nome da impressora na propriedade “PrinterSettings.PrinterName” (do componente PrintDocument) ou implementar uma funcionalidade onde o usuário possa escolher qual impressora utilizar, como eu mostrei no meu artigo sobre impressão direta na impressora. O resultado da impressão será parecido com a imagem abaixo:
Notou o problema? O método “CopyFromScreen” realmente copia tudo o que está sendo exibido na tela no momento da sua chamada, inclusive diálogos que possam estar por cima do formulário (como foi o caso do diálogo de impressão que foi capturado no nosso exemplo). Para que o diálogo não seja capturado na impressão, temos que chamar o método “CopyFromScreen” antes de chamarmos o método “Print” do “PrintDialog“, armazenando o resultado em uma variável no nível do formulário, que posteriormente será utilizada dentro do evento “PrintPage“.
Dito isso, a primeira coisa que temos que fazer é extrairmos a captura em um método (que daremos o nome de “CapturarForm“):
// C# Bitmap captura = null; private void CapturarForm() { captura = new Bitmap(this.Width, this.Height); var graphics = Graphics.FromImage(captura); graphics.CopyFromScreen(this.Location.X, this.Location.Y, 0, 0, this.Size); }
' VB.NET Private Captura As Bitmap Private Sub CapturarForm() Captura = New Bitmap(Me.Width, Me.Height) Dim graphics = System.Drawing.Graphics.FromImage(Captura) graphics.CopyFromScreen(Me.Location.X, Me.Location.Y, 0, 0, Me.Size) End Sub
Em seguida, vamos ajustar o código que faz a impressão, de forma que esse novo método seja chamado antes do “Print” do “PrintDocument” e que o atributo “Captura” seja utilizado dentro do evento “PrintPage“:
// C# private void imprimirButton_Click(object sender, EventArgs e) { CapturarForm(); printDocument.Print(); } private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e) { e.Graphics.DrawImage(captura, 20, 20); }
' VB.NET Private Sub ImprimirButton_Click(sender As Object, e As EventArgs) Handles ImprimirButton.Click CapturarForm() PrintDocument.Print() End Sub Private Sub PrintDocument_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument.PrintPage e.Graphics.DrawImage(Captura, 20, 20) End Sub
Pronto! Veja só o resultado na imagem a seguir:
Como você pode perceber, o diálogo de impressão não é mais capturado na imagem. Porém, você viu que ainda temos um outro problema para resolver? No Windows 10, temos esse efeito de transparência ao redor da janela, que faz com que o conteúdo que estiver embaixo do formulário seja impresso também.
Desconsiderando o efeito de transparência do Windows 10
Depois de procurar bastante, acabei encontrando duas threads no StackOverflow mostrando como pegar as coordenadas do formulário desconsiderando o efeito de transparência da borda. A solução consiste em chamar um método da dll “dwmapi“, que retornará as coordenadas verdadeiras da janela.
Para consertarmos esse problema, vamos criar uma nova classe no nosso projeto, a qual daremos o nome de “WindowHelper“. O conteúdo dessa classe deverá ser o seguinte:
// C# public static class WindowHelper { [Serializable, System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct Rect { public int Left; public int Top; public int Right; public int Bottom; public System.Drawing.Rectangle ToRectangle() { return System.Drawing.Rectangle.FromLTRB(Left, Top, Right, Bottom); } } [System.Runtime.InteropServices.DllImport(@"dwmapi.dll")] public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out Rect pvAttribute, int cbAttribute); public enum Dwmwindowattribute { DwmwaExtendedFrameBounds = 9 } }
' VB.NET Public Module WindowHelper <Serializable, System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)> Public Structure Rect Public Left As Integer Public Top As Integer Public Right As Integer Public Bottom As Integer Public Function ToRectangle() As System.Drawing.Rectangle Return System.Drawing.Rectangle.FromLTRB(Left, Top, Right, Bottom) End Function End Structure <System.Runtime.InteropServices.DllImport("dwmapi.dll")> Public Function DwmGetWindowAttribute(hwnd As IntPtr, dwAttribute As Integer, ByRef pvAttribute As Rect, cbAttribute As Integer) As Integer End Function Public Enum Dwmwindowattribute DwmwaExtendedFrameBounds = 9 End Enum End Module
Em seguida, temos que ajustar o nosso método que faz a captura do formulário, de forma que ele utilize os valores retornados por essa nova classe que acabamos de criar:
// C# private void CapturarForm() { WindowHelper.Rect rect; WindowHelper.DwmGetWindowAttribute(this.Handle, (int)WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds, out rect, System.Runtime.InteropServices.Marshal.SizeOf(typeof(WindowHelper.Rect))); var rectangle = rect.ToRectangle(); captura = new Bitmap(rectangle.Width, rectangle.Height); var graphics = Graphics.FromImage(captura); graphics.CopyFromScreen(rectangle.Left, rectangle.Top, 0, 0, rectangle.Size); }
' VB.NET Private Sub CapturarForm() Dim Rect As WindowHelper.Rect WindowHelper.DwmGetWindowAttribute(Me.Handle, CInt(WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds), Rect, System.Runtime.InteropServices.Marshal.SizeOf(GetType(WindowHelper.Rect))) Dim Rectangle = Rect.ToRectangle() Captura = New Bitmap(Rectangle.Width, Rectangle.Height) Dim Graphics = System.Drawing.Graphics.FromImage(Captura) Graphics.CopyFromScreen(Rectangle.Left, Rectangle.Top, 0, 0, Rectangle.Size) End Sub
Execute a aplicação, clique no botão “Imprimir” e veja o novo resultado:
Agora sim, hein?
Imprimindo o formulário sem a borda
Em algumas situações, pode ser que precisemos imprimir somente o conteúdo interno do formulário, ou seja, desconsiderando totalmente as bordas. Pesquisei um pouco sobre esse tema, encontrei algumas possibilidades para calcularmos as coordenadas da área interna do formulário, mas, todas as opções me pareceram muito “gambiarra“.
Uma alternativa bem simples nesse caso seria, antes de realizarmos a captura, nós alterarmos o estilo da janela, removendo as bordas através da propriedade FormBorderStyle. Depois da captura, nós restauramos o estilo de borda anterior. Eu sei que essa solução não é das mais bonitas, mas, eu acho que às vezes a solução mais “feia” acaba sendo a mais recomendada. Imagina se nós utilizamos um método de calcular as coordenadas internas do formulário e na próxima versão do Windows esse método para de funcionar? Melhor evitar essa situação, não é mesmo?
Dito isso, para capturarmos o formulário sem as bordas, nós poderíamos fazer o seguinte ajuste no método “CapturarForm“:
// C# private void CapturarForm() { var formBorderStyleAnterior = this.FormBorderStyle; try { this.FormBorderStyle = FormBorderStyle.None; WindowHelper.Rect rect; WindowHelper.DwmGetWindowAttribute(this.Handle, (int)WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds, out rect, System.Runtime.InteropServices.Marshal.SizeOf(typeof(WindowHelper.Rect))); var rectangle = rect.ToRectangle(); captura = new Bitmap(rectangle.Width, rectangle.Height); var graphics = Graphics.FromImage(captura); graphics.CopyFromScreen(rectangle.Left, rectangle.Top, 0, 0, rectangle.Size); } finally { this.FormBorderStyle = formBorderStyleAnterior; } }
' VB.NET Private Sub CapturarForm() Dim FormBorderStyleAnterior = Me.FormBorderStyle Try Me.FormBorderStyle = FormBorderStyle.None Dim Rect As WindowHelper.Rect WindowHelper.DwmGetWindowAttribute(Me.Handle, CInt(WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds), Rect, System.Runtime.InteropServices.Marshal.SizeOf(GetType(WindowHelper.Rect))) Dim Rectangle = Rect.ToRectangle() Captura = New Bitmap(Rectangle.Width, Rectangle.Height) Dim Graphics = System.Drawing.Graphics.FromImage(Captura) Graphics.CopyFromScreen(Rectangle.Left, Rectangle.Top, 0, 0, Rectangle.Size) Finally Me.FormBorderStyle = FormBorderStyleAnterior End Try End Sub
Nota: o bloco try-finally assegura que o FormBorderStyle anterior seja sempre restaurado, independentemente se uma exceção tiver sido disparada durante o processo de impressão.
Veja só o resultado da impressão após essas alterações:
Imprimindo uma área específica do formulário
Outra demanda que pode surgir é a necessidade de imprimirmos somente uma área específica do formulário, e não o formulário inteiro. Isso é muito fácil de ser resolvido. Basta ajustarmos os valores de tamanho e coordenadas utilizados no método “CapturarForm“.
Por exemplo, se quisermos imprimir uma área de 200 x 200 pixels do nosso formulário, começando na coordenada 15, 15, o código do método “CapturarForm” ficaria assim:
// C# private void CapturarForm() { captura = new Bitmap(200, 200); var graphics = Graphics.FromImage(captura); graphics.CopyFromScreen(this.Location.X + 15, this.Location.Y + 15, 0, 0, new Size(200, 200)); }
' VB.NET Private Sub CapturarForm() Captura = New Bitmap(200, 200) Dim Graphics = System.Drawing.Graphics.FromImage(Captura) Graphics.CopyFromScreen(Me.Location.X + 15, Me.Location.Y + 15, 0, 0, New Size(200, 200)) End Sub
E este seria o resultado:
Concluindo
Apesar de não ser tão complicado imprimir um formulário no Windows Forms, existem alguns detalhezinhos que podem acabar nos deixando com o cabelo em pé. Principalmente o efeito de transparência nas bordas dos formulários do Windows 10, que pode se tornar um pesadelo nesse processo.
No artigo de hoje você conferiu como imprimir um formulário no Windows Forms e como lidar com o problema do efeito na borda do Windows 10. Além disso, vimos também como imprimir somente o conteúdo interno do formulário (desconsiderando as bordas) e como imprimir somente uma área específica do formulário.
E você? Já precisou imprimir um formulário no Windows Forms? Também passou por esses problemas que eu mencionei nesse artigo? Como é que você conseguiu resolver essa demanda? Fico aguardando os seus comentários!
Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.
Até a próxima!
André Lima
Image by Pixabay used under Creative Commons
https://pixabay.com/en/menu-gui-interface-template-ui-303121/
https://pixabay.com/en/inkjet-print-printer-peripheral-147674/
Listando as instâncias do SQL Server com C# e VB.NET Como incrementar o número da versão automaticamente no Visual Studio
Andre, obrigado pelo seu artigo. Já vejo utilidade futura: eu crio tarefas no Windows Forms para meus alunos e precisarei fazer capturas de telas para explicar como preencher os campos. Acrescentar este código será vantajoso para capturar o Form e explicar o que deve ser feito. Abraços!
Olá Murilo, obrigado pelo comentário! Fico feliz que o artigo tenha o potencial de ser útil nos seus projetos! :)
Qualquer dúvida, é só entrar em contato..
Abraço!
André Lima
Muito bom este Artigo, vai me ajudar em muita coisa..
parabens !!!
klenio
Olá Klenio, muito obigado pelo comentário! Fico feliz que o conteúdo do artigo vá ser útil nos seus projetos.. Qualquer dúvida é só falar..
Abraço!
André Lima
Bom Tarde André,Gostei muito artigo, só gostaria de saber se tem como extrair o documento ao invés de imprimir diretamente ?
Olá Danilo, obrigado pelo comentário! Nesse caso você teria que utilizar alguma biblioteca para gerar o PDF, como eu mostrei neste outro artigo.. Porém, pelo que entendi no seu outro comentário, você já conseguiu isso com a biblioteca iTextSharp, não? Para não misturar as coisas, vou responder à sua outra dúvida no outro comentário..
Abraço!
André Lima
Bom Tarde André,Estou com o seguinte problema,eu fiz uma “gambiarra” que tira Print do Form e salva o Bitmap no diretório raiz da aplicação no formato Pgn normal e depois com a biblioteca iTextSharp eu gero o Pdf a partir da imagem que eu salvei,até aqui deu certo porem o problema é quando o Form é grande,quando vou gerar um PDF só fica uma parte da imagem e a outra faltando por a imagem ser grande,teria como na hora que eu for da um Print no Form ele diminuir o tamanho da imagem redimensionando sem cortar ?
Olá novamente Danilo!
Nesse caso eu acho que você deveria deixar a imagem gerada com o tamanho normal e, antes de incluir a imagem no PDF, você teria que redimensioná-la de acordo com o tamanho da página do seu documento PDF.. Neste link você encontra alguns exemplos de como redimensionar uma imagem sem perder a relação entre altura/largura:
C# Image resizing to different size while preserving aspect ratio
Abraço!
André Lima
Está muito complicado de entender…
Tem como você dar mais uma ideia? Por favor?
Olá Diego!
O que exatamente está muito complicado de entender? Você seguiu o passo a passo e mesmo assim não conseguiu implementar? Onde exatamente você ficou com dúvida?
Abraço!
André Lima
Olá Andre,usando esta ferramenta teria como eu diminuir o tamanho do Print ?, porque estou tentando imprimir um Form grande, e quando vou imprimir ele corta boa parte do Form por estar na escala de 100% ,tem como diminuir isso para caber todo o Form na impressão ?
Desde já agradeço
Atenciosamente
Danilo da Silva.
Olá Danilo! Como mencionei na resposta ao seu outro comentário, acredito que você terá que utilizar algum algoritmo que reduza a imagem sem perder a relação entre altura e largura.. Dá uma olhada lá na minha outra resposta e depois me avisa se o link te ajudou ou não..
Abraço!
André Lima
Buen día para todos,
Como siempre muy buen articulo y tu explicación igualmente muy clara, es información muy útil para tener presente.
Saludos desde Colombia.
Un abrazo.
Olá Edward, muito obrigado pelo comentário! Fico feliz que tenha gostado da explicação.. :)
Um grande abraço!
André Lima
Bom dia para todos,
Muito bom o artigo, mas ficou uma dúvida, há como alterar para a impressão sair em modo retrato?
Consegui aqui, e so alterar a opção :
PrintDocument1.DefaultPageSettings.Landscape = True
Também com o link que você indicou, consegui redimensionar o tamanho da imagem do formulário para caber na impressão.
Obrigado.
Surgiu um erro e uma dúvida.
A declaração WindowHelper não e reconhecido. E de alguma versão mais atual do VS?
“Dim Rect As WindowHelper.Rect
WindowHelper.DwmGetWindowAttribute(Me.Handle, CInt(WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds),
Rect, System.Runtime.InteropServices.Marshal.SizeOf(GetType(WindowHelper.Rect)))
Dim Rectangle = Rect.ToRectangle()”
Olá Thiago, muito obrigado pelos comentários!
Fico feliz que você tenha conseguido alterar as opções de impressão para modo retrato como você mencionou nos comentários anteriores..
Quanto ao WindowHelper, ele é uma classe especial que você tem que adicionar no seu projeto.. Você chegou a dar uma olhada na parte em que eu falo “Para consertarmos esse problema, vamos criar uma nova classe no nosso projeto”? Está logo no início da seção “Desconsiderando o efeito de transparência do Windows 10”:
Abraço!
André Lima
Foi uma “voada” minha…
Fiz aqui e deu certo, mas surgiu uma outra dúvida…rsrs
Implementei uma classe pra sempre que precisar só chamar ela, mas tem um atributo que não estou conseguindo pegar.
“WindowHelper.DwmGetWindowAttribute(Me.Handle, CInt(WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds),
Rect,”
Esse “Me.Handle”, há como eu passar ele como um parâmetro do formulário que ira fazer a impressão?
E mais dúvida…
A qualidade para impressão da imagem fica um pouco embaçada, há como aumentar a qualidade em alguma daquelas configurações?
Olá Thiago!
Não que eu saiba.. A imagem já é salva com a resolução original (ou seja, se o formulário tem 100×100 pixels, ele será salvo com essa resolução).. A meu ver, não teria como aumentar a resolução, uma vez que essa já é a resolução máxima.. É como se você estivesse tirando um screenshot somente da área do formulário..
Mas, se mesmo assim você descobrir uma maneira, volta aqui e conta pra gente.. :)
Abraço!
André Lima
Olá Thiago!
O “Handle” é uma propriedade do Form que está sendo impresso.. Você pode passar ele para a sua classe sem problema nenhum.. Não sei como você estruturou essa classe sua, mas seria algo como:
Você poderia postar mais detalhes de como você implementou essa “classe genérica” para conseguirmos entender melhor o que você fez?
Abraço!
André Lima
Bom dia, desculpa a demora..rsrs…Abaixo segue o método implementado, basta chamar a função “CapturaTela” passando os parâmetros.
Olá Thiago, obrigado por enviar o código..
Pelo que vi, você já está recebendo o handle no parâmetro “FormHandle”.. Qual é a dificuldade que você está tendo, exatamente? Na hora de chamar esse método você só tem que passar o valor da propriedade “Handle” do formulário que você estiver querendo imprimir..
Abraço!
André Lima
Como você coloca o código naquele formulário tabulado?
Olá novamente, Thiago! É com um plugin que eu uso aqui no site.. Precisa colocar umas tags entre o código para ele ser formatado.. Já editei aqui o seu comentário anterior adicionando a formatação..
Abraço!
André Lima
Olá André, quanto tempo rsrsrs
Cara, to tentando imprimir um recorte considerável da tela e mesmo usando o “printDocument1.DefaultPageSettings.Landscape = true;” o espaço do papel não é suficiente para caber a imagem capturada, será que existe alguma gambi para ajustar a imagem pro tamanho do papel?
Tentei o “FixedSize(captura, 800, 800)” q vc recomendou ao Danilo num link nas respostas desse post mas creio que não se aplica ao meu caso, pois (pelo q entendi) “graphics.CopyFromScreen(valorX, valorY, 0, 0, new Size(0, 0));” não retorna imagem
Olá Tony!
Quanto tempo mesmo hein.. Quanto ao seu problema, você já pensou em continuar capturando a imagem do tamanho original, mas aí na hora de desenhar no gráfico do PrintDocument você redimensiona para o tamanho da página? Veja se esta thread do StackOverflow te ajuda:
Printing image with PrintDocument. How to adjust the image to fit paper size?
Abraço!
André Lima