1

我开发了一个 Windows 窗体应用程序,用于在用户单击、按住和拖动鼠标时绘制与用户鼠标位置相关的矩形区域。

该类非常简单,如下所示:

public partial class MainForm : LayeredForm
{
    private bool drawing = false;
    private Point startLocation = Point.Empty;

    public MainForm()
    {
        InitializeComponent();
        Location = Screen.PrimaryScreen.Bounds.Location;
        Size = Screen.PrimaryScreen.Bounds.Size;
        Cursor = Cursors.Cross;
        TopMost = true;
        ShowInTaskbar = false;
        DoubleBuffered = true;
    }

    private void MainForm_MouseDown(object sender, MouseEventArgs e)
    {
        drawing = true;
        startLocation = e.Location;
    }

    private void MainForm_MouseMove(object sender, MouseEventArgs e)
    {
        if (drawing)
            Invalidate();
    }

    private void MainForm_MouseUp(object sender, MouseEventArgs e)
    {
        drawing = false;
    }

    private void MainForm_Paint(object sender, PaintEventArgs e)
    {
        Rectangle r = new Rectangle(Math.Min(startLocation.X, Cursor.Position.X), Math.Min(startLocation.Y, Cursor.Position.Y),
            Math.Abs(startLocation.X - Cursor.Position.X), Math.Abs(startLocation.Y - Cursor.Position.Y));

        e.Graphics.FillRectangle(Brushes.Blue, r);
    }
}


public class LayeredForm : Form
{
    public new event PaintEventHandler Paint;

    public LayeredForm()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        StartPosition = FormStartPosition.Manual;
    }

    protected override CreateParams CreateParams
    {
        get
        {
            if (DesignMode) return base.CreateParams;
            CreateParams cParams = base.CreateParams;
            cParams.ExStyle = cParams.ExStyle | 0x80000;
            return cParams;
        }
    }

    private void PaintNative(Bitmap bitmap)
    {
        IntPtr hdcDestination = Win32.GetDC(IntPtr.Zero);
        IntPtr hdcSource = Win32.CreateCompatibleDC(hdcDestination);
        IntPtr hdcBitmap = IntPtr.Zero;
        IntPtr previousBitmap = IntPtr.Zero;

        try
        {
            hdcBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
            previousBitmap = Win32.SelectObject(hdcSource, hdcBitmap);

            Win32.SIZE size = new Win32.SIZE(bitmap.Width, bitmap.Height);
            Win32.POINT source = new Win32.POINT(0, 0);
            Win32.POINT destination = new Win32.POINT(Left, Top);
            Win32.BLENDFUNCTION blendFunc = new Win32.BLENDFUNCTION()
            {
                BlendOp = Win32.AC_SRC_OVER,
                BlendFlags = 0,
                SourceConstantAlpha = 50,
                AlphaFormat = Win32.AC_SRC_ALPHA
            };

            Win32.UpdateLayeredWindow(Handle, hdcDestination, ref destination, ref size, hdcSource, ref source, 0, ref blendFunc, 2);
        }
        finally
        {
            Win32.ReleaseDC(IntPtr.Zero, hdcDestination);
            if (hdcBitmap != IntPtr.Zero)
            {
                Win32.SelectObject(hdcSource, previousBitmap);
                Win32.DeleteObject(hdcBitmap);
            }
            Win32.DeleteDC(hdcSource);
        }
    }

    public new void Invalidate()
    {
        using (Bitmap bitmap = new Bitmap(ClientSize.Width, ClientSize.Height))
        {
            using (Graphics graphics = Graphics.FromImage(bitmap))
            {
                if (Paint != null)
                    Paint(this, new PaintEventArgs(graphics, Rectangle.Empty));
            }
            PaintNative(bitmap);
        }
    }
}

在我的电脑和其他大多数电脑上,一切都运行良好。然而,在对各种机器进行了一些测试后,我发现一些计算机在绘制矩形时遇到了困难(明显很慢,而且断断续续)。我想我已经确定了这个问题,但是我需要验证和解决这个问题。

我相信问题在于每次鼠标移动事件被触发时,整个表单都会被一次又一次地绘制。在具有大分辨率的弱计算机上,这是非常费力的。

在进行了一些研究之后,我认为解决方案是只绘制已更改的矩形部分而不是整个表单,尽管我对如何做到这一点一无所知。我非常感谢 SO 可以提供的任何帮助,在此先感谢。

