GDI+ Drawing Rectangle Is Slow

杀马特。学长 韩版系。学妹 提交于 2019-12-11 13:45:04

问题


I've developed a Windows Forms Application to draw a rectangular region relevant to the user's mouse position as he or she clicks, holds and drags the mouse.

The class is very simple and is as followed:

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

On my computer and most others, everything runs fine. However, after concluding some testing on various machines I found that some computers where struggling to draw the rectangle (noticeably slow, and choppy). I think I have identified the issue however I am in need of validation and a resolution to this issue.

I believe that the problem lies with the fact that every time the mouse move event is raised, the entire form is drawn again, and again. On weak computers with large resolutions, this is very strenuous.

After conducting some research, I think that the solution is to only draw the part of the rectangle that's changed and not the entire form, although I'm clueless as to how to do this. I'd really appreciate any help that SO can offer, thanks in advance.

Update

Complete code for 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;
    }

}

Solution Download: http://www.mediafire.com/?b9ql4pzh69u10n4


回答1:


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



回答2:


Ok Alex, here to code example you requested.

First, you have to set some styles onto the control which you paint yourself. This is DoubleBuffered, AllPaintingInWmPaint and UserPaint. This avoids flickering (see this example). Second, don't paint in the MouseMove event itself. Remember all data that you need for painting and call Invalidate. Do all painting in override method "Control.OnPaint" or event "Cotrol.Paint". I also added a KeyPress handler, so that I can close your RegionForm again.

On MouseDown event I remember the location of the mouse cursor and the current look (bitmap) of the control. While MouseMove I calculate the vector (distance) between the mouse down location and the current mouse cursor position. This also works if the user goes to the upper left side of the mouse down point. This rectangle is the new region that has to be drawn. But we also have to invalidate the old region. This is why I caluclate a union rectangle which contains both, the old and the new region. This union is used for calling "Control.Invalidate". Clearing the old region is simply done by drawing only the specific part of the saved image.

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



回答3:


This highly depends on the content that is visible on the form. For example, if you are drawing lots of own objects by yourself, you need to invalidate the complete form on every mouse-move event.

Otherwise you need to invalidate a specific region only. Then you need to clear that region with a background color and call all objects that are fully or partly located in that region to re-draw themselves. Afterwards you can draw your DragDrop cursor/graphic.

Mostly it is too complicated to determine the objects that are in the invalidated region.



来源:https://stackoverflow.com/questions/13361234/gdi-drawing-rectangle-is-slow

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!