Recently I needed a way to have an app take a screen shot of itself and save it to a directory. I only wanted the currently focused screen to be captured. I looked around on the web, and found lots of crazy interop scripts to handle doing a screen shots.
But it just didn’t seem right. *I* can screen capture by just hitting Alt->Print Screen, why can’t my app? I knew there would be some interop involved, but certainly it doesn’t have to be that hard, does it?
Turns out, it really doesn’t. By sending some Keyboard events using the Win32 SendInput() via User32.dll, and working with the clipboard data in the normal fashion, it’s fairly easy to get it all working. First, you need the capture and save code:
private void CaptureAndSave()
{
uint intReturn = 0;
NativeWIN32.INPUT structInput;
structInput = new NativeWIN32.INPUT();
structInput.type = (uint)1;
structInput.ki.wScan = 0;
structInput.ki.time = 0;
structInput.ki.dwFlags = 0;
structInput.ki.dwExtraInfo = 0;
//Press Alt Key
structInput.ki.wVk = (ushort)NativeWIN32.VK.MENU;
intReturn = NativeWIN32.SendInput((uint)1, ref structInput, Marshal.SizeOf(structInput));
// Key down the actual key-code
structInput.ki.wVk = (ushort)NativeWIN32.VK.SNAPSHOT;//vk;
intReturn = NativeWIN32.SendInput((uint)1, ref structInput, Marshal.SizeOf(structInput));
// Key up the actual key-code
structInput.ki.dwFlags = NativeWIN32.KEYEVENTF_KEYUP;
structInput.ki.wVk = (ushort)NativeWIN32.VK.SNAPSHOT;//vk;
intReturn = NativeWIN32.SendInput((uint)1, ref structInput, Marshal.SizeOf(structInput));
//Keyup Alt
structInput.ki.dwFlags = NativeWIN32.KEYEVENTF_KEYUP;
structInput.ki.wVk = (ushort)NativeWIN32.VK.MENU;
intReturn = NativeWIN32.SendInput((uint)1, ref structInput, Marshal.SizeOf(structInput));
IDataObject data = Clipboard.GetDataObject();
if (data.GetDataPresent(DataFormats.Bitmap))
{
Image image = (Image)data.GetData(DataFormats.Bitmap,true);
image.Save("image" + ".bmp",System.Drawing.Imaging.ImageFormat.Bmp);
image.Save("image" + ".jpg",System.Drawing.Imaging.ImageFormat.Jpeg);
image.Save("image" + ".gif",System.Drawing.Imaging.ImageFormat.Gif);
}
else
{
Console.WriteLine("The Data In Clipboard is not in an image format");
}
}
As you can see, all the heavy lifting appears to be in a class called NativeWIN32. This class is nothing more than an internal wrapper class around the User32.dll functions. I’ve included the full suite of VK enums since I already had it in my code:
public class NativeWIN32
{
public const ushort KEYEVENTF_KEYUP = 0x0002;
public enum VK : ushort
{
SHIFT = 0x10,
CONTROL = 0x11,
MENU = 0x12,
ESCAPE = 0x1B,
BACK = 0x08,
TAB = 0x09,
RETURN = 0x0D,
PRIOR = 0x21,
NEXT = 0x22,
END = 0x23,
HOME = 0x24,
LEFT = 0x25,
UP = 0x26,
RIGHT = 0x27,
DOWN = 0x28,
SELECT = 0x29,
PRINT = 0x2A,
EXECUTE = 0x2B,
SNAPSHOT = 0x2C,
INSERT = 0x2D,
DELETE = 0x2E,
HELP = 0x2F,
NUMPAD0 = 0x60,
NUMPAD1 = 0x61,
NUMPAD2 = 0x62,
NUMPAD3 = 0x63,
NUMPAD4 = 0x64,
NUMPAD5 = 0x65,
NUMPAD6 = 0x66,
NUMPAD7 = 0x67,
NUMPAD8 = 0x68,
NUMPAD9 = 0x69,
MULTIPLY = 0x6A,
ADD = 0x6B,
SEPARATOR = 0x6C,
SUBTRACT = 0x6D,
DECIMAL = 0x6E,
DIVIDE = 0x6F,
F1 = 0x70,
F2 = 0x71,
F3 = 0x72,
F4 = 0x73,
F5 = 0x74,
F6 = 0x75,
F7 = 0x76,
F8 = 0x77,
F9 = 0x78,
F10 = 0x79,
F11 = 0x7A,
F12 = 0x7B,
OEM_1 = 0xBA, // ',:' for US
OEM_PLUS = 0xBB, // '+' any country
OEM_COMMA = 0xBC, // ',' any country
OEM_MINUS = 0xBD, // '-' any country
OEM_PERIOD = 0xBE, // '.' any country
OEM_2 = 0xBF, // '/?' for US
OEM_3 = 0xC0, // '`~' for US
MEDIA_NEXT_TRACK = 0xB0,
MEDIA_PREV_TRACK = 0xB1,
MEDIA_STOP = 0xB2,
MEDIA_PLAY_PAUSE = 0xB3,
LWIN =0x5B,
RWIN =0x5C
}
public struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public long time;
public uint dwExtraInfo;
};
[StructLayout(LayoutKind.Explicit,Size=28)]
public struct INPUT
{
[FieldOffset(0)] public uint type;
[FieldOffset(4)] public KEYBDINPUT ki;
};
[DllImport("user32.dll")]
public static extern uint SendInput(uint nInputs, ref INPUT pInputs, int cbSize);
}
And viola! Quick, easy screen capture code.
The native calls to SendInput result in async calls which means that when you reach
IDataObject data = clipboard.GetDataObject();
if(data.GetDataPresent(DataFormats.Bitmap))
the new screenshot has not yet ready.
What have you done to wait for the call to SendInput to complete? Thread.Sleep()?
I didn’t have that problem. The tricky part was not calling the method until the thing I wanted screen shotted was ready. But one it was, the call had no problem getting the data to the clipboard in time.
That might not be true if my machine was under heavy load or something similar, but for what I was using it for it worked fairly seamlessly.
I suppose you could do a Thread.Sleep() though if you were having that issue but just like I don’t expect it to take long when I hit Ctrl-C, I don’t expect the Print Screen to take very long either.
Thanks for this article. I had a go at wrapping SendInput and the structures myself, but not knowing any C++ it was a bit of a doomed undertaking…
what if i want to use the same code and not print the focused window but the entire desktop ? Is there a key/field i can simply change ?
waht if i want to use the same code and not print the focused window but the entire desktopn screen shot ? can i change some value for this in your code ?
Also, to take screenshots of the entire desktop instead of just the focused window, simply comment out the code which keys down and keys up the Alt Key.
What is Marshal.SizeOf(structInput)
I had a similar problem. Using a sleep if problematic because system can vary. I used a listener on the clipboard so that the action for the image was only called when the image was ready. Here’s the above code modified.
-Michael G. Butler
michael.g.butler@gmail.com
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
using System.Collections;
using PowerPoint;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.Office.Core;
using System.Globalization;
using System.Reflection;
public partial class Form1 : Form
{
// Assembly anAssembly;
[DllImport(“User32.dll”)]
protected static extern int SetClipboardViewer(int hWndNewViewer);
[DllImport(“User32.dll”, CharSet = CharSet.Auto)]
public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport(“user32.dll”, CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
// HOT key dlls
[DllImport(“user32.dll”)]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
[DllImport(“user32.dll”)]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
IntPtr nextClipboardViewer;
public Form1()
{
InitializeComponent();
Clipboard.Clear();
nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
// Alt =1 , Ctrl =2, Shift =4, Win =0
//register hot Ctrl , Alt, and ‘z’
bool success = Form1.RegisterHotKey(this.Handle, this.GetType().GetHashCode(), 2 ,(int)’Z’);
if (success)
{
Console.WriteLine(“Alt-z hot key successfully registered”);
}
else
MessageBox.Show(“Could not register Hotkey – there is probably a conflict. “, “”, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
/// Clean up any resources being used.
protected override void Dispose(bool disposing)
{
ChangeClipboardChain(this.Handle, nextClipboardViewer);
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
protected override void WndProc(ref System.Windows.Forms.Message m)
{
// defined in winuser.h
const int WM_DRAWCLIPBOARD = 0x308;
const int WM_CHANGECBCHAIN = 0x030D;
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
DisplayClipboardData();
SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
case WM_CHANGECBCHAIN:
if (m.WParam == nextClipboardViewer)
nextClipboardViewer = m.LParam;
else
SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
case 0x0312:
CaptureAndSave();
break;
default:
base.WndProc(ref m);
break;
}
}
void DisplayClipboardData()
{
try
{
System.Windows.Forms.IDataObject iData = new DataObject();
iData = Clipboard.GetDataObject();
if (iData.GetDataPresent(DataFormats.Rtf))
Console.WriteLine ((string)iData.GetData(DataFormats.Rtf));
else if (iData.GetDataPresent(DataFormats.Text))
Console.WriteLine ((string)iData.GetData(DataFormats.Text));
else if (iData.GetDataPresent(DataFormats.Bitmap))
{
Console.WriteLine(“retrieving image”);
Bitmap aBitMap = (Bitmap)iData.GetData(DataFormats.Bitmap);
}
else
Console.WriteLine(“[Clipboard data is not RTF or ASCII Text]”);
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
private void CaptureAndSave()
{
uint intReturn = 0;
NativeWIN32.INPUT structInput;
structInput = new NativeWIN32.INPUT();
structInput.type = (uint)1;
structInput.ki.wScan = 0;
structInput.ki.time = 0;
structInput.ki.dwFlags = 0;
structInput.ki.dwExtraInfo = 0;
//Press Alt Key
structInput.ki.wVk = (ushort)NativeWIN32.VK.MENU;
intReturn = NativeWIN32.SendInput((uint)1, ref structInput, Marshal.SizeOf(structInput));
// Key down the actual key-code
structInput.ki.wVk = (ushort)NativeWIN32.VK.SNAPSHOT;
//vk;
intReturn = NativeWIN32.SendInput((uint)1, ref structInput, Marshal.SizeOf(structInput));
// Key up the actual key-code
structInput.ki.dwFlags = NativeWIN32.KEYEVENTF_KEYUP;
structInput.ki.wVk = (ushort)NativeWIN32.VK.SNAPSHOT;
//vk;
intReturn = NativeWIN32.SendInput((uint)1, ref structInput, Marshal.SizeOf(structInput));
//Keyup Alt
structInput.ki.dwFlags = NativeWIN32.KEYEVENTF_KEYUP;
structInput.ki.wVk = (ushort)NativeWIN32.VK.MENU;
intReturn = NativeWIN32.SendInput((uint)1, ref structInput, Marshal.SizeOf(structInput));
}
}
THANKYOU So much!!! Cheers!
-Edmond
This is genius. Thank you very much :)
– Abbie
just wanted to say thanks for this old but great piece of code :)
Nice code. Tried using it to simulate pasting, But couldn’t get it to work reliably in Vista. Any idea?
I had the problem with Marshal too. I believe it’s part of System.Runtime.InteropServices. At least in my Visual C#. If do the following it compiles fine:
using System.Runtime.InteropServices;
Thank you so much Cory. All I had to do to have a very powerful helper class to handle all sorts of key combinations that would also include characters was to add to NativeWIN32.cs the following function
[DllImport(“user32.dll”)]
public static extern short VkKeyScan(char ch);
what is exactly your application doing?
can you please put here a running project?
I know this is 4 years later but just wanted to say thanks. I used this to check and set NumLock status. For C#.NET, this was a clearer and easier to implement solution than some of the others out there.
Hi there,
just stumbled on this post of yours, needed the keyCode for PrintScreen, so thanks for that :)
Also, as per request, I’m commenting because the sources look awful :D
Cheers
Hi,
Whenever i open my window, I want to copy the highlighted text on the any active application and paste it on the text box in my window.
How to do this using SendInput in C#?
Please help
using SendKeys.SendWait(“{PRTSC}”);