WPF & Win Forms: Capturar um controle para uma imagem – C#

Fala galera,

As vezes, okay, muito raramente, aliás, eu nem mesmo tenho ideia de qual seja a utilidade disso, precisamos capturar um controle de nosso aplicativo, e “salva-lo” para uma Imagem.

Isso é possível, no Win Forms e de forma bem fácil no WPF.
Criei uma classe para fazer a captura no Win Forms, e outra classe para fazer a captura no WPF. A diferença? Você vê a seguir:

WPF:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace HerbertLausmann.Wpf
{
    class VisualToBitmap
    {
        public static System.Windows.Media.Imaging.BitmapSource Render(System.Windows.Media.Visual Visual, int Resolution = 96)
        {
            System.Windows.Media.Imaging.RenderTargetBitmap render;
            double width, height;
            int pixelWidth, pixelHeight;
            width = (double)Visual.GetValue(System.Windows.FrameworkElement.ActualWidthProperty);
            height = (double)Visual.GetValue(System.Windows.FrameworkElement.ActualHeightProperty);
            pixelWidth = Convert.ToInt32((width / 96) * Resolution);
            pixelHeight =  Convert.ToInt32((height / 96) * Resolution);
            render = new System.Windows.Media.Imaging.RenderTargetBitmap(
                pixelWidth, pixelHeight, Resolution, Resolution,
                System.Windows.Media.PixelFormats.Pbgra32);
            render.Render(Visual);
            return render;
        }
    }
}

Win Forms:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace HerbertLausmann.WinForms
{
    class HandleToBitmap
    {
        /// <summary>
        /// Gera um Bitmap diretamente do hWnd correspondente à Janela/Controle
        /// </summary>
        /// <param name="hWnd"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap RenderHwnd(IntPtr hWnd)
        {
            IntPtr hDC = USER32.GetDC(hWnd);
            USER32.RECT WndRect = new USER32.RECT();
            USER32.GetWindowRect(hWnd, out WndRect);

            IntPtr capHDC = GDI32.CreateCompatibleDC(hDC);
            IntPtr capBMP = GDI32.CreateCompatibleBitmap(hDC, WndRect.Width(false), WndRect.Height(false));

            if (capBMP == IntPtr.Zero)
            {
                USER32.ReleaseDC(hWnd, hDC);
                GDI32.DeleteDC(capHDC);
                return null;
            }

            IntPtr prvHDC = (IntPtr)GDI32.SelectObject(capHDC, capBMP);
            GDI32.BitBlt(capHDC, 0, 0, WndRect.Width(false), WndRect.Height(false), hDC, 0, 0, GDI32.SRCCOPY);
            GDI32.SelectObject(capHDC, prvHDC);

            System.Drawing.Bitmap bmp = System.Drawing.Bitmap.FromHbitmap(capBMP);

            USER32.ReleaseDC(hWnd, hDC);
            GDI32.DeleteDC(capHDC);
            GDI32.DeleteObject(capBMP);

            return bmp;
        }
        /// <summary>
        /// A grosso modo, essa função tira um Print Screen da tela e recorta a imagem para as proporções da janela/controle.
        /// </summary>
        /// <param name="hWnd"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap RenderFromDesktop(IntPtr hWnd)
        {
            IntPtr hDC = USER32.GetDC(USER32.GetDesktopWindow());
            USER32.RECT WndRect = new USER32.RECT();
            USER32.GetWindowRect(hWnd, out WndRect);

            IntPtr capHDC = GDI32.CreateCompatibleDC(hDC);
            IntPtr capBMP = GDI32.CreateCompatibleBitmap(hDC, WndRect.Width(), WndRect.Height());

            if (capBMP == IntPtr.Zero)
            {
                USER32.ReleaseDC(hWnd, hDC);
                GDI32.DeleteDC(capHDC);
                return null;
            }

            IntPtr prvHDC = (IntPtr)GDI32.SelectObject(capHDC, capBMP);
            GDI32.BitBlt(capHDC, 0, 0, WndRect.Width(), WndRect.Height(), hDC, WndRect.X(), WndRect.Y(), GDI32.SRCCOPY);
            GDI32.SelectObject(capHDC, prvHDC);

            System.Drawing.Bitmap bmp = System.Drawing.Bitmap.FromHbitmap(capBMP);

            USER32.ReleaseDC(USER32.GetDesktopWindow(), hDC);
            GDI32.DeleteDC(capHDC);
            GDI32.DeleteObject(capBMP);

            return bmp;
        }
        /// <summary>
        /// Gera um Bitmap correspondente à área cliente da Janela/Controle
        /// </summary>
        /// <param name="hWnd"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap RenderClientArea(IntPtr hWnd)
        {
            IntPtr hDC = USER32.GetDC(hWnd);
            USER32.RECT WndRect = new USER32.RECT();
            USER32.GetClientRect(hWnd, out WndRect);

            IntPtr capHDC = GDI32.CreateCompatibleDC(hDC);
            IntPtr capBMP = GDI32.CreateCompatibleBitmap(hDC, WndRect.Width(false), WndRect.Height(false));

            if (capBMP == IntPtr.Zero)// if no compatible bitmap
            {
                USER32.ReleaseDC(hWnd, hDC); //   release window context
                GDI32.DeleteDC(capHDC); //   delete capture context
                return null; //   return null bitmap
            }

            IntPtr prvHDC = (IntPtr)GDI32.SelectObject(capHDC, capBMP);
            GDI32.BitBlt(capHDC, 0, 0, WndRect.Width(false), WndRect.Height(false), hDC, WndRect.X(false), WndRect.Y(false), GDI32.SRCCOPY);
            GDI32.SelectObject(capHDC, prvHDC);

            System.Drawing.Bitmap bmp = System.Drawing.Bitmap.FromHbitmap(capBMP);

            USER32.ReleaseDC(hWnd, hDC);
            GDI32.DeleteDC(capHDC);
            GDI32.DeleteObject(capBMP);

            return bmp;
        }
        private class GDI32
        {
            public const int SRCCOPY = 13369376;

            [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
            public static extern IntPtr DeleteDC(IntPtr hDc);

            [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
            public static extern IntPtr DeleteObject(IntPtr hDc);

            [DllImport("gdi32.dll", EntryPoint = "BitBlt")]
            public static extern bool BitBlt(IntPtr hdcDest, int xDest,
                int yDest, int wDest, int hDest, IntPtr hdcSource,
                int xSrc, int ySrc, int RasterOp);

            [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
            public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc,
                int nWidth, int nHeight);

            [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")]
            public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

            [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
            public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);

            [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
            public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);
        }
        private class USER32
        {
            public const int SM_CXSCREEN = 0;
            public const int SM_CYSCREEN = 1;

            [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
            public static extern IntPtr GetDesktopWindow();

            [DllImport("user32.dll", EntryPoint = "GetDC")]
            public static extern IntPtr GetDC(IntPtr ptr);

            [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
            public static extern int GetSystemMetrics(int abc);

            [DllImport("user32.dll", EntryPoint = "GetWindowDC")]
            public static extern IntPtr GetWindowDC(Int32 ptr);

            [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
            public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);

            [DllImport("user32.dll", EntryPoint = "GetWindowRect")]
            public static extern IntPtr GetWindowRect([In] IntPtr hWnd, out RECT lpRect);

            [DllImport("user32.dll", EntryPoint = "GetClientRect")]
            public static extern IntPtr GetClientRect([In] IntPtr hWnd, out RECT lpRect);

            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                int left;
                int top;
                int right;
                int bottom;

                public int X(bool autoScale = true)
                {
                    if (autoScale)
                        return (int)Math.Round(left * getScalingFactor());
                    else
                        return left;
                }

                public int Y(bool autoScale = true)
                {
                    if (autoScale)
                        return (int)Math.Round(top * getScalingFactor());
                    else
                        return top;
                }
                public int Width(bool autoScale = true)
                {
                    if (autoScale)
                        return (int)Math.Round(Math.Abs(right - left) * getScalingFactor());
                    else
                        return (int)(Math.Abs(right - left));                    
                }

                public int Height(bool autoScale = true)
                {
                    if (autoScale)
                        return (int)Math.Round(Math.Abs(bottom - top) * getScalingFactor());
                    else
                        return (int)(Math.Abs(bottom - top));
                }
            }

            [DllImport("gdi32.dll")]
            static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

            public enum DeviceCap
            {
                VERTRES = 10,
                DESKTOPVERTRES = 117,
            }

            public static float getScalingFactor()
            {
                IntPtr desktop = GetDC(GetDesktopWindow());
                int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES);
                int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES);

                float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;
                USER32.ReleaseDC(GetDesktopWindow(), desktop);
                return ScreenScalingFactor; // 1.25 = 125%
            }
        }
    }
}

Como você pode ver, a diferença é que o WPF oferece uma opção nativa, enquanto que no Win Forms temos que lidar com interoperabilidade na API do Windows.

Um pequeno exemplo de uso para o WPF:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPF_Control_to_Image
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnRender_Click(object sender, RoutedEventArgs e)
        {
            BitmapSource bmp = VisualToBitmap.Render(MainBorder);
            Microsoft.Win32.SaveFileDialog dialog = new Microsoft.Win32.SaveFileDialog();
            dialog.Filter = "Imagem PNG(*.png)|*.png";
            if(dialog.ShowDialog() == true)
            {
                System.Windows.Media.Imaging.PngBitmapEncoder encoder = new PngBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(bmp));
                System.IO.FileStream fs = new System.IO.FileStream(dialog.FileName, System.IO.FileMode.Create);
                encoder.Save(fs);
                fs.Close();
            }
        }
    }
}

