0
votes

I have written a custom control that renders some graphics. The graphics itself is fairly expensive to render but once rendered it rarely changes. The problem I have is that if you move the mouse really fast over the graphics surface it keeps calling the control's overridden Paint method which then incurs a high CPU penalty:

private void UserControl1_Paint(object sender, PaintEventArgs e)

What techniques could be used to avoid this situation or minimise any unnecessary redrawing since the graphics/image underneath the mouse pointer is not actually changing?

6
thanks for the answers so far. I should have added that I use control.Invalidate when I know something has changed. The Paint method is being called by .NET (not me!) when the mouse moves. - freddy smith
OnPaint is not called when the mouse moves over a control, something else is going on. - Ed S.
I just tested with a simple project to be sure and the Paint is not called by mouse move unless I call Invalidate in OnMouseMove (as others have mentioned here). Are you sure there isn't some other condition in code that makes the calls to invalidate when they shouldn't be called? Try to add some logging code to log the calls to invalidate and mouse move and see if they are called as you intend to. - rslite
I think that we need to see some code in order to continue here. - Ed S.

6 Answers

3
votes

EDIT: After seeing your edit, I can assure you that OnPaint is not called by default when the mouse moves over a control. Something in your code is definitely causing the re-paint, you just don't see it yet. Perhaps posting some of your code would help us find the problem.

Are you invalidating your control on MouseMove? That is likely a bad idea, and if you really needed to do it (i.e., you are making a graphics editor or something), you would have to be smart about how large a region was actually re-drawn. So, solution; don't paint your control in MouseMove.

Otherwise, I would not expect OnPaint to fire when the mouse moves over the control. You can also just generate the image once and then blt it to the Graphics object until it needs to be re-generated.

2
votes

You can use a buffer image on which to draw when something is changed and in the Paint method just copy the image on the screen. Should be pretty fast. Also you can use the clip region to copy only the part that needs updating. This should reduce CPU usage.

You can also use an IsDirty flag to know when to update the buffer image (i.e. fully redraw it) if needed.

2
votes

You should not call Paint directly.

Instead, call Invalidate (Control.Invalidate). This queues the need to be repainted, and Windows will take care of the call itself. This way, a lot of rapid invalidations (repaint requests) can be serviced by one call to Paint.

1
votes

One of the things that can cause high CPU load in Paint method is improper (unmanaged) resource release. Make sure you do Dispose() all of the pens, brushes and so forth (probably all of the System.Drawing classes instances has some unmanaged resource tied to them). Basically you should Dispose() of the object as soon as you have finished working with it. Do not cache or anything - GDI+ resources are system resources and should be returned to system as soon as possible. Acquiring them (creating new Brush class instance for example) should be pretty quick, but I do not have anything to back this statement up at the moment.

0
votes

Also check the Clip Rectange in the PaintArgs event argument so that you only repaint the area that needs to get updated instead of re-drawing the entire image.

-1
votes

You can use this code to supress and resume redrawing of a control :] Good luck.

   using System;
    using System.Windows.Forms;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;

namespace pl.emag.audiopc.gui {
    // ************************************************************************
//`enter code here`
    // ************************************************************************
    public class PaintingHelper {
        // ********************************************************************
//
        // ********************************************************************
        public static void SuspendDrawing(Control parent) {
            SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
        }
        // ********************************************************************
//
        // ********************************************************************
        public static void ResumeDrawing(Control parent) {
            SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
            parent.Refresh();
        }
        // ********************************************************************
//
        // ********************************************************************
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, 
            bool wParam, Int32 lParam);
        // ********************************************************************
        //
        // ********************************************************************
        private const int WM_SETREDRAW = 11;
    }
}