Delphi TCanvas объект поврежден после использования из DLL, как восстановить?
Есть проблемы. У меня есть форма с холстом, мне нужен доступ к этому холсту из DLL по его ручке. Я делаю это таким образом:
из длл
canvas := TCanvas.Create;
try
canvas.Handle := handle;
// do some painting on this canvas
finally
canvas.free;
end;
Работает хорошо, я рисую то, что мне нужно от dll. Но этот трюк имеет побочный эффект. После рисования из dll форма теряет настройки шрифта (кстати, я не использовал шрифты при рисовании из dll, только несколько ректов) и когда я рисую на том же холсте из основной формы, даже если я выполняю напрямую canvas.font.size:= ... canvas.font.name:= ...; до canvas.TextOut, шрифт не меняется. Линии, заливка и другие картины в порядке. Но шрифты становятся поврежденными (иногда нет, но в основном).
Есть ли способ сбросить / переустановить объект TCanvas формы?
3 ответа
Canvas не имеет функции сброса, но вы можете попросить API сохранить состояние контекста устройства холста и восстановить его после рисования.
var
SavedDC: Integer;
...
SavedDC := SaveDC(handle);
try
canvas := TCanvas.Create;
try
canvas.Handle := handle;
// do some painting on this canvas
finally
canvas.free;
end;
finally
RestoreDC(handle, SavedDC);
end;
Ответ Реми объясняет, как вы теряете состояние контекста устройства. Почему это не всегда происходит, зависит от времени, я верю. Если форма вошла в новый цикл рисования в то время, когда ее холст использует свой шрифт, все должно быть хорошо, поскольку она работает с вновь полученным и настроенным контекстом устройства.
Причина вашей формы Canvas
получает "поврежден", потому что DLL TCanvas
объект заменяет оригинал HFONT
, HBRUSH
и / или HPEN
объекты, которые уже были назначены HDC
, но затем присваивает фондовые объекты GDI (из GetStockObject()
) во время его уничтожения, вместо переназначения исходных объектов GDI, которые были ранее назначены. Это происходит в TCanvas.DeselectHandles()
метод, когда TCanvas.Handle
Значение свойства изменяется (в том числе при уничтожении):
var
...
StockPen: HPEN;
StockBrush: HBRUSH;
StockFont: HFONT;
...
procedure TCanvas.DeselectHandles;
begin
if (FHandle <> 0) and (State - [csPenValid, csBrushValid, csFontValid] <> State) then
begin
SelectObject(FHandle, StockPen); // <-- STOCK PEN!
SelectObject(FHandle, StockBrush); // <-- STOCK BRUSH!
SelectObject(FHandle, StockFont); // <-- STOCK FONT!
State := State - [csPenValid, csBrushValid, csFontValid];
end;
end;
...
initialization
...
StockPen := GetStockObject(BLACK_PEN);
StockBrush := GetStockObject(HOLLOW_BRUSH);
StockFont := GetStockObject(SYSTEM_FONT);
...
Чтобы сделать "сброс" формы Canvas
после выхода из функции DLL вам придется обмануть Canvas
чтобы знать, что его объекты GDI больше не присваиваются HDC
так что он может очистить соответствующие флаги от его внутреннего State
член и переназначить его объекты GDI по мере необходимости. Вы также можете:
вручную вызвать
OnChange
обработчики событийCanvas.Font
,Canvas.Brush
а такжеCanvas.Pen
свойства:procedure TMyForm.FormPaint(Sender: TObject); begin try CallDllFunc(Canvas.Handle); finally Canvas.Font.OnChange(nil); Canvas.Brush.OnChange(nil); Canvas.Pen.OnChange(nil); end; end;
Или же:
type TGraphicObjectAccess = class(TGraphicObject) end; procedure TMyForm.FormPaint(Sender: TObject); begin try CallDllFunc(Canvas.Handle); finally TGraphicObjectAccess(Canvas.Font).Changed; TGraphicObjectAccess(Canvas.Brush).Changed; TGraphicObjectAccess(Canvas.Pen).Changed; end; end;
Вы можете временно удалить, а затем переназначить оригинал
HDC
, который имеет аналогичное влияние наState
флаги:procedure TMyForm.FormPaint(Sender: TObject); var DC: HDC; begin try CallDllFunc(Canvas.Handle); finally DC := Canvas.Handle; Canvas.Handle := 0; Canvas.Handle := DC; end; end;
использование
SaveDC()
а такжеRestoreDC()
, как показано в ответе Сертака.
Стандартный класс TCanvas не очень подходит для рисования на "заимствованных" полотнах. То есть, беря контекст устройства (например, из какого-либо другого объекта canvas) и используя его в другом, отдельном TCanvas из-за того, как он управляет объектами GDI (полагаясь на "владение" HDC и состояние объектов GDI в этом DC, что оно работает с).
В простых случаях это может работать, но в остальном проблемы, с которыми вы сталкиваетесь, не редкость. В частности, с DLL могут возникнуть проблемы, связанные с тем фактом, что в TCanvas существуют механизмы, основанные на "глобальном" списке полотен (CanvasList), которые необходимо контролировать и синхронизировать в ответ на системные изменения.
то есть в DLL будет CanvasList, который представляет собой список холстов в DLL, отдельный от CanvasList в процессе приложения хоста. Приложение CanvasList не будет включать экземпляры TCanvas в DLL, и наоборот. Если в DLL есть TCanvas, который на самом деле является "дубликатом" TCanvas в приложении (с использованием того же HDC), то должно быть очевидно, как могут возникнуть проблемы.
Я вижу два пути продвижения вперед в вашем случае, которые можно использовать по отдельности или вместе.
Вы не предоставили подробную информацию обо всем своем коде рисования, поэтому трудно сказать, что, вероятно, является источником вашей проблемы. Однако вы можете легко идентифицировать это, закомментировав весь код рисования (между попыткой и, наконец, в вашей программе рисования). Это должно исправить вашу проблему со шрифтом. Если вы затем повторно активируете свой код рисования постепенно (построчно или раздел за разделом), вы можете точно определить, какие операции рисования вызывают проблему, и оттуда (потенциально) определить решение.
Если ваши операции рисования очень просты (просто рисуйте несколько прямоугольников, как вы говорите), то вы можете использовать простые вызовы GDI для рисования в проблемных случаях (или всех них) вместо использования холста. В этом случае я бы предложил передать дескриптор окна в свою DLL, а не в контекст устройства. Ваша DLL должна затем получить свой собственный контекст устройства через GetDC() и выпустить его через ReleaseDC(), когда закончите. Вам нужно будет управлять объектами GDI при рисовании в контексте устройства самостоятельно, но затем вы можете быть уверены, что независимо от того, что вы делаете, вы не вмешиваетесь в объекты GDI, управляемые чертежом TCanvas в том же окне.
Другая возможность заключается в использовании SaveDC()
а также RestoreDC()
, как показано в ответе Сертака.