E um para o Win Forms:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace HWND_To_Image
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnRender_Click(object sender, EventArgs e)
        {
            Bitmap bmp = HandleToBitmap.RenderClientArea(this.Handle);
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.Filter = "Imagem PNG (*.png)|*.png";
            dialog.AddExtension = true;
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                bmp.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Png);
            }
        }

    }
}

E isso é tudo. Vou disponibilizar o download da Solution com os exemplos para Win Forms e WPF:

Control to Image.zip

Até a próxima, pessoal!!!

Anúncios

3 comentários

  1. Olá Herbert!

    Parabéns pelo artigo!

    Esse tipo de funcionalidade pode ser útil para dar suporte. Por exemplo, se você adicionar uma opção do usuário mandar um “screenshot” da tela quando acontece um problema, isso acaba facilitando bastante a vida de quem vai dar suporte.

    Quanto à funcionalidade no Windows Forms, você sabe dizer a diferença entre esse esquema de usar a API do Windows e o método DrawToBitmap da classe Control? Por exemplo, qual a diferença de usar esse método ao invés de chamar um form.DrawToBitmap ou controle.DrawToBitmap?

    Abraço!
    André Lima

    1. Olá André,
      Muito Obrigado!

      Em relação ao DrawToBitmap, ele não funciona em algumas situações e para alguns controles. Um exemplo é o controle RichTextBox o qual não possui suporte para este método.

      Outra coisa que não citei no artigo, mas que é possível com a classe HandleToBitmap, é capturar controles/janelas de outros aplicativos, basta ter o ponteiro para o mesmo…

Deixe um comentário :)

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s