更新

肯的​​完整代码:

public sealed partial class RegionForm : LayeredWindow // : Form
{
    private Bitmap bitmap;
    private bool mouseDown;
    private Point newPoint = Point.Empty;
    private Point oldPoint = Point.Empty;
    private Point startPoint = Point.Empty;

    public RegionForm()
    {
        InitializeComponent();
        Location = Screen.PrimaryScreen.Bounds.Location;
        Size = Screen.PrimaryScreen.Bounds.Size;
        Cursor = Cursors.Cross;
        TopMost = true;
        ShowInTaskbar = false;
    }

    private void regionPanel_MouseDown(object sender, MouseEventArgs e)
    {
        bitmap = new Bitmap(regionPanel.ClientSize.Width,
                            regionPanel.ClientSize.Height,
                            PixelFormat.Format32bppPArgb);

        regionPanel.DrawToBitmap(bitmap, regionPanel.ClientRectangle);
        startPoint = e.Location;
        mouseDown = true;
    }

    private void regionPanel_MouseUp(object sender, MouseEventArgs e)
    {
        mouseDown = false;

        if (bitmap != null)
        {
            bitmap.Dispose();
            bitmap = null;
        }

        Rectangle region = new Rectangle(startPoint, new Size(oldPoint.X - startPoint.X + 1,
                                                              oldPoint.Y - startPoint.Y + 1));
        regionPanel.Invalidate(region, true);
    }

    private void regionPanel_MouseMove(object sender, MouseEventArgs e)
    {
        if (mouseDown)
        {
            using (Graphics g = regionPanel.CreateGraphics())
            {
                g.SmoothingMode = SmoothingMode.None;
                newPoint = e.Location;
                ClearRegion(g);
                oldPoint = newPoint;
                DrawRegion(g);
            }
        }
    }

    private void DrawRegion(Graphics g)
    {
        int x1 = startPoint.X;
        int y1 = startPoint.Y;
        int x2 = newPoint.X;
        int y2 = newPoint.Y;

        //block "negative" selection
        if (x1 > x2)
        {
            x2 = x1;
        }
        if (y1 > y2)
        {
            y2 = y1;
        }

        //Draw a red rectangle
        g.FillRectangle(Brushes.Red, x1, y1, x2 - x1, y2 - y1);
    }

    private void ClearRegion(Graphics g)
    {
        int x1 = startPoint.X;
        int y1 = startPoint.Y;
        int x2 = oldPoint.X;
        int y2 = oldPoint.Y;

        if (x1 > x2)
        {
            x2 = x1;
        }
        if (y1 > y2)
        {
            y2 = y1;
        }

        //check left line
        if (newPoint.Y < y2)
        {
            Rectangle rectdst = new Rectangle(x1, newPoint.Y, 1, oldPoint.Y - newPoint.Y);
            g.DrawImage(bitmap, rectdst, rectdst, GraphicsUnit.Pixel);
        }

        //upper line
        if (newPoint.X < x2)
        {
            Rectangle rectdst = new Rectangle(newPoint.X, y1, oldPoint.X - newPoint.X, 1);
            g.DrawImage(bitmap, rectdst, rectdst, GraphicsUnit.Pixel);
        }

        //right line
        if (newPoint.X != x2 || newPoint.Y < y2)
        {
            int h = 0;
            int y = 0;
            if (newPoint.X == x2)
            {
                y = newPoint.Y;
                h = oldPoint.Y - newPoint.Y + 1;
            }
            else
            {
                y = startPoint.Y;
                h = oldPoint.Y - startPoint.Y + 1;
            }

            Rectangle rectdst = new Rectangle(oldPoint.X, y, 1, h);
            g.DrawImage(bitmap, rectdst, rectdst, GraphicsUnit.Pixel);
        }

        //bottom line
        if (newPoint.Y != y2 || newPoint.X < x2)
        {
            int w = 0;
            int x = 0;

            if (newPoint.Y == y2)
            {
                x = newPoint.X;
                w = oldPoint.X - newPoint.X + 1;
            }
            else
            {
                x = startPoint.X;
                w = oldPoint.X - x1 + 1;
            }

            Rectangle rectdst = new Rectangle(x, oldPoint.Y, w, 1);
            g.DrawImage(bitmap, rectdst, rectdst, GraphicsUnit.Pixel);
        }
    }

public class LayeredWindow : Form
{
    public new event PaintEventHandler Paint;

