Quantcast
Channel: Relatórios – André Alves de Lima
Viewing all articles
Browse latest Browse all 61

Como imprimir um formulário no Windows Forms com C# e VB.NET?

$
0
0

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/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Como imprimir um formulário no Windows Forms com C# e VB.NET? appeared first on André Alves de Lima.


Viewing all articles
Browse latest Browse all 61

Trending Articles


Vimeo 10.7.1 by Vimeo.com, Inc.


UPDATE SC IDOL: TWO BECOME ONE


KASAMBAHAY BILL IN THE HOUSE


Girasoles para colorear


Presence Quotes – Positive Quotes


EASY COME, EASY GO


Love with Heart Breaking Quotes


Re:Mutton Pies (lleechef)


Ka longiing longsem kaba skhem bad kaba khlain ka pynlong kein ia ka...


Vimeo 10.7.0 by Vimeo.com, Inc.


FORECLOSURE OF REAL ESTATE MORTGAGE


FORTUITOUS EVENT


Pokemon para colorear


Sapos para colorear


Smile Quotes


Letting Go Quotes


Love Song lyrics that marks your Heart


RE: Mutton Pies (frankie241)


Hato lada ym dei namar ka jingpyrshah jong U JJM Nichols Roy (Bah Joy) ngin...


Long Distance Relationship Tagalog Love Quotes