一个截屏工具制作的全过程记录——如何使用“拿来主义”
第一部分,截屏功能的实现
利用关键字“.netcapture speicified area”在Google中搜索现成的答案,从答案中,我直接进行“相面”,打开看起来比较满足条件的第二和第三条记录。
逐个打开搜索结果看一看,比较符合我的要求的是“http://stackoverflow.com/questions/3306600/c-how-to-take-a-screenshot-of-a-portion-of-screen”,代码如下:
Rectangle rect= new Rectangle(0, 0, 100, 100); Bitmap bmp =new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb); Graphics g =Graphics.FromImage(bmp); g.CopyFromScreen(rect.Left,rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy); bmp.Save(fileName,ImageFormat.Jpeg);
这份代码的作用就是自定义一个处于屏幕内的一块矩形区域,然后截取矩形区域内的屏幕图像。
第二部分,矩形工具的实现
Google关键字“C#how to resize a none border form”,第一条就是答案!
代码如下:
public partial class Form1 : Form { public Form1() { InitializeComponent(); this.FormBorderStyle = FormBorderStyle.None; this.DoubleBuffered = true; this.SetStyle(ControlStyles.ResizeRedraw, true); } private const int cGrip = 16; // Grip size private const int cCaption = 25; // Caption bar height; protected override void OnPaint(PaintEventArgs e) { Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, this.ClientSize.Height - cGrip, cGrip, cGrip); ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc); rc = new Rectangle(0, 0, this.ClientSize.Width, 32); e.Graphics.FillRectangle(Brushes.DarkBlue, rc); } protected override void WndProc(ref Message m) { if (m.Msg == 0x84) { // Trap WM_NCHITTEST Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16); pos = this.PointToClient(pos); if (pos.Y < cCaption) { m.Result = (IntPtr)2; // HTCAPTION return; } if (pos.X >= this.ClientSize.Width - cGrip && pos.Y >= this.ClientSize.Height - cGrip) { m.Result = (IntPtr)17; // HTBOTTOMRIGHT return; } } base.WndProc(ref m); } }
上段代码实现了一个可拖拽、可调节大小的无边框样式的窗体。重点内容有两处,一个是标题栏的自绘,另一个是对用户交互区域的绘制。因为截图工具并不需要标题栏,所以完全可以把下面的代码删除,这样自绘的标题栏就不会显示。
rc = new Rectangle(0, 0, this.ClientSize.Width, 32); e.Graphics.FillRectangle(Brushes.DarkBlue, rc);
虽然自绘标题栏消失了,但WndProc还是会检测用户压下鼠标动作,并把客户区坐标小于cCaption的动作当作压下标题栏来处理。而我真正需要的是拖拽整个窗体,而非只是针对标题栏,所以需要把pos.Y < cCaption的限定范围扩大,所以修改后的代码应该如下:
if (pos.X >= this.ClientSize.Width - cGrip && pos.Y >= this.ClientSize.Height - cGrip) { m.Result = (IntPtr)17; // HTBOTTOMRIGHT return; } else { m.Result = (IntPtr)2; // HTCAPTION return; }
先处理右下角与用户交互的可调整窗体大小的提示区域,然后把非客户区的其它消息当作标题栏交互消息处理,这样就可以实现窗体拖拽的效果了。
对于窗体大小调整的功能,程序中分两步实现:第一步是在protected override void OnPaint(PaintEventArgs e)中绘制了一个交互性图标,用户通过它可以对窗体的大小进行调节;第二步则是将非客户区,且鼠标处于指定范围内的消息,当作HTBOTTOMRIGHT处理,即右下角的调节大小(当然,也可以为八个方向编写对应的处理,其它的可用值见http://msdn.microsoft.com/en-us/library/windows/desktop/ms645618%28v=vs.85%29.aspx)。
现在已经完美地解决了拖拽,在测试阶段却发现标题栏特有的附加功能——双击。怎么屏蔽呢?从代码中可用看出,我们一直都在处理WM_NCHITTEST消息,其余的消息都用默认的base.WndProc处理了。为了能够达到屏蔽的效果,可用添加一个双击消息的处理,即不做任何处理
if(m.Msg == 0xA3) // WM_NCLBUTTONDBLCLK { m.Result = IntPtr.Zero; return; }
第三部分:如何设置半透明效果:
Google关键字“c#transparent form”,第一条为答案!
BackColor = Color.Lime; TransparencyKey = Color.Lime;
运行一下看效果,且慢!不对啊,画面上什么都没有了,完全看不到窗体!回想了一下,TransparencyKey会将画面中指定的颜色部分搞成全透明的,所以就什么都看不见了。这时又想到了Opacity,OK,搞定!
关于透明,这里要多提一句,TransparencyKey和Opacity,前者功能是让指定颜色完全透明,后者则是让窗体半透明,在后面完整的源码中,我会给出相应的代码例子。
第四部分,全局热键
Google关键字“c#global shortcut key for my exe”,这个稍微复杂了些,从答案中并没有直接给出代码,但从推荐的网站“http://www.dreamincode.net/forums/topic/180436-global-hotkeys/”中我们可以找到现成代码可以下载。但是公司的Policy比较严,不能随便下,所以放弃。
换个关键字“c#global shortcut key example”,第一个就是答案:http://stackoverflow.com/questions/3654787/global-hotkey-in-console-application,代码也有贴出,内容有点长,请参考后面的完整源码部分。
第五部分,图像保存
有了上面的调查结果,我们差不多已经调查完80%的功能了,剩下的基本上就是一些使用上的功能,在这个截图工具中,我打算采用两种保存图片的功能:一个是直接保存到内存中,这样方便直接粘贴到Word这样的高级编辑器中,还有一种就是弹出SaveDialog对话框,让用户自行选择保存地址,生成物理文件。使用关键字“c# copybitmap to clipboard”可以很容易地找到第一种方法的答案,而关于第二种这里就不多做解释了,“http://msdn.microsoft.com/en-us/library/sfezx97z.aspx”里有完整的答案。
下面是把截图保存到内存的实现代码,关键内容就是Clipboard.SetImage:
private void button1_Click(object sender, EventArgs e) { Rectangle r = this.RectangleToScreen(ClientRectangle); Bitmap b = GetDesktopImage(r); Clipboard.SetImage(b); } public Bitmap GetDesktopImage(Rectangle rect) { Graphics graphics; Bitmap bitmap; bitmap = new Bitmap(rect.Width, rect.Height); graphics = Graphics.FromImage(bitmap); graphics.CopyFromScreen(rect.Left, rect.Top, 0, 0, new Size(rect.Width, rect.Height)); return bitmap; }
文件保存成物理文件可以参考“http://stackoverflow.com/questions/457370/c-how-to-convert-bitmap-byte-array-to-jpeg-format”,代码如下:
using(Image img = Image.FromFile("foo.bmp")) { img.Save("foo.jpg", ImageFormat.Jpeg); }
注意,在调用文件保存到内存时,代码会执行出错,原因可能是OLE对象的问题。通过“http://stackoverflow.com/questions/798464/how-to-invoke-with-action”的答案可以找到解决方案。
参考方案
禁止关闭
C# prevent closing
隐藏与显示
Form.Visible = !Form.Visible
系统托盘
C# system tray
http://alanbondo.wordpress.com/2008/06/22/creating-a-system-tray-app-with-c/
public interface SystemTrayHandler { void OnSystemTrayShow(object sender, EventArgs e); void OnSystemTrayExit(object sender, EventArgs e); } public class SystemTrayLoader { public static void WrapFormWithSysTray(SystemTrayHandler handler) { // Create a simple tray menu with only one item. ContextMenu trayMenu = new ContextMenu(); trayMenu.MenuItems.Add("Show Infected Area", handler.OnSystemTrayShow); trayMenu.MenuItems.Add("Exit Shotit", handler.OnSystemTrayExit); // Create a tray icon. In this example we use a // standard system icon for simplicity, but you // can of course use your own custom icon too. NotifyIcon trayIcon = new NotifyIcon(); trayIcon.Text = "Shotit by wooooody```"; trayIcon.Icon = new Icon(SystemIcons.Application, 40, 40); // Add menu to tray icon and show it. trayIcon.ContextMenu = trayMenu; trayIcon.Visible = true; } }
双击系统托盘
http://www.developer.com/net/net/article.php/3336751/C-Tip-Placing-Your-C-Application-in-the-System-Tray.htm
C# double click system tray
内存中默认保存Png格式
http://stackoverflow.com/questions/3517965/convert-bmp-to-png-in-memory-for-clipboard-pasting-in-net