    public LayeredWindow()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        StartPosition = FormStartPosition.Manual;
    }

    protected override CreateParams CreateParams
    {
        get
        {
            if (DesignMode) return base.CreateParams;
            CreateParams cParams = base.CreateParams;
            cParams.ExStyle = cParams.ExStyle | 0x80000;
            return cParams;
        }
    }

    private void PaintNative(Bitmap bitmap)
    {
        IntPtr hdcDestination = Win32.GetDC(IntPtr.Zero);
        IntPtr hdcSource = Win32.CreateCompatibleDC(hdcDestination);
        IntPtr hdcBitmap = IntPtr.Zero;
        IntPtr previousBitmap = IntPtr.Zero;

        try
        {
            hdcBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
            previousBitmap = Win32.SelectObject(hdcSource, hdcBitmap);

            Size size = new Size(bitmap.Width, bitmap.Height);
            Point source = new Point(0, 0);
            Point destination = new Point(Left, Top);
            Win32.BLENDFUNCTION blendFunc = new Win32.BLENDFUNCTION()
            {
                BlendOp = Win32.AC_SRC_OVER,
                BlendFlags = 0,
                SourceConstantAlpha = 50,
                AlphaFormat = Win32.AC_SRC_ALPHA
            };

            Win32.UpdateLayeredWindow(Handle, hdcDestination, ref destination, ref size, hdcSource, ref source, 0, ref blendFunc, 2);
        }
        finally
        {
            Win32.ReleaseDC(IntPtr.Zero, hdcDestination);
            if (hdcBitmap != IntPtr.Zero)
            {
                Win32.SelectObject(hdcSource, previousBitmap);
                Win32.DeleteObject(hdcBitmap);
            }
            Win32.DeleteDC(hdcSource);
        }
    }

    public new void Invalidate()
    {
        using (Bitmap bitmap = new Bitmap(ClientSize.Width, ClientSize.Height))
        {
            using (Graphics graphics = Graphics.FromImage(bitmap))
            {
                if (Paint != null)
                    Paint(this, new PaintEventArgs(graphics, Rectangle.Empty));
            }
            PaintNative(bitmap);
        }
    }
}

public sealed class Win32
{
    [DllImport("user32.dll")]
    public static extern bool HideCaret(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
    public static extern short GetKeyState(int keyCode);

    [DllImport("user32.dll")]
    public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

    [DllImport("user32.dll")]
    public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    [DllImport("gdi32.dll", SetLastError = true)]
    public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

    [DllImport("user32.dll")]
    public static extern IntPtr GetDC(IntPtr hWnd);

    [DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
    public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

    [DllImport("user32.dll")]
    public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

    [DllImport("gdi32.dll")]
    public static extern bool DeleteDC(IntPtr hdc);

    [DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);

    [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pptSrc, uint crKey, [In] ref BLENDFUNCTION pblend, uint dwFlags);

    [DllImport("user32.dll")]
    public static extern IntPtr GetDesktopWindow();

    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr ptr);

    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

    [DllImport("gdi32.dll")]
    public static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);

    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

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

    public const byte AC_SRC_OVER = 0;
    public const byte AC_SRC_ALPHA = 1;
    public const byte ULW_ALPHA = 2;

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct BLENDFUNCTION
    {
        public byte BlendOp;
        public byte BlendFlags;
        public byte SourceConstantAlpha;
        public byte AlphaFormat;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

}

解决方案下载:http ://www.mediafire.com/?b9ql4pzh69u10n4

4

3 回答 3

3

You need to draw it from the MouseMove event. Invalidating the whole control will be slow on some computers/resolutions.

UPDATE:

This code will draw and clear the rectangle for selected region in a highly optimized way.

The routine draws a rectangle on MouseMove, but only clears fragments of the lines as needed. That is if the region is smaller than the previous size only the outer parts of the lines are cleared from the stored bitmap.

Due to this there is no need to enable double-buffering.

In order to get the buffered bitmap of the window in correct position as source copy we need to implement a small trick:

