Click here to Skip to main content
15,902,894 members
Articles / Desktop Programming / Windows Forms
Tip/Trick

Custom Snipping Tool using C# WinForms

Rate me:
Please Sign up or sign in to vote.
4.67/5 (4 votes)
6 May 2015CPOL2 min read 41.4K   4.9K   13   5
This tool helps a lot while capturing more number of snapshots. Example: Developers can use this tool to capture the UTR and save all snaps at the end to a Word document or save as individual images.

Introduction

In Windows, we all know what Snipping Tool does and how much it is helpful in our day to day work. As I am a developer, I extensively use it for capturing UTR (Unit Test Results) and related things. But when we are capturing more number of snaps, we feel that it is difficult to switch between different windows, capture and paste it in Word document. So I thought of creating an extended snipping tool that captures and buffers the snaps without switching between Windows and at the end of my work, I will save all snaps into a Word document or as individual images to a folder. So let's see the usage of tool and how it works.

Using the Tool

Sample Image - maximum width is 600 pixels

Step #1: Set the boundaries of snipping area. By default, it is set to full screen.

Step #2: Take the snaps. The major advantage of this tool is, we do not need to switch between snipping tool and other windows. Just open your target window and press “Ctrl+Shift+Z” to capture the snipping area. Every time you press “Ctrl+Shift+Z”, a snap will be taken and stores in buffer. You can see the preview of snaps in snipping tool window.

Step #3: Once you are done with your work, select File > SaveAsWord/SaveAsImages to save all snaps to a Word document or as individual images to selected folder.

That’s it. Isn’t it so simple? :-)

Using the Code

Here is the explanation about the code. If anyone wants to extend this tool further for your own needs, this will help you.

Code snippet for taking the snipping area bounds. Create a form with no border and window state as maximized. Set opacity to give an impression of original snipping tool behavior. While dragging mouse pointer on window, create a rectangle and up on mouse up event, close the window and return the selected area coordinates to the parent. If ‘Esc’ key is pressed, simply close the window.

C#
using System;
using System.Drawing;
using System.Windows.Forms;

namespace MySnippingTool
{
    class Canvas : Form
    {
        Point startPos;      // mouse-down position
        Point currentPos;    // current mouse position
        bool drawing;

        public Canvas()
        {
            this.WindowState = FormWindowState.Maximized;
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            this.BackColor = Color.White;
            this.Opacity = 0.75;
            this.Cursor = Cursors.Cross;
            this.MouseDown += Canvas_MouseDown;
            this.MouseMove += Canvas_MouseMove;
            this.MouseUp += Canvas_MouseUp;
            this.Paint += Canvas_Paint;
            this.KeyDown += Canvas_KeyDown;
            this.DoubleBuffered = true;
        }

        private void Canvas_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
                this.Close();
            }
        }

        public Rectangle GetRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X, currentPos.X),
                Math.Min(startPos.Y, currentPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

        private void Canvas_MouseDown(object sender, MouseEventArgs e)
        {
            currentPos = startPos = e.Location;
            drawing = true;
        }

        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            currentPos = e.Location;
            if (drawing) this.Invalidate();
        }

        private void Canvas_MouseUp(object sender, MouseEventArgs e)
        {
            this.DialogResult = System.Windows.Forms.DialogResult.OK;
            this.Close();
        }

        private void Canvas_Paint(object sender, PaintEventArgs e)
        {
            if (drawing) e.Graphics.DrawRectangle(Pens.Red, GetRectangle());
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // Canvas
            // 
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Name = "Canvas";
            this.ShowInTaskbar = false;
            this.ResumeLayout(false);
        }
    }
}

Here is the class responsible for capturing the snapshots. It uses CopyFromScreen method of Graphics class to capture the screen shot. By default, canvasBounds field is set to default so that the entire screen will be captured. If we explicitly set the bounds, then the snap will be taken in specified area. Set border for the snap and return it to the caller.

