Capturando a tela em Windows Forms
Carlos dos Santos
Março, 2014
Este artigo demonstrar como é possível capturar a tela ou até mesmo o conteúdo de um controle e salvá-lo como um Bitmap. Imagine que você tem uma solução de atendimento ao cliente e em algum momento precise capturar a tela do seu usuário e depois anexá-la a algum requisito do software ou tratamento de um bug.
Claro que existem várias ferramentas prontas para captura de tela, mas vamos ver como é possível, através de um código em C# usando o recurso de Interop e acessando a API do Windows, criar um método reusável que pode ser utilizado para capturar vários tipos de tela no Windows.
Criando o projeto
Vamos criar um projeto no Visual Studio 2013 do tipo Windows Forms (você pode utilizar qualquer outra versão do Visual Studio):
Para deixar o nosso código de captura reutilizável, vamos criar uma nova classe chamada Tela.cs. Nesta classe iremos criar todo o código de captura, iniciando pelas referências a API do Windows. Esta API é muito rica e possui centenas de métodos muito interessantes, mas vamos nos concentrar basicamente nas rotinas de captura de tela.
Só para conhecimento, todos os controle do Windows são tratados como uma janela e como tal, possuem um identificador único de janela, ou um WindowsHandle. Para acessarmos qualquer informação de uma janela ou controle usando a API do Windows, precisamos deste identificador.
Trabalhando com Interop
Para começar a nossa classe, vamos colocar todo o código de Interop com a API do Windows. Eu não vou explicar em detalhes o que cada método faz, mas você pode acessar um site muito bom chamado www.PInvoke.net que contém referências, explicações e exemplos para a API do Windows.
O código abaixo faz uma referência para um método da API do Windows e o deixa acessível para nós no C#:
1: public class Tela
2: {
3: [DllImport("user32.dll", EntryPoint = "GetDC")]
4: static extern IntPtr GetDC(IntPtr ptr);
5: [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
6: static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
7: [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
8: static extern IntPtr DeleteDC(IntPtr hDc);
9: [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")]
10: static extern IntPtr CreateCompatibleDC(IntPtr hdc);
11: [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
12: static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
13: [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
14: static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
15: [DllImport("gdi32.dll", EntryPoint = "BitBlt")]
16: static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, int RasterOp);
17: [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
18: static extern IntPtr GetDesktopWindow();
19: [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
20: static extern IntPtr DeleteObject(IntPtr hDc);
21:
22: const int SRCCOPY = 13369376;
23: public struct SIZE
24: {
25: public int cx;
26: public int cy;
27: }
Entendo um pouco a API do Windows
DC – Device Context, ou Dispositovo de Contexto, é um identificador para um objeto no Windows, por exemplo uma janela ou controle.
GetDC() – devolve o identificador para uma janela.
ReleaseDC() – restaura o identificador para o Windows. Isto é muito importante, todo identificador que for utilizado, deve ser devolvido.
DeleteDC() – quando nós criamos o DC, ao invés de pegá-lo do Windows, precisamos deletá-lo para liberar o recurso.
CreateCompatibleDC() – cria um DC, no nosso caso iremos criar para manipular o Bitmap.
CreateCompatibleBitmap() – cria um Bitmap compatível com um DC.
SelectObjec() – seleciona um objeto, no nosso caso vincula o DC ao Bitmap.
BitBlt() – faz a cópia do conteúdo de um identificador para outro, e no nosso caso, o identificador de destino será um Bitmap.
GetDesktopWindow() – retorna um identificador para a janela principal do Windows (Desktop).
DeleteObject() – deleta um objeto, liberando o recurso alocado.
Criando a rotina que captura a tela
Agora que já entendemos um pouco da API do Window, vamos criar o método da nossa classe que irá fazer as capturas de tela. Para isto vamos adicionar o código abaixo a nossa classe Tela.cs:
1: public static Bitmap RetornaImagemControle(IntPtr controle, Rectangle area)
2: {
3: SIZE size;
4: IntPtr hBitmap;
5:
6: IntPtr hDC = GetDC(controle);
7: IntPtr hMemDC = CreateCompatibleDC(hDC);
8:
9: size.cx = area.Width - area.Left;
10: size.cy = area.Bottom - area.Top;
11:
12: hBitmap = CreateCompatibleBitmap(hDC, size.cx, size.cy);
13:
14: if (hBitmap != IntPtr.Zero)
15: {
16: IntPtr hOld = (IntPtr)SelectObject(hMemDC, hBitmap);
17: BitBlt(hMemDC, 0, 0, size.cx, size.cy, hDC, 0, 0, SRCCOPY);
18: SelectObject(hMemDC, hOld);
19: DeleteDC(hMemDC);
20: ReleaseDC(GetDesktopWindow(), hDC);
21: Bitmap bmp = System.Drawing.Image.FromHbitmap(hBitmap);
22: DeleteObject(hBitmap);
23: return bmp;
24: }
25: else
26: {
27: return null;
28: }
29: }
O nosso método recebe como parâmetro um IntPtr, que pode representar qualquer controle ou janela do Windows, e recebe também uma estrutura que representa a área que iremos capturar, pois poderemos querer apenas uma parte do nosso controle transformado em uma imagem.
Iniciamos pegando os identificadores necessários (linhas 6 e 7) e em seguida calculamos o tamanho do nosso Bitmap de captura (linhas 9 e 10), para depois já criarmos este Bitmap (linha 12). Caso consigamos criar o Bitmap, então vamos fazer o vínculo do Bitmap (linha 16), e finalmente iremos executar o método que faz toda a mágica, ou seja, que copia o conteúdo do controle para o nosso Bitmap (linha 17).
Após isto iremos liberar os recursos utilizados (linhas 19 e 20), criamos finalmente o nosso Bitmap (linha 21) e liberamos o último recurso alocado. Ufa! Agora é só retornar o Bitmap com o resultado.
Utilizando a classe para capturar uma informação:
Agora que já temos a classe, vamos voltar para o nosso Windows Forms e mostrar como ele irá funcionar. Para isto adicione um controle Button e também um PictureBox, de maneira que a tela fique parecida com a seguinte:
Vamos adicionar o código para o botão “Capturar Desktop”:
1: private void btnCapturaDesktop_Click(object sender, EventArgs e)
2: {
3: imagemCapurada.Image = Tela.RetornaImagemControle(IntPtr.Zero, Screen.PrimaryScreen.WorkingArea);
4: }
Este código tem um truque bem interessante. Como eu falei no início deste post, iríamos capturar uma tela e toda tela no Windows possui um identificador, sendo assim, o nosso Desktop tem a identificação ZERO, ou seja, informando IntPtr.Zero como o nosso controle a ser capturado, iremos capturar o Desktop do Windows, e para conseguirmos capturar toda a tela, eu utilizei a propriedade PrimaryScreen.WorkingArea, que retorna o tamanho do nosso Desktop.
Para finalizar, iremos implementar um outro botão que irá capturar a nossa própria tela, para isto adicione um novo botão ao formulário e acrescente o código a seguir:
1: private void btnCapturaTela_Click(object sender, EventArgs e)
2: {
3: imagemCapurada.Image = Tela.RetornaImagemControle(this.Handle, new Rectangle(0,0,this.Width,this.Height));
4: }
Veja que agora estamos informando o identificador do nosso form e também o seu tamanho, mas você pode informar o identificador de qualquer controle e qualquer tamanho, e talvez capturar uma parte do controle original.
Veja como fica a tela capturada:
Conclusão:
Este post mostra duas coisas bem interessantes: como interoperar com a API do Windows e como capturar o conteúdo de uma janela do Windows.
Espero que tenham gostado e que principalmente seja útil no seu dia a dia.