Существует два способа двойной буферизации для отрисовки WinForms контролов: автоматический и ручной.
Для того чтобы включить автоматическую двойную буферизацию, нужно всего лишь для контрола установить свойство DoubleBuffered = true или вызвать SetStyle(ControlStyles.OptimizedDoubleBuffer, true).
Для ручного же управления двойной буферизацией Framework .NET включает 3 класса:
BufferedGraphicsManager содержит одно статическое свойство
Current, которое возвращает объект
BufferedGraphicsContext для текущего домена приложения.
* Для приложений активно использующих анимацию может быть эффективнее создать новый экземпляр
BufferedGraphicsContext вместо того чтобы использовать
BufferedGraphicsManager.Current. В этом случае по завершению работы с объектом, его необходимо уничтожить явным образом:
BufferedGraphicsContext myContext = new BufferedGraphicsContext();
// использование
myContext.Dispose();
или
using(BufferedGraphicsContext myContext =
new BufferedGraphicsContext()) {
// использование
}
BufferedGraphicsContext имеет один метод
Allocate и одно свойство
MaximumBuffer.
Allocate(Graphics targetGraphics, Rectangle targetRectangle) -- создает новый
BufferedGraphics на основе переданного через параметр
targetGraphics размером
targetRectangle.
Как работает метод Allocate
Исходный код метода:
public BufferedGraphics Allocate(Graphics targetGraphics, Rectangle targetRectangle)
{
if (this.ShouldUseTempManager(targetRectangle))
return this.AllocBufferInTempManager(targetGraphics, IntPtr.Zero, targetRectangle);
else
return this.AllocBuffer(targetGraphics, IntPtr.Zero, targetRectangle);
}
Внутри метода происходит проверка
MaximumBuffer и
targetRectangle (методом ShouldUseTempManager**). Если
требуемый размер полотна превышает заданный полем
MaximumBuffer , то вызывается метод
AllocBufferInTempManager. Этот метод внутри себя создает новый экземпляр класса
BufferedGraphicsContext, вызывает у него метод
AllocBuffer и возвращает полученный от него объект класса
BufferedGraphics.
***
Если же
targetRectangle не превышает MaximumBuffer, то тогда временный объект не создаётся, а метод AllocBuffer вызывается непосредственно у текущего объекта (т.е. у самого себя).
Метод
AllocBuffer внутри себя вызывает метод
CreateBuffer, для создания нового экземпляра
Graphics, оборачивает его в
BufferedGraphics и сохраняет в переменную объекта
buffer. Эта переменная используется для того, чтобы в дальнейшем при уничтожении экземпляра
BufferedGraphicsContext (методом
Dispose), уничтожить связанный с ним экземпляр
BufferedGraphics.
Стоит заметить что объект
BufferedGraphics также хранит в себе ссылку на создавший его
BufferedGraphicsContext в закрытой переменной.
---------------------------------------------------------------
* Код класса
BufferedGraphicsManager выглядит следующим образом:
public sealed class BufferedGraphicsManager
{
private static BufferedGraphicsContext bufferedGraphicsContext;
public static BufferedGraphicsContext Current
{
get
{
return BufferedGraphicsManager.bufferedGraphicsContext;
}
}
static BufferedGraphicsManager()
{
AppDomain.CurrentDomain.ProcessExit +=
new EventHandler(BufferedGraphicsManager.OnShutdown);
AppDomain.CurrentDomain.DomainUnload +=
new EventHandler(BufferedGraphicsManager.OnShutdown);
// В СТАТИЧЕСКОМ КОНСТРУКТОРЕ ПРОСТО СОЗДАЕТСЯ НОВЫЙ
// НОВЫЙ ОБЪЕКТ BufferedGraphicsContext
BufferedGraphicsManager.bufferedGraphicsContext =
new BufferedGraphicsContext();
}
private BufferedGraphicsManager()
{
}
[PrePrepareMethod]
private static void OnShutdown(object sender, EventArgs e)
{
BufferedGraphicsManager.Current.Invalidate();
}
}
** Исходный код метода
ShouldUseTempManager():
private bool ShouldUseTempManager(Rectangle targetBounds)
{
return targetBounds.Width * targetBounds.Height > this.MaximumBuffer.Width * this.MaximumBuffer.Height;
}
*** При этом созданный внутри метода
AllocBufferInTempManager экземпляр BufferedGraphicsContext не уничтожается, ответственность за его уничтожение ложится на пользователя получившего объект
BufferedGraphics, который хранит обратную ссылку, при уничтожении которого и уничтожается породиший его объект BufferedGraphicsContext.
Как это работает? Исходный код метода
AllocBufferInTempManager:
private BufferedGraphics AllocBufferInTempManager(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle)
{
BufferedGraphicsContext bufferedGraphicsContext = (BufferedGraphicsContext) null;
BufferedGraphics bufferedGraphics = (BufferedGraphics) null;
try
{
// Создаем новый "временный" контент
bufferedGraphicsContext = new BufferedGraphicsContext();
if (bufferedGraphicsContext != null) // Всегда ДА
{
// Создаем в нем буферизированное полотно, которое внутри себя (переменная context) хранит
// ссылку на него же
bufferedGraphics = bufferedGraphicsContext.AllocBuffer(targetGraphics, targetDC, targetRectangle);
// ВОТ, этот флаг указывает, что объект bufferedGraphics
// при своем уничтожении должен уничтожить породившего его
// bufferedGraphicsContext:
bufferedGraphics.DisposeContext = true;
}
}
finally
{
// В нормальной ситуации это условие не выполняется
if (bufferedGraphicsContext != null
&& (bufferedGraphics == null
|| bufferedGraphics != null && !bufferedGraphics.DisposeContext))
bufferedGraphicsContext.Dispose();
}
return bufferedGraphics;
}