Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Print Screen in C#

Rate me:
Please Sign up or sign in to vote.
4.50/5 (5 votes)
21 Aug 2009CPOL2 min read 97.4K   49   7
How to Capture the PrintScreen keypress and Save it to an Image

Introduction

I did some research and found a nice way to create a screen capture only to find that our employees continue to use the PrintScreen button. So, I needed a way to capture the PrintScreen keypress, which turned out to be trivial - after I learned the right functions to listen to.  

Background

Every time someone in our organization sends out an email with an embedded image, the server has to store that file for every user. If the email is replied to or forwarded, the embedded image is still sent unless the sender is thoughtful enough to remove it first. This results in a lot of storage space being used on the Mail Server, that is not necessary. 

Handling the PrintScreen button allows a developer to convert the screen capture to a file, which can then be attached to an email. The file format chosen for the image is typically smaller than the Microsoft Office embedded image, and attachments are not included in email replies. Hence, handling the PrintScreen keypress will result in the overall tax on the Mail Server to go down. 

Using the Code 

Two override routines must first be added to your main application:

C#
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
  if (((Keys)(int)msg.WParam) == Keys.PrintScreen) {
    ScreenCapture(Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
  }
  return base.ProcessCmdKey(ref msg, keyData);
}

protected override bool ProcessKeyEventArgs(ref Message msg) {
  if (((Keys)(int)msg.WParam) == Keys.PrintScreen) {
    ScreenCapture(Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
  }
  return base.ProcessKeyEventArgs(ref msg);
}

Notice that the base process is not blocked! If I want to continue on to Microsoft Paint and manually paste my screen capture, I can still do that, too.  Also, notice that I specify the Desktop to be the default location for saving the files (because, sadly, most of our employees don't really know how to navigate the Windows file structure). 

To handle the Screen Capture, I have a very generic routine called ScreenCapture which I initially learned from TeboScreen. My version accepts an initial directory for saving the screen shot and uses a simple BackgroundWorker to create the image, then tries to display the image after it has completed:

C#
public void ScreenCapture(string initialDirectory) {
  using (BackgroundWorker worker = new BackgroundWorker()) {
    Thread.Sleep(0);
    this.Refresh();
    worker.DoWork += delegate(object sender, DoWorkEventArgs e) {
      BackgroundWorker wkr = sender as BackgroundWorker;
      Rectangle bounds = new Rectangle(Location, Size);
      Thread.Sleep(300);
      Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
      using (Graphics g = Graphics.FromImage(bitmap)) {
        g.CopyFromScreen(Location, Point.Empty, bounds.Size);
      }
      e.Result = bitmap;
    };
    worker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e) {
      if (e.Error != null) {
        Exception err = e.Error;
        while (err.InnerException != null) {
          err = err.InnerException;
        }
        MessageBox.Show(err.Message, "Screen Capture", 
			MessageBoxButtons.OK, MessageBoxIcon.Stop);
      } else if (e.Cancelled == true) {
      } else if (e.Result != null) {
        if (e.Result is Bitmap) {
          Bitmap bitmap = (Bitmap)e.Result;
          using (SaveFileDialog dlg = new SaveFileDialog()) {
            dlg.Title = "Image Capture: Image Name, File Format, and Destination";
            dlg.FileName = "Screenshot";
            dlg.InitialDirectory = initialDirectory;
            dlg.DefaultExt = "jpg";
            dlg.AddExtension = true;
            dlg.Filter = "Jpeg Image 
	     (JPG)|*.jpg|PNG Image|*.png|GIF Image (GIF)|*.gif|Bitmap (BMP)|*.bmp" +
              "|EWM Image|*.emf|TIFF Image|*.tiff|Windows Metafile (WMF)|*.wmf|
	     Exchangable image file|*.exif";
            dlg.FilterIndex = 1;
            if (dlg.ShowDialog(this) == DialogResult.OK) {
              ImageFormat fmtStyle;
              switch (dlg.FilterIndex) {
                case 2: fmtStyle = ImageFormat.Jpeg; break;
                case 3: fmtStyle = ImageFormat.Gif; break;
                case 4: fmtStyle = ImageFormat.Bmp; break;
                case 5: fmtStyle = ImageFormat.Emf; break;
                case 6: fmtStyle = ImageFormat.Tiff; break;
                case 7: fmtStyle = ImageFormat.Wmf; break;
                case 8: fmtStyle = ImageFormat.Exif; break;
                default: fmtStyle = ImageFormat.Png; break;
              }
              bitmap.Save(dlg.FileName, fmtStyle);
              try {
                Process.Start(dlg.FileName);
              } catch (Exception) {
                try { // try IE
                  Process.Start("iexplore.exe", dlg.FileName);
                } catch (Exception) { }
              }
            }
          }
        }
      }
    };
    worker.RunWorkerAsync();
  }
}

History

This is my current, only version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer jp2code.net
United States United States
Looking to become a powerful developer one of these days.

Comments and Discussions

 
QuestionCustom application or third party solution? Pin
arc207210-Mar-11 13:12
arc207210-Mar-11 13:12 
AnswerRe: Custom application or third party solution? Pin
jp2code11-Mar-11 3:03
professionaljp2code11-Mar-11 3:03 
QuestionKey capture Pin
David6131-Aug-09 16:19
David6131-Aug-09 16:19 
AnswerRe: Key capture Pin
jp2code31-Aug-09 17:16
professionaljp2code31-Aug-09 17:16 
GeneralRe: Key capture Pin
David619-Sep-09 11:14
David619-Sep-09 11:14 
GeneralGraphics.CopyFromScreen Pin
The_Mega_ZZTer21-Aug-09 11:23
The_Mega_ZZTer21-Aug-09 11:23 
You may wish to look at Graphics.CopyFromScreen for grabbing a chunk of the screen to a Bitmap without needing to have the user press PrintScreen. It may or may not be useful for your program.

Incidentally, I just wrote a VB.NET function for simulating a PrintScreen keypress and then grabbing the image from the clipboard (sort of the opposite of what you're doing). This is not easy since there are a bunch of things that could mess it up, and it's VERY hackish, but it works. It's a useful alternative to Graphics.CopyFromScreen in some cases since Graphics.CopyFromScreen cannot capture transparent windows.

Here's the code for anyone who is interested; P/Invoke declarations not included. Make sure when you do add them that you remember to use the FULL declaration for the INPUT structure including MOUSEINPUT and HARDWAREINPUT, since MOUSEINPUT is needed to make the INPUT structure big enough that SendInput recognizes its size as valid.

Oh yeah and make sure you either set your thread this is running on as STA or run it on the main program thread only; Clipboard doesn't work right if you don't.

Reader Exercise: If this code fails, it will likely loop forever. Put some sort of timeout and/or retry functionality in.

    Public Shared Function PrintScreen() As Image<br />
        ' Save the current clipboard contents so we can restore it later<br />
        Dim o As DataObject = Clipboard.GetDataObject<br />
        Dim d As New Dictionary(Of String, Object) ' DataObjects appear to be linked to the active clipboard and their contents change when it changes, so we need to make a copy of it.<br />
        For Each s As String In o.GetFormats(False) ' Pass False to not get translatable formats, we want to only preserve actual formats on the clipboard<br />
            d.Add(s, o.GetData(s, False)) ' Pass False to not translate for the reason above<br />
        Next s<br />
        o = Nothing<br />
<br />
        Clipboard.Clear() ' Clear the clipboard so the presence of an existing  image doesn't mess up our function.<br />
<br />
        Dim i(4) As WinAPI.sINPUT ' 5 key presses<br />
        ' First send key up for Left Alt, if the user is holding it when we run this function it will cause Alt + PrintScreen which captures the current window instead of the screen, not what we want.<br />
        i(0).type = WinAPI.INPUT.KEYBOARD<br />
        i(0).ki.wVk = Keys.LMenu <br />
        i(0).ki.wScan = 0<br />
        i(0).ki.dwFlags = WinAPI.KEYEVENTF.KEYUP<br />
        i(0).ki.time = 0<br />
        i(0).ki.dwExtraInfo = IntPtr.Zero<br />
<br />
        ' Send key up for right alt too, same reason.<br />
        i(1) = i(0)<br />
        i(1).ki.wVk = Keys.RMenu<br />
<br />
        ' Send key up for printscreen in case it is being held down, otherwise our key press might not do anything.<br />
        i(2) = i(0)<br />
        i(2).ki.wVk = Keys.PrintScreen<br />
<br />
        ' Send key down for printscreen<br />
        i(3) = i(2)<br />
        i(3).ki.dwFlags = 0<br />
<br />
        ' Send key up for printscreen again<br />
        i(4) = i(2)<br />
<br />
        ' Inject key presses into the input buffer<br />
        If WinAPI.SendInput(i.Length, i, Runtime.InteropServices.Marshal.SizeOf(i(0))) _<br />
            < i.Length Then<br />
<br />
            ' If you get a parameter error, check the INPUT struct to ensure you put in MOUSEINPUT and HARDWAREINPUT, otherwise the value SizeOf returns will not be what SendInput likes.<br />
            Throw New ComponentModel.Win32Exception<br />
        End If<br />
        <br />
        Dim im As Image = Nothing<br />
        While im Is Nothing<br />
            Application.DoEvents() ' If we don't do this the PrintScreen will take several seconds to actually work, assuming it ever does at all!  Guess the active window needs to be pumping messages in order for PrintScreen to work properly.<br />
            Threading.Thread.Sleep(10) ' Always sleep a little each iteration in a loop of unknown length (or very large length)<br />
<br />
            Try<br />
                If Not Clipboard.ContainsImage Then<br />
                    Continue While<br />
                End If<br />
<br />
                im = Clipboard.GetImage<br />
            Catch ex As ExternalException<br />
                ' If we try to access the image while Windows is trying to place it on the clipboard, we trigger this exception.<br />
                Continue While<br />
            End Try<br />
        End While<br />
<br />
        ' Restore the old clipboard contents<br />
        o = New DataObject<br />
        For Each x As KeyValuePair(Of String, Object) In d<br />
            o.SetData(x.Key, False, x.Value) ' False means don't translate into other data types, we want to restore the clipboard exactly as we found it.<br />
        Next x<br />
        Clipboard.SetDataObject(o, True) ' Make sure to pass true otherwise when our program quits .NET will helpfully clear the clipboard.<br />
<br />
        Return im<br />
    End Function

GeneralRe: Graphics.CopyFromScreen Pin
jp2code21-Aug-09 11:32
professionaljp2code21-Aug-09 11:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.