  • Add a Panel (panFill in this example) to the form and set docking to fill.
  • Add all controls / background image to the panel, not the form

Now we can copy the bitmap we need later from the Panel instead of the Form. The reason we need this is that using this.DrawBitmap() will draw borders and title-bar to the bitmap too and we don't want that. Using the Panel will eliminate this.

In the global scope of the form's class we set:

Bitmap bmp = null;
bool inDrag = false;
Point regStart = Point.Empty;
Point regNew = Point.Empty;
Point regOld = Point.Empty;

On the Panel's MouseDown/Up event:

private void panFill_MouseDown(object sender, MouseEventArgs e)
{
    //Create a bitmap
    bmp = new Bitmap(panFill.ClientSize.Width, _
                     panFill.ClientSize.Height, _
                     Imaging.PixelFormat.Format32bppPArgb);

    panFill.DrawToBitmap(bmp, panFill.ClientRectangle);

    //store origo/start point and mark that we're active
    regStart = e.Location;
    inDrag = true;
}

private void panFill_MouseUp(object sender, MouseEventArgs e)
{
    inDrag = false;

    //we're done, clean up resources if any
    if (bmp != null) {
        bmp.Dispose();
        bmp = null; //use as marker for this check
    }

    //clean up by redrawing panel
    Rectangle r = new Rectangle(regStart, _
                                new Size(regOld.X - regStart.X + 1, _
                                         regOld.Y - regStart.Y + 1));
    panFill.Invalidate(r, true);

}

In our MouseMove event we call clear and draw a new rectangle:

private void panFill_MouseMove(object sender, MouseEventArgs e)
{
    if (inDrag) {

        using (Graphics g = panFill.CreateGraphics) {
            g.SmoothingMode = Drawing2D.SmoothingMode.None;

            //we store new pos. here as it's used to calculate
            //delta for what we need to redraw
            regNew = e.Location;
            ClearRegion(g);

            regOld = regNew;
            DrawRegion(g);
            }
        }
}

The function to draw the rectangle is pretty straight forward:

private void DrawRegion(Graphics g)
{
    int x1 = regStart.X;
    int y1 = regStart.Y;
    int x2 = regNew.X;
    int y2 = regNew.Y;

    //block "negative" selection
    if (x1 > x2) {
        x2 = x1;
    }
    if (y1 > y2) {
        y2 = y1;
    }

    //Draw a red rectangle
    g.DrawRectangle(Pens.Red, x1, y1, x2 - x1, y2 - y1);
}

The next method holds all the magic optimized to only draw what is needed. It achieve this by checking the two main lines from origo and if the length has shrunk. If so it calculate the delta for the old position and the new position and only redraws the "gap".

For the two other lines if do the same if the tangent position is the same, if not it redraws the whole line.

private void ClearRegion(Graphics g)
{
    int x1 = regStart.X;
    int y1 = regStart.Y;
    int x2 = regOld.X;
    int y2 = regOld.Y;

    if (x1 > x2) {
        x2 = x1;
    }
    if (y1 > y2) {
        y2 = y1;
    }

    //check left line
    if (regNew.Y < y2) {
        Rectangle rectdst = new Rectangle(x1, regNew.Y, 1, regOld.Y - regNew.Y);
        g.DrawImage(bmp, rectdst, rectds, GraphicsUnit.Pixel);
    }

    //upper line
    if (regNew.X < x2) {
        Rectangle rectdst = new Rectangle(regNew.X, y1, regOld.X - regNew.X, 1);
        g.DrawImage(bmp, rectdst, rectdst, GraphicsUnit.Pixel);
    }

    //right line
    if (regNew.X != x2 || regNew.Y < y2) {
        int h = 0;
        int y = 0;
        if (regNew.X == x2) {
            y = regNew.Y;
            h = regOld.Y - regNew.Y + 1;
        } else {
            y = regStart.Y;
            h = regOld.Y - regStart.Y + 1;
        }

        Rectangle rectdst = new Rectangle(regOld.X, y, 1, h);
        g.DrawImage(bmp, rectdst, rectdst, GraphicsUnit.Pixel);
    }

    //bottom line
    if (regNew.Y != y2 || regNew.X < x2) {
        int w = 0;
        int x = 0;

        if (regNew.Y == y2) {
            x = regNew.X;
            w = regOld.X - regNew.X + 1;
        } else {
            x = regStart.X;
            w = regOld.X - x1 + 1;
        }

        Rectangle rectdst = new Rectangle(x, regOld.Y, w, 1);
        g.DrawImage(bmp, rectdst, rectdst, GraphicsUnit.Pixel);

    }
}

This is the, I would say, the most you can get out of GDI+ on a form. Different approaches involved DirectX and more low-level stuff. By limiting what is redrawn based on need, the speed is optimal.

Also notice that the bitmap copy of the fill panel is PARG which is the fastest type as alpha is pre-multiplied. I am, although not necessary in this case, setting smoothing mode to none so the lines will keep themselves sharp without any aliasing leaking.

On a Windows XP 32-bit, 2 GB memory and Atom CPU this runs smooth as it gets.

Note: original code is written in VB and translated to C#. Some errors may occure - adjust as needed. Add handlers as needed.

The original VB code for those interested:

Public Class Form1

