参考了网上的一些代码写了个抓屏程序,主要是根据自己的需求慢慢写的。
自己用着感觉也还不错,满足一般的要求应该是足够了,在这里共享一下程序和代码。
首次原创,转载请注明来源。
(注:运行程序需要Framework 2.0,项目是用VS2008建的)
关于程序:
下载
程序图标(用的QQ的图标)只显示在系统栏中,可通过启动程序、左键单击任务栏中的程序图标或输入设置的快捷键(默认为PrintScreen)开始抓屏。
用鼠标左键确定抓屏区域,确定好区域之后双击左键保存抓取区域的图片。
单击右键或点击ESC键取消抓屏操作并将程序隐藏在任务栏中。
右键单击任务栏中的程序图标弹出程序菜单,可退出程序,进行程序的设置和查看关于对话框。
关于代码:
下载
抓屏:
本程序主要功能是抓屏,先说说抓屏。
抓屏主窗体为FormCatcher,包含的控件主要是一个用于保存图片和画图的PictureBox,然后是一些成员变量。
得到屏幕图片的主要原理是通过 System.Drawing.Graphics 类的成员函数public void CopyFromScreen(Point upperLeftSource, Point upperLeftDestination, Size blockRegionSize)将屏幕复制到System.Drawing.Bitmap类的实例中,然后怎么操作就随便了。
用于拷图操作的私有函数:
private Bitmap GetScreenImage(Point upperLeftSource, Point upperLeftDestination, Size blockRegionSize)
{
Bitmap bmp = new Bitmap(blockRegionSize.Width, blockRegionSize.Height);
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(upperLeftSource, upperLeftDestination, blockRegionSize);
g.Dispose();
return bmp;
}
初始化抓屏操作的函数,用PictureBox控件pbCatchArea覆盖全屏并将其背景图设为拷屏得到的图片,然后初始化抓屏状态_csCurrentStep,显示激活抓屏窗体:
程序主要的代码是在Mouse的MouseDown,MouseMove和MouseDown三个事件中根据不同的抓屏状态_csCurrentStep和鼠标点击时的参数调整各个成员变量的值,然后在pbCatchArea的Paint事件中根据这些成员变量的值进行画图或写字符串的操作,最后在MouseDoubleClick中保存抓屏区域的图。
有必要把几个重要的窗体类的成员变量说明一下:
private Point _pLeftTopPoint = new Point(0, 0), _pRightBottomPoint = new Point(0, 0);
private Point _pSizedPoint = new Point(0, 0), _pCurrentPoint = new Point(0, 0);
private CatchSatus _csCurrentStep = CatchSatus.Start;
private Position _psSizedPosition = Position.Outside;
private DateTime _dtMouseClickTime = System.DateTime.Now;
private TimeSpan MOUSE_DOUBLE_CLICK_MAX_SPAN = new TimeSpan(0, 0, 0, 0, 300);
首先是4个点,分别是确定抓屏区域时的左上点_pLeftTopPoint、右下点_pRightBottomPoint、移动或调整抓屏区域时的参照点_pSizedPoint和鼠标所在的当前点_pCurrentPoint。前两个点用于确定抓屏区域;_pSizedPoint的值是调整抓屏区域时鼠标左键按下时的点坐标,根据此点和鼠标的移动调整抓屏区域;_pCurrentPoint用于确定在决定抓屏区域时提示字符串应该写在屏幕的左上角还是右上角。
枚举变量_csCurrentStep保存抓屏操作的当前状态。
枚举变量_psSizedPosition用于标示鼠标与已经确定了的抓屏区域的相对位置,在调整抓屏区域时感觉此变量进行不同的操作。
_dtMouseClickTime和MOUSE_DOUBLE_CLICK_MAX_SPAN用来区分MouseDown和MouseDoubleClick事件。
MouseDown事件,单击左键调整成员变量并调用pbCatchArea.Invalidate()激活pbCatchArea的Paint事件,单击右键则隐藏程序:
Code
private void pbCatchArea_MouseDown(object sender, MouseEventArgs e)
{
//双击则返回
if ((System.DateTime.Now - _dtMouseClickTime) <= MOUSE_DOUBLE_CLICK_MAX_SPAN)
{
return;
}
if (e.Button == MouseButtons.Left)
{
switch (_csCurrentStep)
{
case CatchSatus.Start:
_csCurrentStep = CatchSatus.Pending;
break;
case CatchSatus.Pending:
_pRightBottomPoint = new Point(e.X, e.Y);
_csCurrentStep = CatchSatus.WaitingSave;
break;
case CatchSatus.WaitingSave:
_pSizedPoint = new Point(e.X, e.Y);
_psSizedPosition = Compare(_pSizedPoint, new Rectangle(_pLeftTopPoint.X, _pLeftTopPoint.Y, _pRightBottomPoint.X - _pLeftTopPoint.X, _pRightBottomPoint.Y - _pLeftTopPoint.Y));
_csCurrentStep = CatchSatus.Resizing;
break;
}
_dtMouseClickTime = System.DateTime.Now;
pbCatchArea.Invalidate();
}
else
{
this.Hide();
}
}
MouseMove事件,也是处理逻辑然后激活pbCatchArea的Paint事件:
Code
private void pbCatchArea_MouseMove(object sender, MouseEventArgs e)
{
_pCurrentPoint = new Point(e.X, e.Y);
switch (_csCurrentStep)
{
case CatchSatus.Start:
_pLeftTopPoint = new Point(e.X, e.Y);
_pRightBottomPoint = new Point(e.X, e.Y);
break;
case CatchSatus.Pending:
_pRightBottomPoint = new Point(e.X, e.Y);
break;
case CatchSatus.WaitingSave:
Position ps = Compare(new Point(e.X, e.Y), new Rectangle(_pLeftTopPoint.X, _pLeftTopPoint.Y, _pRightBottomPoint.X - _pLeftTopPoint.X, _pRightBottomPoint.Y - _pLeftTopPoint.Y));
switch (ps)
{
case Position.Inside:
pbCatchArea.Cursor = Cursors.SizeAll;
break;
case Position.Outside:
pbCatchArea.Cursor = Cursors.Default;
break;
case Position.Left:
case Position.Right:
pbCatchArea.Cursor = Cursors.SizeWE;
break;
case Position.Top:
case Position.Bottom:
pbCatchArea.Cursor = Cursors.SizeNS;
break;
case Position.LeftBottom:
case Position.RightTop:
pbCatchArea.Cursor = Cursors.SizeNESW;
break;
case Position.LeftTop:
case Position.RightBottom:
pbCatchArea.Cursor = Cursors.SizeNWSE;
break;
}
break;
case CatchSatus.Resizing:
switch (_psSizedPosition)
{
case Position.Inside:
//限制在屏幕范围内
if (_pLeftTopPoint.X + e.X - _pSizedPoint.X >= 0 && _pLeftTopPoint.Y + e.Y - _pSizedPoint.Y >= 0 && _pRightBottomPoint.X + e.X - _pSizedPoint.X <= this.Width && _pRightBottomPoint.Y + e.Y - _pSizedPoint.Y <= this.Height)
{
_pLeftTopPoint.X += e.X - _pSizedPoint.X;
_pLeftTopPoint.Y += e.Y - _pSizedPoint.Y;
_pRightBottomPoint.X += e.X - _pSizedPoint.X;
_pRightBottomPoint.Y += e.Y - _pSizedPoint.Y;
}
break;
case Position.Left:
_pLeftTopPoint.X += e.X - _pSizedPoint.X;
break;
case Position.Right:
_pRightBottomPoint.X += e.X - _pSizedPoint.X;
break;
case Position.Top:
_pLeftTopPoint.Y += e.Y - _pSizedPoint.Y;
break;
case Position.Bottom:
_pRightBottomPoint.Y += e.Y - _pSizedPoint.Y;
break;
case Position.LeftTop:
_pLeftTopPoint.X += e.X - _pSizedPoint.X;
_pLeftTopPoint.Y += e.Y - _pSizedPoint.Y;
break;
case Position.LeftBottom:
_pLeftTopPoint.X += e.X - _pSizedPoint.X;
_pRightBottomPoint.Y += e.Y - _pSizedPoint.Y;
break;
case Position.RightTop:
_pRightBottomPoint.X += e.X - _pSizedPoint.X;
_pLeftTopPoint.Y += e.Y - _pSizedPoint.Y;
break;
case Position.RightBottom:
_pRightBottomPoint.X += e.X - _pSizedPoint.X;
_pRightBottomPoint.Y += e.Y - _pSizedPoint.Y;
break;
}
_pSizedPoint = new Point(e.X, e.Y);
break;
}
pbCatchArea.Invalidate();
}
MouseUp事件,调整抓屏区域的左上点和右下点:
private void pbCatchArea_MouseUp(object sender, MouseEventArgs e)
{
if (_csCurrentStep == CatchSatus.WaitingSave || _csCurrentStep == CatchSatus.Resizing)
{
AdjustPoints();
_csCurrentStep = CatchSatus.WaitingSave;
}
}
因为确定抓屏区域时鼠标点下的第一个点不一定就是左上点,调整抓屏区域的大小时也一样,AdjustPoints()作用就是重新调整_pLeftTopPoint和_pRightBottomPoint。
pbCatchArea的Paint事件响应:
private void pbCatchArea_Paint(object sender, PaintEventArgs e)
{
switch (_csCurrentStep)
{
case CatchSatus.Start:
DrawReferLine(e.Graphics);
DrawString(e.Graphics, string.Format("{0}", _pRightBottomPoint));
break;
case CatchSatus.Pending:
DrawReferLine(e.Graphics);
DrawCatchRectangle(e.Graphics);
DrawString(e.Graphics, string.Format("{0}->{1} 宽:{2};高:{3}", _pLeftTopPoint, _pRightBottomPoint, _pRightBottomPoint.X - _pLeftTopPoint.X, _pRightBottomPoint.Y - _pLeftTopPoint.Y));
break;
case CatchSatus.Resizing:
DrawCatchRectangle(e.Graphics);
DrawString(e.Graphics, string.Format("{0}->{1} 宽:{2};高:{3}", _pLeftTopPoint, _pRightBottomPoint, _pRightBottomPoint.X - _pLeftTopPoint.X, _pRightBottomPoint.Y - _pLeftTopPoint.Y));
break;
case CatchSatus.WaitingSave:
case CatchSatus.Saving:
DrawCatchRectangle(e.Graphics);
break;
default:
break;
}
}
MouseDoulbeClick事件保存抓屏区域内的图:
Code
private void pbCatchArea_MouseDoubleClick(object sender, MouseEventArgs e)
{
//将图片复制到剪切板并保存
if (_csCurrentStep == CatchSatus.WaitingSave)
{
//Saving时不响应快捷键
_csCurrentStep = CatchSatus.Saving;
Bitmap bmpCatched = GetScreenImage(new Point(_pRightBottomPoint.X - _pLeftTopPoint.X > 0 ? _pLeftTopPoint.X + 1 : _pRightBottomPoint.X + 1, _pRightBottomPoint.Y - _pLeftTopPoint.Y > 0 ? _pLeftTopPoint.Y + 1 : _pRightBottomPoint.Y + 1), new Point(0, 0), new Size(System.Math.Abs(_pRightBottomPoint.X - _pLeftTopPoint.X - 1), System.Math.Abs(_pRightBottomPoint.Y - _pLeftTopPoint.Y - 1)));
Clipboard.SetImage(bmpCatched);
if (Setting.Default.SavePic)
{
SaveFileDialog sfg = new SaveFileDialog();
sfg.Filter = "Jpeg(*.jpg)|*.jpg|Bmp(*.bmp)|*.bmp|All|*.*";
if (sfg.ShowDialog() == DialogResult.OK)
{
System.Drawing.Imaging.ImageFormat formatPicSave = System.Drawing.Imaging.ImageFormat.Jpeg;
switch (sfg.FilterIndex)
{
case 1:
formatPicSave = System.Drawing.Imaging.ImageFormat.Jpeg;
break;
case 2:
formatPicSave = System.Drawing.Imaging.ImageFormat.Bmp;
break;
default:
break;
}
bmpCatched.Save(sfg.FileName, formatPicSave);
}
}
else
{
MessageBox.Show("图片已经复制到剪切板", "提示");
}
_csCurrentStep = CatchSatus.WaitingSave;
if (Setting.Default.HideAfterCatch)
{
this.Hide();
}
}
}
到这整个抓屏操作就完成了。
全局快捷键的设置和调用:
快捷键处理类:
快捷键处理类的代码主要是参考http://www.cnblogs.com/TianFang/archive/2007/05/14/745489.html写的,就做了一点点的小改动:
-
Regist:如果注册的函数已经在keymap中存在则不重复注册;
- UnRegist:加了同步更新keymap;
- ProcessHotKey:传进来的参数由Windows消息改成了消息的值,在这里消息的值即为回调函数的Id。
Code
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace ScreenCatcher
{
public static class HotkeyHelper
{
#region 系统api
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool RegisterHotKey(IntPtr hWnd, int id, ModifierKeys fsModifiers, Keys vkMain);
[DllImport("user32.dll")]
static extern bool UnregisterHotKey(IntPtr hWnd, int id);
#endregion
//Keymap的ID, 消息处理函数的WParam
static int keyid = 10;
static Dictionary<int, HotKeyCallBackHanlder> keymap = new Dictionary<int, HotKeyCallBackHanlder>();
public delegate void HotKeyCallBackHanlder();
/**/
/// <summary>
/// 注册快捷键
/// </summary>
/// <param name="hWnd">持有快捷键窗口的句柄</param>
/// <param name="fsModifiers">组合键</param>
/// <param name="vk">快捷键的虚拟键码</param>
/// <param name="callBack">回调函数</param>
public static void Regist(IntPtr hWnd, ModifierKeys fsModifiers, Keys vkMain, HotKeyCallBackHanlder callBack)
{
if (keymap.ContainsValue(callBack))
return;
int id = keyid++;
if (!RegisterHotKey(hWnd, id, fsModifiers, vkMain))
throw new Exception("regist hotkey fail.");
keymap[id] = callBack;
}
/**/
/// <summary>
/// 注销快捷键
/// </summary>
/// <param name="hWnd">持有快捷键窗口的句柄</param>
/// <param name="callBack">回调函数</param>
public static void UnRegist(IntPtr hWnd, HotKeyCallBackHanlder callBack)
{
int iKey = -1;
foreach (KeyValuePair<int, HotKeyCallBackHanlder> var in keymap)
{
if (var.Value == callBack)
{
iKey = var.Key;
UnregisterHotKey(hWnd, var.Key);
}
}
if (iKey >= 10)
keymap.Remove(iKey);
}
/**/
/// <summary>
/// 快捷键消息处理,在窗体中WndProc中快捷键消息(WM_HOTKEY = 0x312)中调用
/// </summary>
public static void ProcessHotKey(int callbackId)
{
HotKeyCallBackHanlder callback;
if (keymap.TryGetValue(callbackId, out callback))
{
callback();
}
}
}
[Flags()]
public enum ModifierKeys
{
None = 0x0,
Alt = 0x1,
Control = 0x2,
Shift = 0x4,
Windows = 0x8
}
}
快捷键的设置:
程序启动时通过Setting.Default(Setting类的实例)读取设置文件Config.xml中设置的快捷键值,然后通过HotkeyHelper.Regist(this.Handle, kModifier, kMain, RestartCatch)注册快捷键。
处理快捷键是在消息处理函数中:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case FormCatcher.UM_RESTART:
//试图运行程序的多个实例时转为重新开始截图的操作
this.RestartCatch();
break;
case FormCatcher.WM_HOTKEY:
//正在保存图片时不处理快捷键
if (_csCurrentStep != CatchSatus.Saving)
{
HotkeyHelper.ProcessHotKey(m.WParam.ToInt32());
}
break;
default:
break;
}
base.WndProc(ref m);
}
只运行一个程序实例:
也是参考网上的代码用互斥变量实现的。
不同之处是如果找到此程序已运行则发送用户消息UM_RESTART给此进程的frmCatcher窗体。
然后在窗体FormCatcher类中的消息处理函数(如上代码)中做处理,在这里是调用RestartCatch()重启抓屏操作。
给已运行的进程中frmCatcher窗体发送消息的代码如下:
private static void HandleRunningInstance()
{
Process pRunning = null;
Process pCurrent = Process.GetCurrentProcess();
Process[] psSameName = Process.GetProcessesByName(pCurrent.ProcessName);
foreach (Process p in psSameName)
{
if (p.Id != pCurrent.Id)
{
pRunning = p;
break;
}
}
if (pRunning != null)
{
IntPtr mainWindowHandle = pRunning.MainWindowHandle == IntPtr.Zero ? FindWindow(null, "frmCatcher") : pRunning.MainWindowHandle;
//发送消息给程序主窗体
SendMessage(mainWindowHandle, FormCatcher.UM_RESTART, IntPtr.Zero, IntPtr.Zero);
}
}
此函数用到了Windows API FindWindow和SendMessage。
另外,程序还包括其他三个类:
FormSetting:程序的设置窗体类,包括全局快捷键的设置。
Setting:封装了程序的各个设置选项,并通过XmlHelper进行程序设置的初始化和保存。
XmlHelper:Xml文档操作类,也就是读取和保存简单的Xml文档。