Como imprimir um formulário no Windows Forms com C# e VB.NET? - André Alves de Lima
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:
private void imprimirButton_Click(object sender, EventArgs e)
Private Sub ImprimirButton_Click(sender As Object, e As EventArgs) Handles ImprimirButton.Click
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:
var image = new Bitmap(this.Width, this.Height);
graphics.CopyFromScreen(this.Location.X, this.Location.Y, 0, 0, this.Size);
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)
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"):
Bitmap captura = null;
private void CapturarForm()
captura = new Bitmap(this.Width, this.Height);
var graphics = Graphics.FromImage(captura);
Private Captura As Bitmap
Private Sub CapturarForm()
Captura = New Bitmap(Me.Width, Me.Height)
Dim graphics = System.Drawing.Graphics.FromImage(Captura)
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":
CapturarForm();
e.Graphics.DrawImage(captura, 20, 20);
CapturarForm()
e.Graphics.DrawImage(Captura, 20, 20)
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:
[Serializable, System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public System.Drawing.Rectangle ToRectangle()
return System.Drawing.Rectangle.FromLTRB(Left, Top, Right, Bottom);
[System.Runtime.InteropServices.DllImport(@"dwmapi.dll")]
DwmwaExtendedFrameBounds = 9
Public Module WindowHelper
<Serializable, System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)>
Public Function ToRectangle() As System.Drawing.Rectangle
Return System.Drawing.Rectangle.FromLTRB(Left, Top, Right, Bottom)
<System.Runtime.InteropServices.DllImport("dwmapi.dll")>
Public Function DwmGetWindowAttribute(hwnd As IntPtr, dwAttribute As Integer, ByRef pvAttribute As Rect, cbAttribute As Integer) As Integer
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:
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);
graphics.CopyFromScreen(rectangle.Left, rectangle.Top, 0, 0, rectangle.Size);
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)
Graphics.CopyFromScreen(Rectangle.Left, Rectangle.Top, 0, 0, Rectangle.Size)
Execute a aplicação, clique no botão "Imprimir" e veja o novo resultado:
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":
var formBorderStyleAnterior = this.FormBorderStyle;
this.FormBorderStyle = formBorderStyleAnterior;
Dim FormBorderStyleAnterior = Me.FormBorderStyle
Me.FormBorderStyle = FormBorderStyleAnterior
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:
captura = new Bitmap(200, 200);
graphics.CopyFromScreen(this.Location.X + 15, this.Location.Y + 15, 0, 0, new Size(200, 200));
Captura = New Bitmap(200, 200)
Graphics.CopyFromScreen(Me.Location.X + 15, Me.Location.Y + 15, 0, 0, New Size(200, 200))
E este seria o resultado:
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!
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
28 thoughts on "Como imprimir um formulário no Windows Forms com C# e VB.NET?"
17 de novembro de 2016 às 1:53
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..
klenio onerio disse:
Muito bom este Artigo, vai me ajudar em muita coisa..
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..
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..
Danilo da silva barbosa disse:
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:
Está muito complicado de entender…
Tem como você dar mais uma ideia? Por favor?
9 de outubro de 2017 às 8:56
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?
19 de novembro de 2016 às 16:07
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 ?
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..
22 de novembro de 2016 às 3:28
Como siempre muy buen articulo y tu explicación igualmente muy clara, es información muy útil para tener presente.
Olá Edward, muito obrigado pelo comentário! Fico feliz que tenha gostado da explicação.. :)
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 :
Também com o link que você indicou, consegui redimensionar o tamanho da imagem do formulário para caber na impressão.
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
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":
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),
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?
18 de janeiro de 2018 às 6:04
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.. :)
18 de janeiro de 2018 às 6:01
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:
suaClasseDeImpressao.Imprime(FormularioASerImpresso.Handle)
Você poderia postar mais detalhes de como você implementou essa "classe genérica" para conseguirmos entender melhor o que você fez?
Bom dia, desculpa a demora..rsrs…Abaixo segue o método implementado, basta chamar a função "CapturaTela" passando os parâmetros.
Public Class CapturarTela
Public Shared Function CapturarTelas(ByVal AlturaForm As Integer, ByVal LarguraForm As Integer, ByVal XLocalizacao As Integer, ByVal yLocalizao As Integer, ByVal TamanhoForm As System.Drawing.Size, ByRef EstiloBordaForm As FormBorderStyle, ByRef FormHandle As System.IntPtr) As Image
CapturarForm(AlturaForm, LarguraForm, XLocalizacao, yLocalizao, TamanhoForm, EstiloBordaForm, FormHandle)
Return Captura2
Shared Captura, Captura2 As Bitmap
Private Shared Sub CapturarForm(ByVal AlturaForm As Integer, ByVal LarguraForm As Integer, ByVal XLocalizacao As Integer, ByVal yLocalizao As Integer, ByVal TamanhoForm As System.Drawing.Size, ByRef EstiloBordaForm As FormBorderStyle, ByRef FormHandle As System.IntPtr)
Dim FormBorderStyleAnterior = EstiloBordaForm
EstiloBordaForm = FormBorderStyle.None
WindowHelper.DwmGetWindowAttribute(FormHandle, CInt(WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds),
Captura = New Bitmap(LarguraForm, AlturaForm)
graphics.CopyFromScreen(XLocalizacao, yLocalizao, 0, 0, TamanhoForm)
Captura2 = FixedSize(Captura, 1115, 790)
EstiloBordaForm = FormBorderStyleAnterior
Dim bmPhoto As Bitmap = New Bitmap(Width, Height, PixelFormat.Format64bppPArgb)
grPhoto.InterpolationMode = InterpolationMode.HighQualityBilinear
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..
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..
5 de outubro de 2018 às 5:11
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
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: