一个截屏工具制作的全过程记录——如何使用“拿来主义”

第一部分,截屏功能的实现

利用关键字“.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”的答案可以找到解决方案。

参考方案

解决方案

地址

跨线程访问控件

http://stackoverflow.com/questions/798464/how-to-invoke-with-action

全局快捷键

http://stackoverflow.com/questions/3654787/global-hotkey-in-console-application

SaveDialog用法

http://msdn.microsoft.com/en-us/library/sfezx97z.aspx

禁用双击

http://stackoverflow.com/questions/9588540/how-can-i-stop-a-double-click-of-the-window-title-bar-from-maximizing-a-window-o

保存屏幕截图到内存

http://www.codekeep.net/snippets/abc85672-d163-46df-99a1-1ac993074f56.aspx

保存图片到磁盘

http://stackoverflow.com/questions/457370/c-how-to-convert-bitmap-byte-array-to-jpeg-format

按键参考大全

http://msdn.microsoft.com/en-us/library/system.windows.forms.keys%28v=vs.71%29.aspx

非客户区WM_NCHITTEST消息参考

http://msdn.microsoft.com/en-us/library/windows/desktop/ms645618%28v=vs.85%29.aspx

禁止关闭

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


原文地址:https://www.cnblogs.com/xinyuyuanm/p/3027061.html