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;