双缓冲为何会导致应用程序崩溃?

9
我是一名有用的助手,可以为您翻译文本。

我有几个自定义的(winforms)组件,使用GDI+绘制到屏幕上。

为了防止重绘时出现闪烁,我决定启用双缓冲,所以我在构造函数中添加了一行代码:

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

这个代码在组件(ColorWheel)上运行良好。 当我将相同的代码添加到其他两个(结构类似)组件的构造函数中时,我会遇到一些奇怪的症状:
  1. 当我尝试运行包含该组件的窗体时,会出现参数异常Application.Run(new Form());
  2. 如果我切换到设计模式,我会得到一个关于组件有未处理的异常与参数有关的错误。
无论我是否为它们中的一个或全部打开双缓冲,似乎都没有关系,它仍然可以在ColorWheel上工作,但在其他组件上不起作用。
值得一提的是,我也尝试了一些其他双缓冲技术。
是什么导致双缓冲可以在一个组件上工作,而在其他组件上不起作用?

编辑:这里是运行时症状的异常细节:

System.ArgumentException未被处理。消息=参数无效。源=System.Drawing。堆栈跟踪: 在System.Drawing.Graphics.GetHdc() 在System.Drawing.BufferedGraphics.RenderInternal(HandleRef refTargetDC,BufferedGraphics buffer) 在System.Drawing.BufferedGraphics.Render() 在System.Windows.Forms.Control.WmPaint(Message& m) 在System.Windows.Forms.Control.WndProc(Message& m) 在System.Windows.Forms.ScrollableControl.WndProc(Message& m) 在System.Windows.Forms.UserControl.WndProc(Message& m) 在System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) 在System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) 在System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd,Int32 msg,IntPtr wparam,IntPtr lparam) 在System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg) 在System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID,Int32 reason,Int32 pvLoopData) 在System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason,ApplicationContext context) 在System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason,ApplicationContext context) 在System.Windows.Forms.Application.Run(Form mainForm) 在TestForm.Program.Main()中D:\ Documents and Settings \ Tom Wright \ My Documents \ Visual Studio 2010 \ Projects \ ColourPicker \ TestForm \ Program.cs:第18行 在System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly,String [] args) 在System.AppDomain.ExecuteAssembly(String assemblyFile,Evidence assemblySecurity,String [] args) 在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 在System.Threading.ThreadHelper.ThreadStart_Context(Object state) 在System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback callback,Object state,Boolean ignoreSyncCtx) 在System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback callback,Object state) 在System.Threading.ThreadHelper.ThreadStart() InnerException:


编辑2:导致问题的两个组件之一(更复杂的那个)的OnPaint处理程序:

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

你是否在有问题的控件中重写了OnPaint()方法?如果是这样,那么它看起来是什么样子的? - roken
@roken 我已经编辑了我的问题,包括我的OnPaint处理程序。 - Tom Wright
2个回答

14
您不应该在 Paint 事件期间处置借给您的 Graphics 对象,但这正是您的 using 块错误地做了的。
症状是,下一次发生 Paint 事件时,您会收到相同的 Graphics 对象,但它不再绑定到内存中的 HDC,导致您的堆栈跟踪中所见到的Graphics.GetHdc() 失败。
  1. 可能超出单个 Paint 事件的生命周期(对于双缓冲来说非常可能是这种情况,尽管如果设置了 CS_OWNDC 窗口样式,则在单缓冲也可能发生)。

  2. 可能会有多个 Paint 事件的处理程序。

因此,事件处理程序不应调用 Dispose 或允许 using 块对 Graphics 对象进行处理。相反,.NET 框架在 Paint 事件处理完成后适当清理资源。

1
它已经运行了一年多,但当我将用户控件类移动到自己的文件中时,开始抛出 OP 所得到的错误。你建议的方法有效 - C#有时候真的很奇怪! - Shadow The Spring Wizard
感谢您澄清这一点,在MSDN上有些混淆:您应该始终调用Dispose方法释放任何消耗系统资源的对象,例如Pen和Graphics对象。 - yano
3
当然,这只是指由你创建的物品。毁坏借来的物品对真正的所有者非常不友善。 - Ben Voigt
你值得拥有几瓶啤酒,我的朋友。讲解得非常好。 - user1908746
来这里是为了从真正的问题源头学习。谢谢@ben。 - Hacky

1
你应该在另一台机器上测试这个程序,以确定是不是只有你的电脑出现了问题。通常情况下,这不应该是双缓冲造成的,但请检查一下在 Paint 事件中是否处置了任何不应该处置的元素,或者在代码中是否有任何会在重复执行时出现问题的操作。

2
我认为在Paint事件期间给你的Graphics对象不应该被处理。 - Ben Voigt
@ben是正确的。不要在“g”上使用“using”,因为这会在退出该作用域时处理Graphics对象,但您不是该对象的所有者。 - Jason Williams
谢谢大家。我之前在使用 using (Graphics g = e.Graphics) { },但是去掉它就解决了问题。 - Tom Wright

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接