    Private bmp As Bitmap = Nothing
    Private inDrag As Boolean = False
    Private regStart As Point = Point.Empty
    Private regNew As Point = Point.Empty
    Private regOld As Point = Point.Empty

    Public Event RegionSelected(r As Rectangle)

    Private Sub panfill_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles panFill.MouseDown

        bmp = New Bitmap(panFill.ClientSize.Width, panFill.ClientSize.Height, Imaging.PixelFormat.Format32bppPArgb)
        panFill.DrawToBitmap(bmp, panFill.ClientRectangle)

        regStart = e.Location

        inDrag = True

    End Sub
    Private Sub panFill_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles panFill.MouseMove

        If inDrag Then
            Using g As Graphics = panFill.CreateGraphics

                g.SmoothingMode = Drawing2D.SmoothingMode.None

                regNew = e.Location

                ClearRegion(g)

                regOld = regNew

                DrawRegion(g)

            End Using

        End If

    End Sub
    Private Sub panFill_MouseUp(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles panFill.MouseUp

        inDrag = False

        If bmp IsNot Nothing Then
            bmp.Dispose()
            bmp = Nothing
        End If

        Dim r As New Rectangle(regStart, New Size(regOld.X - regStart.X + 1, regOld.Y - regStart.Y + 1))
        panFill.Invalidate(r, True)

        RaiseEvent RegionSelected(r)

    End Sub
    Private Sub DrawRegion(g As Graphics)

        Dim x1, y1, x2, y2 As Integer
        x1 = regStart.X
        y1 = regStart.Y
        x2 = regNew.X
        y2 = regNew.Y

        If x1 > x2 Then
            x2 = x1
        End If
        If y1 > y2 Then
            y2 = y1
        End If

        g.DrawRectangle(Pens.Red, x1, y1, x2 - x1, y2 - y1)

    End Sub
    Private Sub ClearRegion(g As Graphics)

        Dim x1, y1, x2, y2 As Integer

        x1 = regStart.X
        y1 = regStart.Y
        x2 = regOld.X
        y2 = regOld.Y

        If x1 > x2 Then
            x2 = x1
        End If
        If y1 > y2 Then
            y2 = y1
        End If

        'left line
        If regNew.Y < y2 Then

            Dim rectdst As New Rectangle(x1, regNew.Y, 1, regOld.Y - regNew.Y)
            g.DrawImage(bmp, rectdst, rectdst, GraphicsUnit.Pixel)

        End If

        'upper line
        If regNew.X < x2 Then

            Dim rectdst As New Rectangle(regNew.X, y1, regOld.X - regNew.X, 1)
            g.DrawImage(bmp, rectdst, rectdst, GraphicsUnit.Pixel)

        End If

        'right line
        If regNew.X <> x2 OrElse regNew.Y < y2 Then

            Dim h, y As Integer
            If regNew.X = x2 Then
                y = regNew.Y
                h = regOld.Y - regNew.Y + 1
            Else
                y = regStart.Y
                h = regOld.Y - regStart.Y + 1
            End If

            Dim rectdst As New Rectangle(regOld.X, y, 1, h)
            g.DrawImage(bmp, rectdst, rectdst, GraphicsUnit.Pixel)

        End If

        'bottom line
        If regNew.Y <> y2 OrElse regNew.X < x2 Then

            Dim w, x As Integer
            If regNew.Y = y2 Then
                x = regNew.X
                w = regOld.X - regNew.X + 1
            Else
                x = regStart.X
                w = regOld.X - x1 + 1
            End If

            Dim rectdst As New Rectangle(x, regOld.Y, w, 1)
            g.DrawImage(bmp, rectdst, rectdst, GraphicsUnit.Pixel)

        End If

    End Sub

End Class
于 2012-11-18T06:29:21.920 回答
2

好的,亚历克斯,这里是您要求的代码示例。

首先,您必须在自己绘制的控件上设置一些样式。这是 DoubleBuffered、AllPaintingInWmPaint 和 UserPaint。这样可以避免闪烁(参见此示例)。其次,不要在 MouseMove 事件本身中进行绘制。记住绘制所需的所有数据并调用 Invalidate。在覆盖方法“Control.OnPaint”或事件“Cotrol.Paint”中进行所有绘画。我还添加了一个 KeyPress 处理程序,以便我可以再次关闭您的 RegionForm。

在 MouseDown 事件中,我记得鼠标光标的位置和控件的当前外观(位图)。在 MouseMove 时,我计算鼠标按下位置和当前鼠标光标位置之间的向量(距离)。如果用户转到鼠标向下点的左上角,这也有效。这个矩形是必须绘制的新区域。但我们还必须使旧区域无效。这就是为什么我计算一个包含旧区域和新区域的联合矩形。该联合用于调用“Control.Invalidate”。只需绘制已保存图像的特定部分即可清除旧区域。

public sealed partial class RegionForm : Form
{
    private Bitmap bitmap;
    private bool mouseDown;
    private Rectangle newRegion;
    private Rectangle oldRegion;
    private Point startPoint;

    public RegionForm()
    {
        InitializeComponent();
        Location = Screen.PrimaryScreen.Bounds.Location;
        Size = Screen.PrimaryScreen.Bounds.Size;
        Cursor = Cursors.Cross;
        TopMost = true;
        ShowInTaskbar = false;

        this.SetStyle(
            ControlStyles.UserPaint |
            ControlStyles.AllPaintingInWmPaint |
            ControlStyles.DoubleBuffer, true);
    }

    protected override void OnKeyPress(KeyPressEventArgs e)
    {
        base.OnKeyPress(e);

        if (e.KeyChar == (char)Keys.Escape)
            this.Close();
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);

        bitmap = new Bitmap(this.ClientSize.Width,
                    this.ClientSize.Height,
                    PixelFormat.Format32bppPArgb);

        this.DrawToBitmap(bitmap, this.ClientRectangle);
        startPoint = e.Location;
        mouseDown = true;
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);

        mouseDown = false;

        if (bitmap != null)
        {
            bitmap.Dispose();
            bitmap = null;
        }

        // reset regions
        newRegion = Rectangle.Empty;
        oldRegion = Rectangle.Empty;

        // invalidate all
        Invalidate(true);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);

