GDI +绘图矩形很慢

时间:2012-11-13 13:03:29

标签: c# performance gdi+

我开发了一个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提供的任何帮助,提前感谢。

更新

Ken的完整代码:

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

3 个答案:

答案 0 :(得分:3)

您需要从MouseMove事件中提取它。在某些计算机/分辨率上,整个控件的无效将会很慢。

更新:

此代码将以高度优化的方式绘制和清除所选区域的矩形。

例程在MouseMove上绘制一个矩形,但只根据需要清除行的片段。也就是说,如果该区域小于先前的大小,则仅从存储的位图中清除行的外部部分。

由于这个原因,不需要启用双缓冲。

为了将窗口的缓冲位图作为源副本放在正确的位置,我们需要实现一个小技巧:

  • 在表单中添加Panel(在此示例中为panFill)并将dock设置为fill。
  • 将所有控件/背景图片添加到面板,而不是表单

现在我们可以从Panel而不是Form复制我们稍后需要的位图。我们需要这个的原因是使用this.DrawBitmap()也会为位图绘制边框和标题栏,我们不希望这样。使用Panel将消除此问题。

在表格的全局范围内,我们设置:

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

在Panel的MouseDown / Up事件中:

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);

}

在我们的MouseMove事件中,我们调用clear并绘制一个新矩形:

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);
            }
        }
}

绘制矩形的功能非常简单:

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);
}

下一个方法包含所有优化的魔法,只绘制所需的内容。它通过检查origo中的两条主线以及长度是否缩小来实现此目的。如果是这样,它计算旧位置和新位置的delta,只重绘“间隙”。

对于其他两条线,如果切线位置相同则相同,否则重绘整条线。

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);

    }
}

我想说,这是你在表格上获得的最多GDI +。不同的方法涉及DirectX和更低级别的东西。通过限制根据需要重绘的内容,速度是最佳的。

另请注意,填充面板的位图副本是PARG,这是最快的类型,因为alpha是预乘的。我虽然在这种情况下不是必需的,但是将平滑模式设置为无,这样线条就会保持清晰,没有任何混叠泄漏。

在Windows XP 32位,2 GB内存和Atom CPU上,它运行顺畅。

注意:原始代码用VB编写并转换为C#。可能会发生一些错误 - 根据需要进行调整。根据需要添加处理程序。

感兴趣的人的原始VB代码:

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

答案 1 :(得分:2)

好的Alex,这里是你要求的代码示例。

首先,您必须将一些样式设置到您自己绘制的控件上。这是DoubleBuffered,AllPaintingInWmPaint和UserPaint。这可以避免闪烁(参见this example)。其次,不要在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);
    }

答案 2 :(得分:0)

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

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

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