Delphi TFrame Create/Destroy
Asked Answered
A

2

5

How to create (when I want to show it) and destroy (when I want to hide it) frames on the main TForm? Frames' align = alClient.

I tried this:

The form:

unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, uFrame1, uFrame2;

type
  TFormMain = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    f1: TFrame1;
    f2: TFrame2;
  end;

var
  FormMain: TFormMain;

implementation

{$R *.dfm}

procedure TFormMain.FormCreate(Sender: TObject);
begin
  f1 := TFrame1.Create(Self);
  f1.Parent := Self;
end;

end.

First frame:

unit uFrame1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

type
  TFrame1 = class(TFrame)
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

implementation

{$R *.dfm}

uses main, uFrame2;

procedure TFrame1.btn1Click(Sender: TObject);
begin
  Self.Free;
  FormMain.f2 := TFrame2.Create(FormMain);
  FormMain.f2.Parent := FormMain;
end;

end.

Second frame:

unit uFrame2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

type
  TFrame2 = class(TFrame)
    lbl1: TLabel;
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

implementation

{$R *.dfm}

uses main, uFrame1;

procedure TFrame2.btn1Click(Sender: TObject);
begin
  Self.Free;
  FormMain.f1 := TFrame1.Create(FormMain);
  FormMain.f1.Parent := FormMain;
end;

end.

but it crashes with access vialataions when I click button on FrameStart or Frame1 (TForm FormCreate works fine i.e. it creates and shows FrameStart).

Delphi 7.

With the first frame With the second frame

Accepter answered 15/5, 2012 at 20:59 Comment(12)
Self.Free? 0_o, I'm not sure that event processing code of VCL can handle that - with forms this does not work, for example #709347Numeration
Self.free is fine, just don't do anything with self, after calling, you'll get things like access violations.Scriven
It works nicely with XE2... I can 't reproduceLinter
Still kinda not sure..even if AV is not being caught, it does not mean that there is no AV. #5473242 #4318080 #2503065Numeration
I'm not even sure that wnd proc that called event handler can successfully finish after FreeObjectInstance() was executed in Free, and a new frame allocated possibly overwriting the old memory.Numeration
@Tony No, Self.Free is categorically not fineAngora
@David. Was er, tongue in cheek that comment. When I get a funny in other peoples code. One of those this can't possibly happen bugs, Self.Free is one of the first things I look for. First time I saw it I was amazed, I'd never even thought of doing it, just assumed it wouldn't work.Scriven
@TonyHopkinson: I agree with David. It is not safe to call Self.Free from inside an event handler that is tied to object that Self is pointing at. When the event handler exists, the VCL still needs access to that object for internal operations, so it must remain valid. Which is why you get AVs. This is why TForm has a Release() method for delaying Free() when an event handler needs to call it...Ere
For those wondering why it seems to work sometimes. It all depends on whether the self (or any other reference) to the instance is used to call a method on it, before the memory it was pointing to gets reused.Scriven
@TonyHopkinson: ... TFrame does not have a Release() method, but it is easy to mimic - use PostMessage(Self.Handle, CM_RELEASE, 0, 0) instead of Self.Free, then call Self.Free inside of the TFrame.WndProc() method when it processes the CM_RELEASE message.Ere
@Remy I agree with David as well. It was a bit of british irony.Scriven
That's easy? Why would I do that instead of simply managing my instance lifetimes more accurately? I never call self.free. Strict control over the lifetimes of my instances is something I live by. About the only way I'd do something like that would be for an emergency bodge at management's insistance to make a deadline.Scriven
A
9

You can't call Self.Free in those event handlers. When the event handler returns, the VCL code that executes next still uses a reference to an object that you just freed. And that's where the access violation comes from. If you had been running with FastMM in full debug mode then you would have been shown a helpful diagnostic message.

These frames will have to kill themselves in a more roundabout manner. Post a CM_RELEASE message to the frame asking it to call Free on the frame. You post the message, rather than sending it, so that all the in flight messages are processed first. You'll need to add a message handler to the frame to respond to the message.

Angora answered 15/5, 2012 at 21:32 Comment(6)
and Boris links show you some examples...Linter
That's right! However I can't reproduce the AV with the code from the question (at least in Delphi 2009).Cenis
@tlama that's just the nature of this type of AV. Run in Fast MM full debug mode and you'll see some action. This mistake is one of the classics.Angora
I know that I never should try to cut down a branch, I was just wondering why is this silent.Cenis
All depends on whether any methods get called on the instance self is referencing, before the the memory self is pointing to gets used for something else. It's a don't do this, and if you do rejoice when your code falls over immediately scenarioScriven
Thank you :) This answer and https://mcmap.net/q/974835/-how-to-free-control-inside-its-event-handler link by @Linter was a solution for me.Accepter
S
4

You've got some of it.

The basic idea behind this sort of stuff.

add a private property to your mainform to hold the frame.

in the button click handler assuming you only want one at a time do

if assigned(fMyFrame) then
begin
  fMyFrame.Free;
  fMyFrame := nil;
end;
fMyFrame := TSomeFrame.Create(self);
fMyFrame.Parent := self;
fMyFrame.blah...

When you just want to get rid of it as opposed to replacing it

if assigned(fMyFrame) then
begin
  fMyFrame.Free;
  fMyFrame := nil;
end;

If you want your frame to raise another frame, repeat the above in there.

If you want the frame you raise in a frame to be a sibling, e.g. have the same Parent, then don't use Form1 var.

fMyNextFrame.Parent = self.Parent;

There's a huge number of ways you could improve on this once you get it working, it's a classic scenario for interfaces and or inheritance, but figure this bit out first.

mySomething := TMySomething.Create();

you can now do something with something. After you called free, it's not can't, it's don't and don't let anything else either.

Don't do self.free, it's like playing with matches in barrel of petrol. It will hurt....

Scriven answered 15/5, 2012 at 22:4 Comment(2)
This doesn't get it done. You'd need to move the event handler to be a form method for this to work. And that defeats the purpose of using frames.Angora
Agreed, but something has to manage the reference to the frame and it's that, that needs to manage the lifetime of it. I was trying to get the management aspect across, should have chosen something other than Frame for the example.Scriven

© 2022 - 2024 — McMap. All rights reserved.