Delphi/GDI+: Когда контекст устройства создается / уничтожается?

Обычно используя GDI+ в Delphi, вы можете использовать TPaintBox и рисовать во время события OnPaint:

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
   g: TGPGraphics;
begin
   g := TGPGraphics.Create(PaintBox1.Canvas.Handle);
   try
      g.DrawImage(FSomeImage, 0, 0);
   finally
      g.Free;
   end;
end;

Проблема этой парадигмы состоит в том, что создание разрушающего объекта Graphics каждый раз является расточительным и неэффективным. Кроме того, в GDI+ имеется несколько конструкций, которые вы можете использовать только при наличии постоянного объекта Graphics.

Проблема, конечно, в том, когда я могу создать этот графический объект? Мне нужно знать, когда дескриптор станет доступным, а затем, когда он больше не действителен. Мне нужна эта информация, чтобы я мог создавать и уничтожать мой графический объект.


Решение Попытка № 1

я могу решить проблему создания, создав ее, когда это действительно необходимо - в первый раз вызывается цикл рисования:

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
   if FGraphics = nil then
      FGraphics := TGPGraphics.Create(PaintBox1.Canvas.Handle);

   FGraphics.DrawImage(FSomeImage, 0, 0);
end;

Но я должен знать, когда контекст устройства больше не действителен, чтобы я мог уничтожить мой объект FGraphcis, чтобы он создавался заново в следующий раз, когда это необходимо. Если по какой-то причине контекст устройства TPaintBox будет воссоздан, я буду рисовать недопустимый контекст устройства при следующем вызове OnPaint.

Какой механизм в Delphi предназначен для меня, чтобы знать, когда дескриптор контекста устройства TPaintBox создается, уничтожается или воссоздается?

4 ответа

Вы не можете сделать это со стандартным TPaintBox, потому что TPaintBox имеет Canvas типа TControlCanvas, членами которого являются следующие:

TControlCanvas = class(TCanvas)
private
  ...
  procedure SetControl(AControl: TControl);
protected
  procedure CreateHandle; override;
public
  procedure FreeHandle;
  ...
  property Control: TControl read FControl write SetControl;
end;

Проблема в том, что FreeHandle и SetControl не являются виртуальными.

Но: TControlCanvas создан и назначен здесь:

 constructor TGraphicControl.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   FCanvas := TControlCanvas.Create;
   TControlCanvas(FCanvas).Control := Self;
 end;

Итак, вы можете создать нисходящий TMyControlCanvas, у которого есть виртуальные методы, и TMyPaintBox, который назначает Canvas следующим образом:

 constructor TMyPaintBox.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   FCanvas.Free;
   FCanvas := TMyControlCanvas.Create;
   TMyControlCanvas(FCanvas).Control := Self;
 end;

Затем вы можете использовать методы в TMyControlCanvas для динамического создания и уничтожения вашей TGPGraphics.

Это должно помочь вам.

--jeroen

Обнаружение создания легко. Просто переопределить CreateHandle в потомке TControlCanvas и поставьте свою вместо стандартной по умолчанию, как показывает ответ Йероена. Обнаружение разрушения сложнее.

Один из способов избежать этой проблемы - проверить, совпадает ли дескриптор TGpGraphics с дескриптором блока рисования, поэтому вместо того, чтобы обнаруживать момент освобождения контекста устройства, вы просто проверяете его, прежде чем узнаете.

if not Assigned(FGraphics)
    or (FGraphics.GetHDC <> PaintBox1.Canvas.Handle) then begin
  FGraphics.Free;
  FGraphics := TGpGraphics.Create(PaintBox1.Canvas.Handle);
end;

Это, вероятно, не надежно, хотя; значения дескриптора могут быть использованы повторно, поэтому, хотя значение HDC может быть одинаковым между двумя проверками, нет гарантии, что оно по-прежнему ссылается на один и тот же объект контекста устройства ОС.


TCanvas базовый класс никогда не очищает свой собственный Handle свойство, поэтому все, что делает недействительным холст, должно происходить извне. TControlCanvas очищает его Handle собственность, когда ее Control свойство переопределяется, но обычно это происходит только при создании элемента управления, поскольку TControlCanvas экземпляры редко делятся. Тем не мение, TControlCanvas экземпляры работают из пула дескрипторов контекста устройства, хранящихся в CanvasList, Всякий раз, когда один из них нуждается в DC (в TControlCanvas.CreateHandle), это вызывает FreeDeviceContext освободить место в кэше холста для ручки, которую он собирается создать. Эта функция вызывает (не виртуальный) FreeHandle метод. Размер кеша равен 4 (см. CanvasListCacheSize), так что если у вас есть несколько потомков TCustomControl или же TGraphicControl в вашей программе высока вероятность того, что вы получите ошибки кеширования, когда более четырех из них нужно будет перерисовать одновременно.

TControlCanvas.FreeHandle не является виртуальным и не вызывает никаких виртуальных методов. Хотя вы можете сделать потомком этого класса и предоставить ему виртуальные методы, остальная часть VCL будет продолжать вызывать не виртуальные методы, не обращая внимания ни на одно из ваших дополнений.


Вместо того, чтобы пытаться определить, когда был выпущен контекст устройства, вам лучше использовать другой конструктор TGpGraphics. Например, используйте тот, который использует дескриптор окна вместо дескриптора DC. Разрушение оконной ручки намного легче обнаружить. Для одноразового решения назначьте свой собственный метод TPaintBox.WindowProc собственность и следить за wm_Destroy Сообщения. Если вы делаете это часто, то создайте класс-потомок и переопределите DestroyWnd,

Падение производительности, которое вы берете за создание / уничтожение графического объекта, минимально. Это намного перевешивает снижение производительности при использовании команд рисования gdi +. Ни о чем, imo, не стоит беспокоиться, когда дело доходит до рисования пользовательских интерфейсов, потому что пользователь все равно не заметит. И, честно говоря, может быть очень неудобно пытаться переносить графический объект и отслеживать изменения в дескрипторе DC (особенно, если вы инкапсулируете графические процедуры в своем собственном наборе классов).

Если вам нужно кешировать растровые изображения, то вы можете подумать о том, чтобы создать растровое изображение, которое вы хотите кешировать, с помощью GDI+ (сделайте его подходящим по размеру и с любыми настройками сглаживания, которые вы хотите), сохранив его в поток tmemorystream и затем, когда вам это нужно загрузить его из потока и нарисовать его, используя хороший старый битблт. Это будет намного быстрее, чем использование Graphics.DrawImage. Я говорю на порядок быстрее.

procedure TGraphicControl.WMPaint(var Message: TWMPaint);
begin
  if Message.DC <> 0 then
  begin
    Canvas.Lock;
    try
      Canvas.Handle := Message.DC;
      try
        Paint;
      finally
        Canvas.Handle := 0;
      end;
    finally
      Canvas.Unlock;
    end;
  end;
end;

Canvas.Handle := Message.DC;
Другие вопросы по тегам