четверг, 17 мая 2012 г.

Двойная буферизация в .NET

Существует два способа двойной буферизации для отрисовки 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;
    }

Комментариев нет:

Отправить комментарий