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
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.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace MySnippingTool
{
class Canvas : Form
{
Point startPos;
Point currentPos;
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();
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.
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)
{
Image dstImg = srcImg.Clone() as Image;
Graphics g = Graphics.FromImage(dstImg);
Pen pBorder = new Pen(color, width)
{
Alignment = PenAlignment.Center
};
g.DrawRectangle(pBorder, 0, 0, dstImg.Width - 1, dstImg.Height - 1);
pBorder.Dispose();
g.Save();
g.Dispose();
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.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
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;
}
public IntPtr Handle;
public short HotkeyID { get; private set; }
public void RegisterGlobalHotKey(int hotkey, int modifiers, IntPtr handle)
{
UnregisterGlobalHotKey();
this.Handle = handle;
RegisterGlobalHotKey(hotkey, modifiers);
}
public void RegisterGlobalHotKey(int hotkey, int modifiers)
{
UnregisterGlobalHotKey();
try
{
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());
if (!RegisterHotKey(this.Handle, HotkeyID, (uint)modifiers, (uint)hotkey))
throw new Exception("Unable to register hotkey. Error: " +
Marshal.GetLastWin32Error().ToString());
}
catch (Exception ex)
{
Dispose();
Console.WriteLine(ex);
}
}
public void UnregisterGlobalHotKey()
{
if (this.HotkeyID != 0)
{
UnregisterHotKey(this.Handle, HotkeyID);
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”.
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:
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.
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>