I know I can get the screenshot of the entire screen using Graphics.CopyFromScreen(). However, what if I just want the screenshot of a specific application?
You could look into P/Invoking the win32 way of doing this, an article to this effect... sort of.
Basically, go through the trouble of setting up a DC to a bitmap and send WM_PRINT to the application window in question. Its pretty nasty, all told, but may work for you.
Functions you may need: SendMessage, GetDC, CreateCompatibleBitmp, and SelectObject.
I can't say I've ever done this before, but this is how I'd attack the problem. (Well, I'd probably do it in pure C but still; roughly the way I'd attack it).
Here's some code to get you started:
public void CaptureApplication(string procName)
{
var proc = Process.GetProcessesByName(procName)[0];
var rect = new User32.Rect();
User32.GetWindowRect(proc.MainWindowHandle, ref rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(bmp);
graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
bmp.Save("c:\\tmp\\test.png", ImageFormat.Png);
}
private class User32
{
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
}
It works, but needs improvement:
- You may want to use a different mechanism to get the process handle (or at least do some defensive coding)
- If your target window isn't in the foreground, you'll end up with a screenshot that's the right size/position, but will just be filled with whatever is in the foreground (you probably want to pull the given window into the foreground first)
- You probably want to do something other than just save the bmp to a temp directory
Based on Alconja's answer, I made a few improvements:
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hWnd);
private const int SW_RESTORE = 9;
[DllImport("user32.dll")]
private static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
public Bitmap CaptureApplication(string procName)
{
Process proc;
// Cater for cases when the process can't be located.
try
{
proc = Process.GetProcessesByName(procName)[0];
}
catch (IndexOutOfRangeException e)
{
return null;
}
// You need to focus on the application
SetForegroundWindow(proc.MainWindowHandle);
ShowWindow(proc.MainWindowHandle, SW_RESTORE);
// You need some amount of delay, but 1 second may be overkill
Thread.Sleep(1000);
Rect rect = new Rect();
IntPtr error = GetWindowRect(proc.MainWindowHandle, ref rect);
// sometimes it gives error.
while (error == (IntPtr)0)
{
error = GetWindowRect(proc.MainWindowHandle, ref rect);
}
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics.FromImage(bmp).CopyFromScreen(rect.left,
rect.top,
0,
0,
new Size(width, height),
CopyPixelOperation.SourceCopy);
return bmp;
}
The PrintWindow win32 api will capture a window bitmap even if the window is covered by other windows or if it is off screen:
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);
public static Bitmap PrintWindow(IntPtr hwnd)
{
RECT rc;
GetWindowRect(hwnd, out rc);
Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();
PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
gfxBmp.Dispose();
return bmp;
}