Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What could cause Double Buffering to kill my app?

I have a few custom (winforms) components that draw to the screen using GDI+.

To prevent flickering on repaint, I decided to enable double buffering, so I added a line to my constructor:

public ColourWheel()
{
    InitializeComponent();
    this.DoubleBuffered = true;
}

Which works fine on this component (ColourWheel). When I add the same line to the constructor of either of my other two (similarly structured) components, I get a couple of strange symptoms:

  1. When I try to run a form with the component on, I get an Argument Exception on Application.Run(new Form());.
  2. If I switch to design mode, I get an error about the component having an unhandled exception to do with a parameter.

It doesn't seem to matter whether I turn double buffering on one or all of them, it still works on the ColourWheel, but not the others.

For the record, I've also tried a few other double buffering techniques.

What could be causing double buffering to work on one component, but not others?


EDIT: Here's the exception detail from the run-time symptom:

System.ArgumentException was unhandled Message=Parameter is not valid. Source=System.Drawing StackTrace: at System.Drawing.Graphics.GetHdc() at System.Drawing.BufferedGraphics.RenderInternal(HandleRef refTargetDC, BufferedGraphics buffer) at System.Drawing.BufferedGraphics.Render() at System.Windows.Forms.Control.WmPaint(Message& m) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ScrollableControl.WndProc(Message& m) at System.Windows.Forms.UserControl.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.Run(Form mainForm) at TestForm.Program.Main() in D:\Documents and Settings\Tom Wright\My Documents\Visual Studio 2010\Projects\ColourPicker\TestForm\Program.cs:line 18 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException:


EDIT 2: The OnPaint handler from one (the more complicated) of the two components that are causing problems:

private void ValueSlider_Paint(object sender, PaintEventArgs e)
{
       using (Graphics g = e.Graphics)
       {
           g.DrawImage(this.gradientImage, new Rectangle(0, 0, paintArea.Width, paintArea.Height));
           if (this.showmarker)
           {
               ColourHandler.HSV alt = ColourHandler.RGBtoHSV(new ColourHandler.RGB(this.SelectedColour.R, this.SelectedColour.G, this.SelectedColour.B));
               alt.Saturation = 0;
               alt.value = 255 - alt.value;
               using (Pen pen = new Pen(ColourHandler.HSVtoColour(alt)))
               {
                   pen.Width = (float)MARKERWIDTH;
                   g.DrawRectangle(pen, 0 - pen.Width, this.brightnessPoint.Y - MARKERWIDTH, this.paintArea.Width + (pen.Width * 2), MARKERWIDTH * 2);
               }
           }
        }
}
like image 458
Tom Wright Avatar asked Feb 03 '12 19:02

Tom Wright


2 Answers

You aren't supposed to dispose the Graphics object loaned to you during the Paint event, and that's what your using block improperly does.

The symptom is that the next time the Paint event fires, you get the same Graphics object back, but it is no longer bound to an in-memory HDC, causing Graphics.GetHdc() to fail as seen in your stack trace.

  1. It's possible that it outlives a single Paint event (and this is very likely the case with double-buffering, although it's also possible with single-buffering if the CS_OWNDC window style is set).

  2. There can be more than one handler for the Paint event.

So, event handlers should not call Dispose on the Graphics objects or allow a using block to do so. Instead, the .NET framework cleans up resources as appropriate after Paint event handling is complete.

like image 61
Ben Voigt Avatar answered Nov 15 '22 11:11

Ben Voigt


You should test this on another machine to see if it's just your computer or not. For the most part, this shouldn't happen as a result of double buffering, but check to see if you are disposing any elements you shouldn't be in the Paint event or doing anything in the code that would have issues if done twice.

like image 32
corylulu Avatar answered Nov 15 '22 13:11

corylulu