C#
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace MySnippingTool
{
    class ScreenCapture
    {
        private Rectangle canvasBounds = Screen.GetBounds(Point.Empty);

        public ScreenCapture()
        {
            SetCanvas();
        }

        public Bitmap GetSnapShot()
        {
            using (Image image = new Bitmap(canvasBounds.Width, canvasBounds.Height))
            {
                using (Graphics graphics = Graphics.FromImage(image))
                {
                    graphics.CopyFromScreen(new Point
                    (canvasBounds.Left, canvasBounds.Top), Point.Empty, canvasBounds.Size);
                }
                return new Bitmap(SetBorder(image, Color.Black, 1));
            }
        }

        private Image SetBorder(Image srcImg, Color color, int width)
        {
            // Create a copy of the image and graphics context
            Image dstImg = srcImg.Clone() as Image;
            Graphics g = Graphics.FromImage(dstImg);
            
            // Create the pen
            Pen pBorder = new Pen(color, width)
            {
                Alignment = PenAlignment.Center
            };

            // Draw
            g.DrawRectangle(pBorder, 0, 0, dstImg.Width - 1, dstImg.Height - 1);

            // Clean up
            pBorder.Dispose();
            g.Save();
            g.Dispose();

            // Return
            return dstImg;
        }

        public void SetCanvas()
        {
            using (Canvas canvas = new Canvas())
            {
                if (canvas.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    this.canvasBounds = canvas.GetRectangle();
                }
            }
        }        
    }
}

Now let’s see how this tool is able to capture the snaps, when the other windows are active. This is happening by registering the global hot key for our application using User32 assembly. For more details, please refer to this link.

The below code snippet was taken from here.

C#
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

/// <summary> This class allows you to manage a hotkey </summary>
public class GlobalHotkey : IDisposable
{
    [DllImport("user32", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool RegisterHotKey(IntPtr hwnd, int id, uint fsModifiers, uint vk);
    [DllImport("user32", SetLastError = true)]
    public static extern int UnregisterHotKey(IntPtr hwnd, int id);
    [DllImport("kernel32", SetLastError = true)]
    public static extern short GlobalAddAtom(string lpString);
    [DllImport("kernel32", SetLastError = true)]
    public static extern short GlobalDeleteAtom(short nAtom);

    public const int MOD_ALT = 1;
    public const int MOD_CONTROL = 2;
    public const int MOD_SHIFT = 4;
    public const int MOD_WIN = 8;

    public const int WM_HOTKEY = 0x312;

    public GlobalHotkey()
    {
        this.Handle = Process.GetCurrentProcess().Handle;
    }

    /// <summary>Handle of the current process</summary>
    public IntPtr Handle;

    /// <summary>The ID for the hotkey</summary>
    public short HotkeyID { get; private set; }

    /// <summary>Register the hotkey</summary>
    public void RegisterGlobalHotKey(int hotkey, int modifiers, IntPtr handle)
    {
        UnregisterGlobalHotKey();
        this.Handle = handle;
        RegisterGlobalHotKey(hotkey, modifiers);
    }

    /// <summary>Register the hotkey</summary>
    public void RegisterGlobalHotKey(int hotkey, int modifiers)
    {
        UnregisterGlobalHotKey();

        try
        {
            // use the GlobalAddAtom API to get a unique ID (as suggested by MSDN)
            string atomName = Thread.CurrentThread.ManagedThreadId.ToString("X8") + 
                              this.GetType().FullName;
            HotkeyID = GlobalAddAtom(atomName);
            if (HotkeyID == 0)
                throw new Exception("Unable to generate unique hotkey ID. Error: " + 
                                     Marshal.GetLastWin32Error().ToString());

            // register the hotkey, throw if any error
            if (!RegisterHotKey(this.Handle, HotkeyID, (uint)modifiers, (uint)hotkey))
                throw new Exception("Unable to register hotkey. Error: " + 
                                     Marshal.GetLastWin32Error().ToString());
        }
        catch (Exception ex)
        {
            // clean up if hotkey registration failed
            Dispose();
            Console.WriteLine(ex);
        }
    }

    /// <summary>Unregister the hotkey</summary>
    public void UnregisterGlobalHotKey()
    {
        if (this.HotkeyID != 0)
        {
            UnregisterHotKey(this.Handle, HotkeyID);
            // clean up the atom list
            GlobalDeleteAtom(HotkeyID);
            HotkeyID = 0;
        }
    }

    public void Dispose()
    {
        UnregisterGlobalHotKey();
    }
}

The important method in the main window class is WndProc. It acts like a listener for the Hot Key “Ctrl+Shift+Z”.

C#
protected override void WndProc(ref Message m)
{
	if (m.Msg == 0x0312 && m.WParam.ToInt32() == TakeSnapHotKey.HotkeyID)
	{
		TakeSnap();
	}
	base.WndProc(ref m);
}

The complete code behind for main window is:

C#
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MySnippingTool
{
    public partial class MainWindow : Form
    {
        private GlobalHotkey TakeSnapHotKey;
        private ScreenCapture objScreenCapture;
        private int snapCount;
        private List<bitmap> snaps;

        public MainWindow()
        {
            InitializeComponent();

            objScreenCapture = new ScreenCapture();
            snapCount = 0;
            snaps = new List<bitmap>();

            TakeSnapHotKey = new GlobalHotkey();
            TakeSnapHotKey.RegisterGlobalHotKey((int)Keys.Z, 
                GlobalHotkey.MOD_CONTROL | GlobalHotkey.MOD_SHIFT,
                this.Handle);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x0312 && m.WParam.ToInt32() == TakeSnapHotKey.HotkeyID)
            {
                TakeSnap();
            }
            base.WndProc(ref m);
        }

        private void takeSnapToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TakeSnap();            
        }

        private void setBoundsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            objScreenCapture.SetCanvas();
        }