        if (mouseDown)
        {
            // calculate new region
            var vector = Point.Subtract(e.Location, new Size(startPoint));
            newRegion = new Rectangle(System.Math.Min(startPoint.X, e.Location.X), System.Math.Min(startPoint.Y, e.Location.Y), System.Math.Abs(vector.X), System.Math.Abs(vector.Y));

            // invalidate only the area of interest
            var invalidate = Rectangle.Union(oldRegion, newRegion);
            Invalidate(invalidate, true);
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.SmoothingMode = SmoothingMode.None;

        ClearRegion(e.Graphics, bitmap, oldRegion);
        DrawRegion(e.Graphics, newRegion);

        // remember which region has been handled
        oldRegion = newRegion;
    }

    static void DrawRegion(Graphics g, Rectangle region)
    {
        if (g == null || region == Rectangle.Empty)
            return;

        //Draw a red rectangle
        g.FillRectangle(Brushes.Red, region);
    }

    static void ClearRegion(Graphics g, Bitmap bitmap, Rectangle region)
    {
        if (g == null || region == Rectangle.Empty || bitmap == null)
            return;

        // take only the selected region from the original image and draw that part
        g.DrawImage(bitmap, region, region, GraphicsUnit.Pixel);
    }
于 2013-01-14T11:44:46.840 回答
0

这在很大程度上取决于表单上可见的内容。例如,如果您自己绘制大量自己的对象,则需要在每个鼠标移动事件上使完整的表单无效。

否则,您只需使特定区域无效。然后您需要使用背景颜色清除该区域并调用完全或部分位于该区域中的所有对象以重新绘制它们自己。之后,您可以绘制 DragDrop 光标/图形。

大多数情况下,确定无效区域中的对象太复杂了。

于 2013-01-06T14:58:45.970 回答