滞后的截图工具

Laggy snipping tool

我正在尝试创建像这样的截图工具 https://app.prntscr.com/en/index.html。我能够修改一些代码并设法显示带有选定矩形(片段)的调整大小手柄,手柄工作正常,我可以移动选定的矩形并调整它的大小。但是当我将它与上面提到的工具进行比较时它非常慢,有人可以指出我的代码中的一些问题以使其更快吗?

我正在制作的工具如下所示:

    /* 
 * Credit for this portion of Imgur Snipping Tool goes to Hans Passant from whosebug.com
 * 
 * 
 * Modified to work with multiple monitors.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Free_Snipping_Tool.Classes;

namespace Free_Snipping_Tool
{
    public partial class SnippingTool2 : Form
    {
        public enum ScreenshotType
        {
            FULL, ACTIVE, DEFINED
        }
        private int oldX;
        private int oldY;
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        Rectangle topLeft = new Rectangle();
        Rectangle topMiddle = new Rectangle();
        Rectangle topRight = new Rectangle();
        Rectangle bottomLeft = new Rectangle();
        Rectangle bottomMiddle = new Rectangle();
        Rectangle bottomRight = new Rectangle();
        Rectangle leftMiddle = new Rectangle();
        Rectangle rightMiddle = new Rectangle();

        bool topLeftSelected = false;
        bool topMiddleSelected = false;
        bool topRightSelected = false;
        bool bottomLeftSelected = false;
        bool bottomMiddleSelected = false;
        bool bottomRightSelected = false;
        bool leftMiddleSelected = false;
        bool rightMiddleSelected = false;
        bool rectangleSelected = false;

        [DllImport("user32.dll")]
        private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

        [DllImport("user32.dll")]
        private static extern int GetForegroundWindow();

        SnipSizeControl lbl = new SnipSizeControl();
        floatingControlRight fcR = new floatingControlRight();
        floatingControlBottom fcB = new floatingControlBottom();

        public static Image Snip(ScreenshotType type)
        {
            switch (type)
            {
                case ScreenshotType.FULL:
                    return CreateScreenshot(0, 0, SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height);

                case ScreenshotType.DEFINED:
                    SnippingTool2 snipper = new SnippingTool2(CreateScreenshot(0, 0, SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height), new Point(SystemInformation.VirtualScreen.Left, SystemInformation.VirtualScreen.Top));
                    if (snipper.ShowDialog() == DialogResult.OK)
                    {
                        return snipper.Image;
                    }
                    break;
                case ScreenshotType.ACTIVE:
                    RECT windowRectangle;
                    GetWindowRect((System.IntPtr)GetForegroundWindow(), out windowRectangle);
                    return CreateScreenshot(windowRectangle.Left, windowRectangle.Top, windowRectangle.Right - windowRectangle.Left, windowRectangle.Bottom - windowRectangle.Top);
            }
            return null;
        }

        private static Bitmap CreateScreenshot(int left, int top, int width, int height)
        {
            Bitmap bmp = new Bitmap(width, height);
            Graphics g = Graphics.FromImage(bmp);
            g.CopyFromScreen(left, top, 0, 0, new Size(width, height));
            g.Dispose();
            return bmp;
        }


        public SnippingTool2(Image screenShot, Point startPos)
        {
            InitializeComponent();

            lbl.Text = "0x0";
            lbl.Visible = true;
            lbl.BackColor = Color.Black;
            lbl.ForeColor = Color.White;
            lbl.Visible = false;
            this.Controls.Add(lbl);

            this.BackgroundImage = screenShot;
            Size s = screenShot.Size;
            this.ShowInTaskbar = false;
            this.FormBorderStyle = FormBorderStyle.None;
            this.StartPosition = FormStartPosition.Manual;
            this.Size = s;
            this.Location = startPos;
            this.DoubleBuffered = true;
        }
        public Image Image { get; set; }

        private Rectangle rcSelect = new Rectangle();
        private Point pntStart;
        bool handleSelected = false;

        protected override void OnMouseDown(MouseEventArgs e)
        {

            if ((e.X > topLeft.X) && (e.X < topLeft.X + topLeft.Width) && (e.Y > topLeft.Y) && (e.Y < topLeft.Y + topLeft.Height))
            {
                this.Cursor = Cursors.SizeNWSE;
                topLeftSelected = true;
                handleSelected = true;
            }

            else if ((e.X > topMiddle.X) && (e.X < topMiddle.X + topMiddle.Width) && (e.Y > topMiddle.Y) && (e.Y < topMiddle.Y + topMiddle.Height))
            {
                this.Cursor = Cursors.SizeNS;
                topMiddleSelected = true;
                handleSelected = true;
            }

            else if ((e.X > topRight.X) && (e.X < topRight.X + topRight.Width) && (e.Y > topRight.Y) && (e.Y < topRight.Y + topRight.Height))
            {
                this.Cursor = Cursors.SizeNESW;
                topRightSelected = true;
                handleSelected = true;
            }

            else if ((e.X > bottomLeft.X) && (e.X < bottomLeft.X + bottomLeft.Width) && (e.Y > bottomLeft.Y) && (e.Y < bottomLeft.Y + bottomLeft.Height))
            {
                this.Cursor = Cursors.SizeNESW;

                bottomLeftSelected = true;
                handleSelected = true;

            }

            else if ((e.X > bottomMiddle.X) && (e.X < bottomMiddle.X + bottomMiddle.Width) && (e.Y > bottomMiddle.Y) && (e.Y < bottomMiddle.Y + bottomMiddle.Height))
            {
                this.Cursor = Cursors.SizeNS;
                bottomMiddleSelected = true;
                handleSelected = true;
            }

            else if ((e.X > bottomRight.X) && (e.X < bottomRight.X + bottomRight.Width) && (e.Y > bottomRight.Y) && (e.Y < bottomRight.Y + bottomRight.Height))
            {
                this.Cursor = Cursors.SizeNWSE;
                bottomRightSelected = true;
                handleSelected = true;

            }

            else if ((e.X > leftMiddle.X) && (e.X < leftMiddle.X + leftMiddle.Width) && (e.Y > leftMiddle.Y) && (e.Y < leftMiddle.Y + leftMiddle.Height))
            {
                this.Cursor = Cursors.SizeWE;
                leftMiddleSelected = true;
                handleSelected = true;
            }

            else if ((e.X > rightMiddle.X) && (e.X < rightMiddle.X + rightMiddle.Width) && (e.Y > rightMiddle.Y) && (e.Y < rightMiddle.Y + rightMiddle.Height))
            {
                this.Cursor = Cursors.SizeWE;
                rightMiddleSelected = true;
                handleSelected = true;
            }
            else if ((e.X > rcSelect.X) && (e.X < rcSelect.X + rcSelect.Width) && (e.Y > rcSelect.Y) && (e.Y < rcSelect.Y + rcSelect.Height))
            {
                this.Cursor = Cursors.SizeAll;
                rectangleSelected = true;
                handleSelected = true;
            }
            else
            {
                this.Cursor = Cursors.Cross;
                topLeftSelected = false;
                topMiddleSelected = false;
                topRightSelected = false;
                bottomLeftSelected = false;
                bottomMiddleSelected = false;
                bottomRightSelected = false;
                leftMiddleSelected = false;
                rightMiddleSelected = false;
                rectangleSelected = false;
                handleSelected = false;
            }

            // Start the snip on mouse down
            if (e.Button != MouseButtons.Left) return;
            if (handleSelected == true) return;

            pntStart = e.Location;
            rcSelect = new Rectangle(e.Location, new Size(0, 0));
            lbl.Visible = true;

            lbl.Location = new Point(e.X + 10, e.Y + 10); ;
            lbl.Value = string.Format("0x0");
            lbl.Refresh();
            fcR.Visible = false;

            oldX = e.X;
            oldY = e.Y;

            this.Refresh();
        }

        protected override CreateParams CreateParams
        {
            get
            {
                var cp = base.CreateParams;
                cp.ExStyle |= 0x02000000;    // Turn on WS_EX_COMPOSITED
                return cp;
            }
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            // Modify the selection on mouse move

            if ((e.X > topLeft.X) && (e.X < topLeft.X + topLeft.Width) && (e.Y > topLeft.Y) && (e.Y < topLeft.Y + topLeft.Height))
            {
                this.Cursor = Cursors.SizeNWSE;
            }

            else if ((e.X > topMiddle.X) && (e.X < topMiddle.X + topMiddle.Width) && (e.Y > topMiddle.Y) && (e.Y < topMiddle.Y + topMiddle.Height))
            {
                this.Cursor = Cursors.SizeNS;
            }

            else if ((e.X > topRight.X) && (e.X < topRight.X + topRight.Width) && (e.Y > topRight.Y) && (e.Y < topRight.Y + topRight.Height))
            {
                this.Cursor = Cursors.SizeNESW;
            }

            else if ((e.X > bottomLeft.X) && (e.X < bottomLeft.X + bottomLeft.Width) && (e.Y > bottomLeft.Y) && (e.Y < bottomLeft.Y + bottomLeft.Height))
            {
                this.Cursor = Cursors.SizeNESW;
            }

            else if ((e.X > bottomMiddle.X) && (e.X < bottomMiddle.X + bottomMiddle.Width) && (e.Y > bottomMiddle.Y) && (e.Y < bottomMiddle.Y + bottomMiddle.Height))
            {
                this.Cursor = Cursors.SizeNS;
            }

            else if ((e.X > bottomRight.X) && (e.X < bottomRight.X + bottomRight.Width) && (e.Y > bottomRight.Y) && (e.Y < bottomRight.Y + bottomRight.Height))
            {
                this.Cursor = Cursors.SizeNWSE;
            }

            else if ((e.X > leftMiddle.X) && (e.X < leftMiddle.X + leftMiddle.Width) && (e.Y > leftMiddle.Y) && (e.Y < leftMiddle.Y + leftMiddle.Height))
            {
                this.Cursor = Cursors.SizeWE;
            }

            else if ((e.X > rightMiddle.X) && (e.X < rightMiddle.X + rightMiddle.Width) && (e.Y > rightMiddle.Y) && (e.Y < rightMiddle.Y + rightMiddle.Height))
            {
                this.Cursor = Cursors.SizeWE;
            }
            else if ((e.X > rcSelect.X) && (e.X < rcSelect.X + rcSelect.Width) && (e.Y > rcSelect.Y) && (e.Y < rcSelect.Y + rcSelect.Height))
            {
                this.Cursor = Cursors.SizeAll;
            }
            else
            {
                this.Cursor = Cursors.Cross;
            }

            if (e.Button == MouseButtons.Left && handleSelected == true)
            {
                Rectangle backupRect = rcSelect;

                if (topLeftSelected == true)
                {
                    rcSelect.X += e.X - oldX;
                    rcSelect.Width -= e.X - oldX;
                    rcSelect.Y += e.Y - oldY;
                    rcSelect.Height -= e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }

                else if (topRightSelected == true)
                {
                    rcSelect.Width += e.X - oldX;
                    rcSelect.Y += e.Y - oldY;
                    rcSelect.Height -= e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }

                else if (topMiddleSelected == true)
                {

                    rcSelect.Y += e.Y - oldY;
                    rcSelect.Height -= e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();

                }

                else if (bottomLeftSelected == true)
                {
                    rcSelect.Width -= e.X - oldX;
                    rcSelect.X += e.X - oldX;
                    rcSelect.Height += e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (bottomMiddleSelected == true)
                {
                    rcSelect.Height += e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (bottomRightSelected == true)
                {
                    rcSelect.Width += e.X - oldX;
                    rcSelect.Height += e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (rightMiddleSelected == true)
                {
                    rcSelect.Width += e.X - oldX;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (leftMiddleSelected == true)
                {
                    rcSelect.X += e.X - oldX;
                    rcSelect.Width -= e.X - oldX;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (rectangleSelected == true)
                {
                    rcSelect.X = rcSelect.X + e.X - oldX;
                    rcSelect.Y = rcSelect.Y + e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
            }
            else if (e.Button == MouseButtons.Left)
            {
                int x1 = Math.Min(e.X, pntStart.X);
                int y1 = Math.Min(e.Y, pntStart.Y);
                int x2 = Math.Max(e.X, pntStart.X);
                int y2 = Math.Max(e.Y, pntStart.Y);

                rcSelect = new Rectangle(x1, y1, x2 - x1, y2 - y1);

                this.Refresh();

                lbl.Location = new Point(x1, y1 - 25);
                lbl.Value = string.Format("{0}x{1}", x2 - x1, y2 - y1);
                lbl.Refresh();

            }

            oldX = e.X;
            oldY = e.Y;

        }
        protected override void OnMouseUp(MouseEventArgs e)
        {
            // Complete the snip on mouse-up
            if (rcSelect.Width <= 0 || rcSelect.Height <= 0) return;
            Image = new Bitmap(rcSelect.Width, rcSelect.Height);
            using (Graphics gr = Graphics.FromImage(Image))
            {
                gr.DrawImage(this.BackgroundImage, new Rectangle(0, 0, Image.Width, Image.Height),
                    rcSelect, GraphicsUnit.Pixel);
            }

            topLeftSelected = false;
            topMiddleSelected = false;
            topRightSelected = false;
            bottomLeftSelected = false;
            bottomMiddleSelected = false;
            bottomRightSelected = false;
            leftMiddleSelected = false;
            rightMiddleSelected = false;
            rectangleSelected = false;
            handleSelected = false;

            //DialogResult = DialogResult.OK;
        }
        protected override void OnPaint(PaintEventArgs e)
        {
            // Draw the current selection
            using (Brush br = new SolidBrush(Color.FromArgb(30, Color.Black)))
            {
                int x1 = rcSelect.X; int x2 = rcSelect.X + rcSelect.Width;
                int y1 = rcSelect.Y; int y2 = rcSelect.Y + rcSelect.Height;
                e.Graphics.FillRectangle(br, new Rectangle(0, 0, x1, this.Height));
                e.Graphics.FillRectangle(br, new Rectangle(x2, 0, this.Width - x2, this.Height));
                e.Graphics.FillRectangle(br, new Rectangle(x1, 0, x2 - x1, y1));
                e.Graphics.FillRectangle(br, new Rectangle(x1, y2, x2 - x1, this.Height - y2));
            }
            using (Pen pen = new Pen(Color.Red, 1))
            {
                e.Graphics.DrawRectangle(pen, rcSelect);
            }


            //Resize Controls

            //Top left
            topLeft = new Rectangle(rcSelect.X - 3, rcSelect.Y - 3, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, topLeft);
            }

            //Top middle
            topMiddle = new Rectangle((rcSelect.X - 3) + (rcSelect.Width) / 2, rcSelect.Y - 3, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, topMiddle);
            }

            //Top right
            topRight = new Rectangle((rcSelect.X - 3) + rcSelect.Width, rcSelect.Y - 3, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, topRight);
            }


            //Bottom left
            bottomLeft = new Rectangle(rcSelect.X - 3, (rcSelect.Y - 3) + rcSelect.Height, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, bottomLeft);
            }

            //Bottom middle
            bottomMiddle = new Rectangle((rcSelect.X - 3) + (rcSelect.Width) / 2, (rcSelect.Y - 3) + rcSelect.Height, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, bottomMiddle);
            }

            //Bottom right
            bottomRight = new Rectangle((rcSelect.X - 3) + rcSelect.Width, (rcSelect.Y - 3) + rcSelect.Height, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, bottomRight);
            }

            //Left middle
            leftMiddle = new Rectangle(rcSelect.X - 3, (rcSelect.Y - 3) + (rcSelect.Height) / 2, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, leftMiddle);
            }

            //Right middle
            rightMiddle = new Rectangle((rcSelect.X - 3) + rcSelect.Width, (rcSelect.Y - 3) + (rcSelect.Height) / 2, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, rightMiddle);
            }
        }
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            // Allow canceling the snip with the Escape key
            if (keyData == Keys.Escape) this.DialogResult = DialogResult.Cancel;
            return base.ProcessCmdKey(ref msg, keyData);
        }
    }
}

编辑: 项目文件:https://www.dropbox.com/s/k1afggj6inye4kp/Free%20Snipipng%20Tool-project.rar?dl=0

我会引用官方Windows documentation。虽然该文档是关于 Native API,但 Winforms 使用相同的底层技术:

An application invalidates a portion of a window and sets the update region by using the InvalidateRect or InvalidateRgn function. These functions add the specified rectangle or region to the update region, combining the rectangle or region with anything the system or the application may have previously added to the update region.

The InvalidateRect and InvalidateRgn functions do not generate WM_PAINT messages. Instead, the system accumulates the changes made by these functions and its own changes. By accumulating changes, a window processes all changes at once instead of updating bits and pieces one step at a time.

调用 .NET Refresh() 等同于调用 InvalidateAll() + Update()InvalidateAll 将整个屏幕标记为无效,Update() 强制重新绘制无效内容的过程,因此整个屏幕。 如果您只是使 "manually" 您知道已更改的内容无效,则可以优化您的程序。

这就是我在修改后的示例中所做的。我没有调用 Refresh(),而是添加了一个新的 oldRcRect 变量来使旧状态和新状态失效,以及一个像这样的 RefreshOnMove() 方法(我用 RefreshOnMove 调用替换了大多数 Refresh 调用):

    private void RefreshOnMove()
    {
        // invalidate the old rect (+ size of red box)
        var rc = oldRcSelect;
        rc.Inflate(3, 3);
        Invalidate(rc);

        // invalidate the new rect (+ size of red box)
        // note you can almost omit this second one, but if you move the mouse really fast, you'll see some red box not fully displayed
        // but the benefit is small, something like a 3 x width/height rectangle
        rc = rcSelect;
        rc.Inflate(3, 3);
        Invalidate(rc);

        // each time you call invalidate, you just accumulate a change
        // to the change region, nothing actually changes on the screen

        // now, ask Windows to process the combination of changes
        Update();
    }

PS:关于我对内部区域的评论,我只是说可以避免每次都使白框的内容无效,但它更复杂。