Delphi / GDI +: Когда контекст устройства создается / уничтожается? - PullRequest
3 голосов
/ 24 октября 2009

Обычно, используя 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;

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

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


Решение Попытка Nº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 мне нужен, чтобы знать, когда дескриптор контекста устройства *1043* TPaintBox создается, уничтожается или воссоздается?

Ответы [ 4 ]

3 голосов
/ 24 октября 2009

Вы не можете использовать стандартный 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.

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

- Йерун

2 голосов
/ 26 октября 2009

Обнаружение создания легко. Просто переопределите 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.

1 голос
/ 31 октября 2009

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

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

0 голосов
/ 14 января 2012
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;
...