Из-за чего двойная буферизация может убить мое приложение?

У меня есть несколько пользовательских компонентов (winforms), которые выводятся на экран с помощью GDI +.

Чтобы предотвратить мерцание при перерисовке, я решил включить двойную буферизацию, поэтому добавил в свой конструктор строку:

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

Что отлично работает с этим компонентом (ColourWheel). Когда я добавляю ту же строку в конструктор любого из двух других (аналогичных по структуре) компонентов, я получаю пару странных симптомов:

  1. Когда я пытаюсь запустить форму с включенным компонентом, я получаю исключение аргумента на Application.Run(new Form());.
  2. Если я переключаюсь в режим разработки, я получаю сообщение об ошибке компонента, имеющего необработанное исключение, связанное с параметром.

Кажется, не имеет значения, включаю ли я двойную буферизацию на одном или на всех, она по-прежнему работает на ColourWheel, но не на других.

Для записи, я также пробовал несколько других double методы буферизации.

Что могло заставить двойную буферизацию работать на одном компоненте, но не на других?


РЕДАКТИРОВАТЬ: Вот подробности исключения из симптома во время выполнения:

Необработанное исключение System.ArgumentException Сообщение = Параметр недействителен. Источник = System.Drawing StackTrace: в System.Drawing.Graphics.GetHdc () в System.Drawing.BufferedGraphics.RenderInternal (HandleRef refTargetDC, буфер BufferedGraphics) в 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 (сообщение & m) в System.Windows.Forms.Control.ControlNativeWindow.WndProc (сообщение & 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.FPushMessageLo op (IntPtr dwComponentID, причина Int32, Int32 pvLoopData) в System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner (причина Int32, контекст ApplicationContext) в System.Windows.Forms.Application.ThreadContext.RunMessageLoop (Int32) в причине, в контексте ApplicationContext. System.Windows.Forms.Application.Run (форма mainForm) в TestForm.Program.Main () в D: \ Documents and Settings \ Tom Wright \ My Documents \ Visual Studio 2010 \ Projects \ ColourPicker \ TestForm \ Program.cs: line 18 в System.AppDomain._nExecuteAssembly (сборка RuntimeAssembly, аргументы String []) в System.AppDomain.ExecuteAssembly (String AssemblyFile, Evidence assemblySecurity, String [] args) в Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly () в System.Threading .ThreadHelper.ThreadStart_Context (состояние объекта) в System.Threading.ExecutionContext.Run (контекст выполнения ExecutionContext, обратный вызов ContextCallback, состояние объекта, B oolean ignoreSyncCtx) в System.Threading.ExecutionContext.Run (ExecutionContext executionContext, обратный вызов ContextCallback, состояние объекта) в 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);
               }
           }
        }
}

person Tom Wright    schedule 03.02.2012    source источник
comment
Вы отменяете OnPaint () в некорректных элементах управления? Если да, то как это выглядит?   -  person roken    schedule 04.02.2012
comment
@roken Я редактировал свой вопрос, добавляя обработчик OnPaint.   -  person Tom Wright    schedule 04.02.2012


Ответы (2)


Вы не должны избавляться от Graphics объекта, предоставленного вам во временное пользование во время Paint события, и это то, что ваш using блок делает неправильно.

Симптомом является то, что при следующем запуске события Paint вы получите тот же объект Graphics обратно, но он больше не привязан к HDC в памяти, что приведет к сбою Graphics.GetHdc(), как это видно в трассировке стека.

  1. Возможно, он переживет одно событие Paint (и это очень вероятно в случае с двойной буферизацией, хотя это также возможно с одной буферизацией, если установлен стиль окна CS_OWNDC).

  2. Для события Paint может быть несколько обработчиков.

Таким образом, обработчики событий не должны вызывать Dispose на объектах Graphics или позволять блоку using делать это. Вместо этого платформа .NET при необходимости очищает ресурсы после Paint обработки события.

person Ben Voigt    schedule 04.02.2012
comment
Он работал более года, затем, когда я переместил класс пользовательского элемента управления в его собственный файл, начал выдавать ошибку, которую получил OP. То, что вы предложили, сработало - C # иногда может быть действительно странным! - person Shadow Wizard Wearing Mask V2; 08.04.2014
comment
Спасибо за разъяснение, довольно запутанно на MSDN: Вы всегда должны вызывать Dispose для любых объектов, потребляющих системные ресурсы, таких как объекты Pen и Graphics. - person yano; 16.10.2020
comment
@yano: Конечно, это означает только те, которые ВЫ создали. Уничтожать одолженные вещи очень недоброжелательно по отношению к настоящему владельцу. - person Ben Voigt; 16.10.2020

Вы должны протестировать это на другом компьютере, чтобы узнать, ваш компьютер или нет. По большей части этого не должно происходить в результате двойной буферизации, но проверьте, удаляете ли вы какие-либо элементы, которых не должно быть в событии Paint, или делаете что-либо в коде, что может вызвать проблемы, если сделать это дважды.

person corylulu    schedule 03.02.2012
comment
Я не думаю, что вы должны избавляться от объекта Graphics, предоставленного вам во время события Paint. - person Ben Voigt; 04.02.2012
comment
@ben прав. Не делайте «using» на «g», так как это приведет к удалению объекта Graphics при выходе из этой области, но вы не являетесь «владельцем» этого объекта. - person Jason Williams; 04.02.2012
comment
Спасибо, парни. Я делал using (Graphics g = e.Graphics) { }. Удаление этого было ключом. - person Tom Wright; 04.02.2012