Normally using GDI+ in Delphi you can use a TPaintBox, and paint during the OnPaint event:
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;
The problem with this paradigm is that creating a destroying a Graphics object each time is wasteful and poorly performing. Additionally, there are a few constructs availabe in GDI+ you can only use when you have a persistent Graphics object.
The problem, of course, is when can i create that Graphics object? i need to know when the handle becomes available, and then when it is no longer valid. i need this information so i can create and destroy my Graphics object.
Solution Attempt NÂș1
i can solve the creation problem by creating it when it is really needed - on the first time the paint cycle is called:
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
if FGraphics = nil then
FGraphics := TGPGraphics.Create(PaintBox1.Canvas.Handle);
FGraphics.DrawImage(FSomeImage, 0, 0);
end;
But i have to know when the device context is no longer valid, so i can destroy my FGraphcis object, so that it is re-created the next time it's needed. If for some reason the TPaintBox's device context gets recreated, i would be drawing on an invalid device context the next time OnPaint is called.
What is the intended mechanism in Delphi for me to know when the device context handle of a TPaintBox is created, destroyed, or re-created?
You can't with the standard TPaintBox because the TPaintBox has a Canvas of type TControlCanvas, for which members relevant to this issue are these:
TControlCanvas = class(TCanvas)
private
...
procedure SetControl(AControl: TControl);
protected
procedure CreateHandle; override;
public
procedure FreeHandle;
...
property Control: TControl read FControl write SetControl;
end;
The problem is that FreeHandle and SetControl are not virtual.
But: the TControlCanvas is created and assigned here:
constructor TGraphicControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCanvas := TControlCanvas.Create;
TControlCanvas(FCanvas).Control := Self;
end;
So what you could do is create a descending TMyControlCanvas that does have virtual methods, and a TMyPaintBox that assigns the Canvas like this:
constructor TMyPaintBox.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCanvas.Free;
FCanvas := TMyControlCanvas.Create;
TMyControlCanvas(FCanvas).Control := Self;
end;
Then you can use the methods in TMyControlCanvas to dynamically create and destroy your TGPGraphics.
That should get you going.
--jeroen
Detecting creation is easy. Just override CreateHandle in a descendant TControlCanvas and put yours in place of the default one as Jeroen's answer demonstrates. Detecting destruction is harder.
One way to avoid the issue is to check whether the TGpGraphics handle is equal to the paint-box's handle, so, rather than detect the moment when the device context is freed, you simply check before you need to know.
if not Assigned(FGraphics)
or (FGraphics.GetHDC <> PaintBox1.Canvas.Handle) then begin
FGraphics.Free;
FGraphics := TGpGraphics.Create(PaintBox1.Canvas.Handle);
end;
This probably isn't reliable, though; handle values are liable to be re-used, so although the HDC value might be the same between two checks, there's no guarantee that it still refers to the same OS device-context object.
The TCanvas base class never clears its own Handle property, so anything that invalidates the canvas must occur externally. TControlCanvas clears its Handle property when its Control property gets re-assigned, but that usually only happens when the control is created since TControlCanvas instances are rarely shared. However, TControlCanvas instances work from a pool of device-context handles kept in CanvasList. Whenever one of them needs a DC (in TControlCanvas.CreateHandle), it calls FreeDeviceContext to make room in the canvas cache for the handle it's about to create. That function calls the (non-virtual) FreeHandle method. The cache size is 4 (see CanvasListCacheSize), so if you have several descendants of TCustomControl or TGraphicControl in your program, chances are high that you'll get cache misses whenever more than four of them need to be repainted at once.
TControlCanvas.FreeHandle is not virtual, and it doesn't call any virtual methods. Although you could make a descendant of that class and give it virtual methods, the rest of the VCL is going to continue calling the non-virtual methods, oblivious to any of your additions.
Instead of trying to detect when a device context is released, you might be better off using a different TGpGraphics constructor. Use the one that takes a window handle instead of a DC handle, for instance. Window-handle destruction is much easier to detect. For a one-off solution, assign your own method to the TPaintBox.WindowProc property and watch for wm_Destroy messages. If you're doing this often, then make a descendant class and override DestroyWnd.
The performance hit you take for creating/destroying the graphics object is minimal. It's far outweighed by the performance hit of using gdi+'s drawing commands in the first place. Neither of which, imo, are worth worrying about when it comes to drawing user interfaces because the user wont notice anyways. And frankly, it can be very inconvenient to try to carry around a graphics object and track changes to the DC handle (especially if you're encapsulating graphics routines inside your own set of classes).
If you need to cache bitmaps, what you may consider doing is creating the bitmap you want to cache with GDI+ (make it the right size & w/ whatever antialias settings you want), saving it to a tmemorystream, and then when you need it, load it from a stream and draw it using good ol' bitblt. It'll be much, much faster than using Graphics.DrawImage. I'm talking orders of magnitude faster.
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;
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
Have some problem. I have a form with a canvas, I need access to this canvas from dll by its handle. I do this in this way:
from dll
canvas := TCanvas.Create;
try
canvas.Handle := handle;
// do some painting on this canvas
finally
canvas.free;
end;
It works well, I paint what I need from dll. But this trick has side effect. After painting from dll, form loses font settings (btw I did not use fonts when painted from dll, just few rects) and when I paint on same canvas from main form, even if I do directly canvas.font.size := ...; canvas.font.name := ...; before canvas.TextOut, the font does not change. Lines, filling and other paintings are ok. But fonts become corrupted (sometimes not, but mostly).
Is there a way to reset/reinit TCanvas object of the form?
Canvas does not have any reset functionality but you can ask the api to save the state of the device context of the canvas, and restore it after your drawing.
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;
Remy's answer explains how you lose the sate of the device context. Why it doesn't always happen should depend on timing I believe. If the form has entered a new paint cycle at the time its canvas uses its font, all should be well since it operates on a newly acquired and setup device context.
The reason your Form's Canvas gets "corrupted" is because the DLL's TCanvas object is replacing the original HFONT, HBRUSH and/or HPEN objects that were already assigned to the HDC, but is then assigning stock GDI objects (from GetStockObject()) during its destruction, instead of re-assigning the original GDI objects that were previously assigned. This happens in the TCanvas.DeselectHandles() method when the TCanvas.Handle property changes value (which includes during destruction):
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);
...
To make the Form "reset" its Canvas after the DLL function exits, you will have to trick the Canvas into knowing its GDI objects are not longer assigned to the HDC so it can clear the relevant flags from its internal State member and reassign its GDI objects as needed. You can either:
manually trigger the OnChange event handlers of the Canvas.Font, Canvas.Brush and Canvas.Pen properties:
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;
Or:
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;
you can temporarily remove and then re-assign the original HDC, which has a similar effect on the State flags:
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;
Use SaveDC() and RestoreDC(), as shown in Sertac's answer.
The standard TCanvas class is not really suited for painting on "borrowed" canvasses. That is, taking a device context (e.g. from some other canvas object) and using it within another, separate TCanvas due to the way it manages GDI objects (relying on "owning" the HDC and the state of GDI objects in that DC that it is working with).
It can work, in simple cases, but otherwise the problems you are experiencing are not uncommon. In particular with a DLL, there could be problems arising from the fact that there are mechanisms within a TCanvas that rely on a "global" list of canvases (CanvasList) that need to be managed and kept in sync in response to system changes.
i.e. In a DLL there will be a CanvasList which is list of canvases in the DLL, separate to the CanvasList in the host application process. The application CanvasList will not include any TCanvas instances in the DLL, and vice versa. If a DLL has a TCanvas which is in fact a "duplicate" of a TCanvas in the application (using the same HDC) then it should be obvious how problems could arise.
I see two ways forward in your case which could be used separately or together.
You haven't provided details of all your painting code so it's difficult to say which is likely to be the source of your problem. However, you can identify this yourself quite easily by commenting out all of your painting code (between the try and finally in your painting routine). This should fix your font problem. If you then re-enable your painting code incrementally (line by line or section by section) you can identify precisely which painting operations are causing the problem and from there (potentially) identify a solution.
If your painting operations are very simple (just painting a few rectangles as you say) then you could use simple GDI calls to do your painting in the problem cases (or all of them), rather than using a canvas. In this case I would suggest that you pass a window handle to your DLL, rather than a device context. Your DLL should then obtain it's own device context via GetDC() and release it via ReleaseDC() when finished. You will need to manage the GDI objects when painting on the device context yourself but can then be sure that whatever you do you are not interfering with the GDI objects being managed by a TCanvas drawing on the same window.
Another possibility is to use SaveDC() and RestoreDC(), as shown in Sertac's answer.
Originally the question had to be how to access the components in the first place, however I somehow managed to figure it out. I am just learning Delphi so I am prone to dumb and obvious questions. I am also at a stage when I'm not actually writing anything useful, but just messing with random stuff to see how it works and maybe learn something.
Wall of text incoming, i want to explain what i am exploring at the moment...
Basically i have a form1 with a button1, pressing it creates a frame2, that frame2 has a button2, pressing button2 creates a frame3 within frame2 (it being frame3's parent and owner). Each frame has an another freeandnil-button. Upon pressing each button1/2/3, it gets disabled to prevent creating multiple instances. My original problem was that after using freeandnil-button, I couldnt access the button on the previous frame (it worked fine for forms, form1.button1.enabled:=true works fine from within frame2) that got disabled in order to re-enable it (frame2.button1.enabled:=true from within frame3 creates an access violation, I think).
Suppose I write something in the future that requires such communication? So I added an editbox to each frame, with a button on the other to change the editbox text, this is my current working solution:
procedure TFrame2.Button3Click(Sender: TObject);
var i,z:integer;
begin
for i := 0 to ComponentCount - 1 do
if components[i] is tframe3 then
for z := 0 to (components[i] as tframe3).ComponentCount - 1 do
if (components[i] as tframe3).Components[z] is TEdit then
((components[i] as tframe3).Components[z] as TEdit).Text:='ping';
end;
and
procedure TFrame3.Button3Click(Sender: TObject);
var i:integer;
begin
for i := 0 to parent.ComponentCount-1 do
if parent.components[i] is TEdit then
(parent.Components[i] as TEdit).Text:='pong';
end;
If I have a bunch of editboxes or whatever the hell, I suppose I could use Tag property to identify them, however, this much component counting and passing something AS something doesn't really look right or efficient enough to me.
My questions at the moment are: can it be done in a better way? and can someone provide the reasoning why cant I access "parent-frame" components from a "child-frame" in a dumb way (ie: frame2.button1.enabled:=true from within frame3)?
A couple of points:
Controls/Components are usually set to be owned by the form/frame that controls their lifetime and parented to the control that shows them. So when you create a TEdit to sit on a TPanel on a TForm, you would set TForm as the owner and TPanel as the parent of the TEdit. With frames you do the same: the frame is the owner of all controls on it, controls are parented to whatever container on the frame (can be the frame itself) is holding and and is thus reponsible for showing (painting) it.
When you iterate over the children of a control, iterating over Components uses the Owner relation; iterating over Controls uses the Parent relation. So your code above would already do a lot less if it were to iterate over the Controls.
Of course you can reference other controls in a "dumb" way, but you will have to provide for the access method. If you want others (not just child frames) you will at the very least have to declare a method to retrieve that control. Many ways to do this. One is to "ask" the form when you need it (using a method on the form), or have the form set a property on the frame when it is created...
Avoid accessing form's and frame's published properties. They are mainly there for Delphi's streaming system. When you tie your application together using your approach described above it can very soon become one incredibly tangled mess...
Example coming up (sometime this evening), it will take me a bit of time to also explain and I have work to do...
The following example deals only with the ping-pong game between the frames. Freeing any form or frame from one of its own event handlers is not such a good idea. Use the Release method for that as it prevents the form/frame from processing messages after is was freed. (Plenty of questions about this on SO by the way). Also, when you do release a frame from one of its own buttons, you will need to take care that the frame that created it has a chance to nil the references it may have held to that frame otherwise you are setting yourself up for some interesting to debug mayhem. Look into "Notification" and "NotifyControls" and the automatic notification by forms and frames that is sent to their owner/parent so these can remove the control from their components/controls collection. In your example, if you were to release Frame3 from its own "FreeAndNil" button's OnClick event handler, you would have to make sure that the Frame2 responds to the deletion (I think) notification message and nil's any references it holds to Frame3 (besides the ones that will already be cleared automatically in the components/controls collections).
Now, the ping pong game. There are a couple of ways to go about this.
Method 1
The first way is what you already tried with a loop over the components of the other frame. While it certainly is a way to avoid having to "use" the other frame, it is cumbersome and not very concise. Plus when you get more controls on your forms/frames, you will have to add a check on the name to know that you have the correct TEdit. And then you might just as well use the name directly, especially as one frame already has the other frame in its uses clause because it is creating it.
// PingFrame (your Frame2)
uses
...
Pong_fr;
type
TPingFrame = class(TFrame)
...
procedure CreateChildBtnClick(Sender: TObject);
procedure PingPongBtnClick(Sender: TObject);
private
FPong: TPongFrame; // This is the "extra" reference you need to nil when
// freeing the TPongFrame from one of its own event handlers.
...
end;
procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
CreateChildBtn.Enabled := False;
FPong := TPongFrame.Create(Self);
FPong.Parent := ContainerPnl;
FPong.Align := alClient;
end;
procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
if Assigned(FPong) then
FPong.Edit1.Text := 'Ping';
end;
And on the other end:
// PongFrame (your Frame3)
type
TPongFrame = class(TFrame)
...
procedure PingPongBtnClick(Sender: TObject);
end;
implementation
uses
Ping_fr;
procedure TPongFrame.PingPong1BtnClick(Sender: TObject);
begin
(Owner as TPingFrame).Edit1.Text := 'Pong called';
end;
This method seems all nice and dandy, but it has drawbacks:
You need to use the unit that holds the TPingFrame (Ping_fr in this example). Not too bad I guess, but... as Ping_fr already uses Pong_fr (the unit holding TPongFrame), you can't have Ping_fr using Pong_fr in the interface section and have Pong_fr using Ping_fr in the interface section as well. Doing so would have Delphi throwing an error because of a circular references. This can be solved by moving one of the uses to the implementation section (as done in the example for the use of Ping_fr in the Pong_fr unit.
A bigger drawback is that there is now a very tight coupling between the two frames. You cannot change the TEdit in either one to another type of control (unless that happens to have a Text property as well) without having to change the code in the other unit as well. Such tight coupling is a cause of major headaches and it is good practice to try and avoid it.
Method 2
One way to decrease the coupling between the frames and allow each frame to change it controls as it sees fit is not to use the controls of another form/frame directly. To do so you declare a method on each frame that the other can call. Each method updates its own controls. And from the OnClick event handlers you no longer access the other frame's controls directly, but you call that method
type
TPingFrame = class(TFrame)
...
public
procedure Ping;
end;
implementation
procedure TPingFrame.PingPongBtnClick(Sender: TObject);
begin
if Assigned(FPong) then
FPong.Ping;
end;
procedure TPingFrame.Ping;
begin
Edit1.Text := 'Someone pinged me';
end;
And on the other end:
type
TPongFrame = class(TFrame)
...
public
procedure Ping;
end;
implementation
procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
(Owner as TPingFrame).Ping;
end;
procedure TPongFrame.Ping;
begin
Edit1.Text := 'Someone pinged me';
end;
This is better than Method 1 as it allows both frames to change their controls without having to worryabout "outsiders" referencing them, but still has the drawback of the circular reference that can only be "solved" by moving one "use" to the implementation section.
It is a good practice to try and avoid circular references altogether instead of patching them by moving units to the implementation section's uses clause. A rule of thumb I use is:
Any form/frame may know and use the public interface of the forms/frames it instantiates (though it should avoid the controls in the "default visibility" part of that interface), but no form/frame should not have any knowledge of the specific form/frame that created it.
Method 3
One way of achieving this (there are many) it to use events just like the TButton's OnClick event.
On the TPongFrame side of things you can remove the use of Ping_fr. You no longer need it.
Then you need to declare a property and the field it references and declare a method to fire the event.
type
TPongFrame = class(TFrame)
private
FOnPingPongClicked: TNotifyEvent;
protected
procedure DoPingPongClicked;
public
property OnPingPongClicked: TNotifyEvent
read FOnPingPongClicked write FOnPingPongClicked;
end;
The Ping method stays and its implementation is unchanged. The PingPongBtnClick event handler also stays, but its implementation now becomes:
procedure TPongFrame.PingPongBtnClick(Sender: TObject);
begin
DoPingPongClicked;
end;
procedure TPongFrame.DoPingPongClicked;
begin
if Assigned(FOnPingPongClicked) then
FOnPingPongClicked(Self);
end;
You could put the code from DoPingPongClicked straight in here, but it is a good practice to fire an event in a separate method. It avoids duplicating the exact same code if you would have an event that can be fired from multiple parts of your code. And it also allows descendants (when you get those) to override the "firing" method (you'll have to mark it virtual in the ancestor) and do something specific all the times the event is fired.
On the TPingFrame side of things you need to code a handler for the new event:
type
TPingFrame = class(TFrame)
...
protected
procedure HandleOnPingPongClicked(Sender: TObject);
Note that the signature of this handler needs to be what the TNotifyEvent specifies. And of course you need to ensure that this event handler gets called when the event fires in TPongFrame:
procedure TPingFrame.CreateChildBtnClick(Sender: TObject);
begin
CreateChildBtn.Enabled := False;
FPong := TPongFrame.Create(Self);
FPong.Parent := ContainerPnl;
FPong.Align := alClient;
// Listen to event fired by PongFrame whenever it's PingPingBtn is clicked
FPong.OnPingPongClicked := HandleOnPingPongClicked;
end;
In the handler code you can do what you need to do. It can be general:
procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
begin
Edit1.Text := 'OnPingPongClicked event was fired';
end;
And of course you can also use the fact that the event passes a reference to the instance firing the event as well:
procedure TPingFrame.HandleOnPingPongClicked(Sender: TObject);
var
Pong: TPongFrame;
begin
// This checks that Sender actually is a TPongFrame and throws an exception if not
Pong := Sender as TPongFrame;
// Use properties from the Pong instance that was passed in
Edit1.Text := 'OnPingPongClicked event was fired by ' + Pong.Name;
end;
Enjoy!
I'm was trying to write a dll library in Delphi wih a function that creates an instance of a TFrame descendant and returns it. But when I imported this function in an application, every time I called it I would get an exception like "the 'xxx' control has no parent window". I'm not 100% sure, but the exception appeared in the constructor of that class when any of GUI controls was accessed.
Could you please tell me what the reason of that behaviour is? Should I just use TForm descendants instead or is there a better solution?
Thank you!
About the error
That error message is raised from the Controls.pas unit, from the TWinControl.CreateWnd method. Essentially that code is used to create the Window handle for your TWinControl descendant (TFrame, TButton, TEdit... if it can have keyboard focus it's an TWinControl descendant), and it's actually an very sensible error message: You can't have a Window without an WindowParent, and since we're talking about the VCL here, it makes a lot of sense to try and get the parent window handle from TWinControl.Parent; And that's not assigned.
That's not WHY the error message is popping up. You get to see that error message because some of the code you're using to set up the frame requires an Window handle for some operation. It could be anything, like setting the Caption of some component (that internally requires an window handle do to some calculation). I personally really hate it when that happens. When I create GUI's from code I try to delay the assignment of Parent as much as possible, in an attempt to delay the creation of the window, so I got bitten by this many times.
Specific to your DLL usage, possible fix
I'm going to put my psycho mind reader hat on. Since you need to return a FRAME from your DLL, and you can't return the actual Frame because that's an Delphi-specific object and you're not allowed to return Delphi-specific objects over DLL boundaries, my guess is you're returning an Window Handle, as all the nice API's do, using a function definition like this:
function GiveMeTheNiceFrame:HWND;
The trouble is, that routine requires the creation of the actual Window Handle, by a call to TWinControl.CreateWnd, and in turn that call requires an parent window handle to set up the call to Windows.CreateWindowEx, and the routine can't get an parent window handle, so it errors out.
Try replacing your function with something allong the lines of:
function GiveMeTheNiceFrame(OwnerWindow:HWND):HWND;
begin
Result := TMyNiceFrame.CreateParanted(OwnerWindow).Handle;
end;
... ie: use the CreateParented(AParentWindow:HWND) constructor, not the usual Create(AOwner:TComponent) and pass an owner HWND to your DLL.
There are a few important things to remember:
When using DLLs, both your DLL and your EXE each have an Application instance that are struggling for control. The Controls in your DLL will see the Application instance that belongs to the DLL; the Controls in your EXE will see the Application instance that belongs to the EXE. That struggle is not there when using packages, as then there will only be one Application instance.
Frames are Controls, but they are not Forms.
When using Controls in an application, they cannot visually exist without a parent Control (usually a Form or a container that has a parent hierarchy towards a Form).
Some Controls cannot expose their full functionality unless they exist visually and have a valid parent.
Try to reproduce your problem inside the EXE; if you cannot reproduce, it is probably the first thing in the above list.
--jeroen
Sounds like you simply need to assign the component (a form or part of a form, like a panel) that holds the frame to theframe.parent.
You cannot do GUI work before it is assigned. Frames are parts of forms for reuse, and normally need to assign some parent to them.
Move the GUI code to onshow or a procedure you call explicitely, so that the calling code can assign parent.
Or make the parent a parameter in the function.
I found this (CreateParams is called as part of CreateWnd):
procedure TCustomFrame.CreateParams(var Params: TCreateParams);
begin
inherited;
if Parent = nil then
Params.WndParent := Application.Handle;
end;
And Application.Handle = 0 so it always throws the error later in CreateWnd.
After reading this
Delphi: How to call inherited inherited ancestor on a virtual method?
I have solved it by overriding CreateParams in my frame to miss out the tCustomFrame version:
type
tCreateParamsMethod = procedure(var Params: TCreateParams) of object;
type
tMyScrollingWinControl = class(TScrollingWinControl);
procedure TDelphiFrame.CreateParams(var Params: TCreateParams);
var
Proc: tCreateParamsMethod;
begin
TMethod(Proc).Code := #TMyScrollingWinControl.CreateParams;
TMethod(Proc).Data := Self;
Proc(Params);
end;
Now it's just throwing errors when trying to set the focus on subcontrols, which I think I will fix by intercepting WM_FOCUS but we'll how it goes from here.
function CreateFrame(hwndParent: HWnd): HWnd; stdcall;
var
frame: tFrame;
begin
Result := 0;
try
frame := TDelphiFrame.CreateParented(hwndParent);
Result := frame.Handle;
except on e: Exception do
ShowMessage(e.Message);
end;
end;
You can avoid this message by assigning nil to the parent OnClose event, sometimes it works:
SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;
I think this is very cool solution. I think it is not tried before :)
I'm using a Dummy Parent (which is a Form).
function MyFrame_Create(hApplication, hwndParent:THandle; X, Y, W, H:Integer):Pointer; stdcall;
var Fr: TMyFrame;
F: TForm;
CurAppHandle: THandle;
begin
CurAppHandle:=Application.Handle;
Application.Handle:=hApplication;
//---
F:=TForm. Create(Application);//Create a dummy form
F.Position:=poDesigned;
F.Width:=0; F.Top:=0; F.Left:=-400; F.Top:=-400;//Hide Form
F.Visible:=True;
//---
Fr:=TMyFrame.Create(Application);
Fr.Parent:=F;//Set Frame's parent
//Fr.ParentWindow:=hwndParent;
Windows.SetParent(Fr.Handle, hwndParent);//Set Frame's parent window
if CurAppHandle>0 then Application.Handle:=CurAppHandle;
//---
Fr.Left:=X;
Fr.Top:=Y;
Fr.Width:=W;
Fr.Height:=H;
Result:=Fr;
end;//MyFrame_Create
procedure MyFrame_Destroy(_Fr:Pointer); stdcall;
var Fr: TMyFrame;
F: TObject;
begin
Fr:=_Fr;
F:=Fr.Parent;
Fr.Parent:=Nil;
if (F is TForm) then F.Free;
//SetParent(Fr.Handle, 0);
//Fr.ParentWindow:=0;
Fr.Free;
end;//MyFrame_Destroy
I have an TUpDown control whose Associate is set to an instance of a TEdit subclass. The edit class calls RecreateWnd in its overriden DoEnter method. Unfortunately this kills the buddy connection at the API level which leads to strange behavior e.g. when clicking on the updown arrows.
My problem is that the edit instance doesn't know that it is the buddy of some updown to which it should reconnect and the updown isn't notified of the loss of its buddy. Any ideas how I could reconnect the two?
I noticed how TCustomUpDown.SetAssociate checks that updown and buddy have the same parent and uses this to avoid duplicate associations. So I tried calling my own RecreateWnd method:
procedure TAlignedEdit.RecreateWnd;
var
i: Integer;
c: TControl;
ud: TCustomUpDown;
begin
ud := nil;
for i := 0 to Pred(Parent.ControlCount) do
begin
c := Parent.Controls[i];
if c is TCustomUpDown then
if THACK_CustomUpDown(c).Associate = Self then
begin
ud := TCustomUpDown(c);
Break;
end;
end;
inherited RecreateWnd;
if Assigned(ud) then
begin
THACK_CustomUpDown(ud).Associate := nil;
THACK_CustomUpDown(ud).Associate := Self;
end;
end;
et voila - it works!
You've discovered something rather unfortunate. You set up an association between two controls at the application level, so you should be able to continue to manage that association in application-level code, but the VCL doesn't provide the framework necessary for maintaining that. Ideally, there would be a generic association framework, so associated controls could notify each other that they should update themselves.
The VCL has the beginnings of that, with the Notification method, but that only notifies of components being destroyed.
I think your proposed solution is a little too specific to the task. An edit control shouldn't necessarily know that it's attached to an up-down control, and even if it does, they shouldn't be required to share a parent. On the other hand, writing an entire generic observer framework for this problem would be overkill. I propose a compromise.
Start with a new event property on the edit control:
property OnRecreateWnd: TNotifyEvent read FOnRecreateWnd write FOnRecreateWnd;
Then override RecreateWnd as you did above, but instead of all the up-down-control-specific code, simply trigger the event:
procedure TAlignedEdit.RecreateWnd;
begin
inherited;
if Assigned(OnRecreateWnd) then
OnRecreateWnd(Self);
end;
Now, handle that event in your application code, where you know exactly which controls are associated with each other, so you don't have to search for anything, and you don't need to require any parent-child relationships:
procedure TUlrichForm.AlignedEdit1RecreateWnd(Sender: TObject);
begin
Assert(Sender = AlignedEdit1);
UpDown1.Associate := nil;
UpDown1.Associate := AlignedEdit1;
end;
Try storing the value of the Associate property in a local variable before you call RecreateWnd, then setting it back afterwards.
I have an issues where I am trying to determine if a reference to an object is valid. But it seems to be returning strange results.
procedure TForm1.Button1Click(Sender: TObject);
var form1 : TForm;
ref2 : TControl;
begin
form1 := TForm.Create(nil);
form1.Name := 'CustomForm';
form1.Parent := self; //Main Form
form1.Show;
ref2 := form1;
showmessage(ref2.ClassName+' - '+ref2.Name+' - '+BoolToStr(ref2.visible,true));
freeandnil(form1);
showmessage(ref2.ClassName+' - '+ref2.Name+' - '+BoolToStr(ref2.visible,true));
end;
The first showmessage returns - "TForm - CustomForm - True" (Just like I would expect it to).
The second showmessage return - "TForm - - False". I was actually hoping for some kind of access violation that I could then trap and know that the reference isn't valid.
In my application I need to compile a list of random TForm descendants as they are created and then check later if they have gone away (or are not visible). Unfortunately it is a plugin based system so I can go change all of these Forms to post a "I'm done Message."
Would code like this be safe to use (assuming I actually am checking for access violations)? Does anybody have any ideas what is happening.
Thanks
The problem is that with a certain likelyhood the memory accessed is still reserved by the Delphi memory manager. In that case Windows does not generate any kind of access violation, because that memory belongs to you!
One possibility is to switch to a different Delphi memory manager which can detect the use of freed objects. FastMM4, for example, has several "memory hygiene" checks, which are very useful for debugging, but even then you won't catch all of these errors immediately.
You can download FastMM4 from SourceForge.
Any TComponent (e.g. a TForm descendant) can register for notifications when other components are destroyed.
In your form, call FreeNotification(form) for each form that you wish to be notified of the destruction of. Then on the same form override the Notification() method. When any form (or other component) for which you have called FreeNotification() is destroyed, your Notification() method will be called with a Component parameter referencing the form and an Operation of opRemove.
If I've understood what it is you are trying to achieve, I think this should be enough information to devise an approach to do what you need.
After
freeandnil(form1);
the Delphi memory manager just marks the memory allocated by form1 as free, but all form1 data is still there, and can be accessed via ref2 until the memory manager reuse the freed memory for some other object(s).
You can't check that way if ref2 references a valid object or not. Code like this can't be safe, it is actually a bug.
If you want to obtain a 100% access violation modify the code as follows (here ref2^ = nil if form1 is freed):
procedure TForm1.Button1Click(Sender: TObject);
var form1 : TForm;
ref2 : ^TControl;
begin
form1 := TForm.Create(nil);
form1.Name := 'CustomForm';
form1.Parent := self; //Main Form
form1.Show;
ref2 := #form1;
showmessage(ref2^.ClassName+' - '+ref2^.Name+' - '+BoolToStr(ref2^.visible,true));
freeandnil(form1);
showmessage(ref2^.ClassName+' - '+ref2^.Name+' - '+BoolToStr(ref2^.visible,true));
end;
There is no reliable way to do what you are trying to do using the technique you're attempting. Forms that have "gone away" may have their memory reused, possibly even for a new form.
At best, you could work some mechanism whereby you cache the results of iterating Screen.Forms, but you can still fall foul of accidental duplicates, where a form gets destroyed and another gets reallocated and gets the same object address. That scenario is less likely than the memory being reused for some other object, however.
In a similar case I am using a singleton object that keeps a list of all the created forms.
Each form has a field with a reference to this Object.
TMyForm = class(TForm)
private
//*** This is the reference to the singleton...
FFormHandler: TFormHandler;
public
...
//*** you might want to publish it as a property:
property FormHandler: TFormHandler read FFormHandler write FFormHandler;
end;
You can set this reference e.g. when calling the constructor:
TMyForm.Create(aFormHandler: TFormHandler; aOwner: TComponent)
begin
FFormHandler := aFormHandler;
inherited Create(aOwner);
end;
(Or you could set the field from outside directly after creating the form if you don't want to change the parameters of the constructor).
When the form ist destroyed it notifies the handler and tells him to remove the form from the list - something like that:
TMyForm.Destroy(Sender: TObject);
begin
FFormHandler.RemoveFromFormList(Self);
inherited;
end;
(The details of the track-keeping are not included in the expample - e.g. a method "AddToFomList" or something alike would be needed)
There is one very interesting memory manager. It is called SafeMM: http://blogs.embarcadero.com/medington/2009/10/16/24839 But still it is for debugging only.
Given that you cannot modify the code that is out there in the plugins, all the good solutions about how to write safer code are not applicable to your case.
You have 1 way of doing it by
checking if an Object reference is
still what it's supposed to be by
looking up the VMT. This idea was
first published by Ray Lischner (who advocated for FreeAndNil for that very reason) and
later by Hallvard Vassbotn: see
this SO answer.
Another, better but introducing major slowdown, is to use FastMM4 in FullDebugmode to have it to replace all the freed objects by a TFreeObject instance instead of simply releasing the memory to the available pool.
Note that both methods do not prevent a false positive if another instance of the same class happens to be created at the same memory address. You get a valid object of the right type, just not the original one. (Unlikely in your case, but possible)
it is as simple as comparing against NIL:
// object declaration
Type object;
object = new Type();
...
// here you want to be sure of the existance of the object:
if (object <> nil )
object.free;
If you cannot test in another manner, you can use this as a last resort±
function IsValidClass( Cls: TClass ): Boolean;
var
i: Integer;
begin
for i := 0 to 99 do begin
Result := ( Cls = TObject ); // note that other modules may have a different root TObject!
if Result then Exit;
if IsBadReadPtr( Cls, sizeof( Pointer ) ) then Break;
if IsBadReadPtr( Pointer( Integer( Cls ) + vmtParent ), sizeof( Pointer ) ) then Break;
Cls := Cls.ClassParent;
end;
Result := False;
end;
function IsValidObject( Obj: TObject ): Boolean;
begin
Result := not IsBadReadPtr( Obj, sizeof( Pointer ) ) and IsValidClass( Obj.ClassType ) and not IsBadReadPtr( Obj, Obj.InstanceSize );
end;
IsBadReadPtr comes from Windows.