        private void TakeSnap()
        {
            var snap = objScreenCapture.GetSnapShot();
            snaps.Add(snap);
            AddToPreview(snap);
        }

        private void AddToPreview(Bitmap snap)
        {
            imageList1.Images.Add(snap);
            listView1.Items.Add(new ListViewItem
            ("Snap_" + (++snapCount), imageList1.Images.Count - 1)).EnsureVisible();            
        }

        private void saveAsImagesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Utility.SaveAsImages(snaps);
        }

        private void saveAsWordToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Utility.SaveAsWord(snaps);
        }

        private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
        {
            TakeSnapHotKey.Dispose();
        }
    }
}</bitmap></bitmap>

Here are the utility methods to save the screenshots.
Note: Add reference to Microsoft Word Object Library from COM.

C#
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace MySnippingTool
{
    class Utility
    {
        public static void SaveAsImages(List<bitmap> images)
        {
            using (FolderBrowserDialog dialog = new FolderBrowserDialog())
            {
                if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    int count = 1;
                    foreach (Bitmap img in images)
                    {
                        img.Save(dialog.SelectedPath + "\\Snap_" + (count++) + ".bmp");
                    }
                }
            }
        }

        public static void SaveAsWord(List<bitmap> images)
        {
            using (SaveFileDialog dialog = new SaveFileDialog())
            {
                dialog.Filter = "Word File | *.doc";
                dialog.DefaultExt = "doc";
                if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    Microsoft.Office.Interop.Word.Application app;
                    Microsoft.Office.Interop.Word.Document doc;
                    object miss = System.Reflection.Missing.Value;

                    app = new Microsoft.Office.Interop.Word.Application();
                    app.Visible = false;

                    doc = app.Documents.Add(ref miss, ref miss, ref miss, ref miss);

                    foreach (var img in images)
                    {
                        object start = doc.Content.End - 1;
                        object end = doc.Content.End;
                        Microsoft.Office.Interop.Word.Range rng = doc.Range(ref start, ref end);
                        Clipboard.SetDataObject(img, true);
                        rng.Paste();
                    }

                    doc.SaveAs2(dialog.FileName);

                    doc.Close(ref miss, ref miss, ref miss);
                    app.Quit(ref miss, ref miss, ref miss);
                }
            }
        }
    }    
}</bitmap></bitmap>

License

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


Written By
Software Developer (Senior)
India India
Working as a Senior Developer in an MNC with 3+ years of experience in .Net related technologies. Passionate about programming and software architecture.

Comments and Discussions

 
SuggestionTransparency Pin
varandas1-May-21 8:13
varandas1-May-21 8:13 
QuestionArea not working on 2 monitors only on main. Pin
Member 141282446-Feb-19 1:55
Member 141282446-Feb-19 1:55 
QuestionEraser button Like Snip Tool Pin
Member 133960226-Sep-17 1:22
Member 133960226-Sep-17 1:22 
QuestionTime and date Pin
Member 1313673818-Apr-17 15:22
Member 1313673818-Apr-17 15:22 
PraiseAwesome! Pin
Member 1306381316-Mar-17 7:08
Member 1306381316-Mar-17 7:08 

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.