views:

243

answers:

5

I tried a script from a web site I run http://www.delphi-central.com/runtime.aspx and succeed.


private
  { Private declarations }
  procedure CustomButtonClick(Sender: TObject);

procedure TForm1.AddNewButtonClick(Sender: TObject);
var
  NewButton : TButton;
begin 
  NewButton := TButton.create(self);

  with NewButton do
  begin
    Top    := 30;
    Width  := 60;
    Left   := Width * (self.ControlCount-2);
    Parent := self;
    OnClick := CustomButtonClick;
    Caption := 'Button '+ inttostr (self.ControlCount-2);
  end;  //With
end;

procedure TForm1.DeleteLastButtonClick(Sender: TObject);
begin
  if Self.ControlCount>2 then
    TButton (Controls[ControlCount-1]).destroy;
end;

procedure TForm1.CustomButtonClick(Sender: TObject); 
begin    
    ShowMessage(TButton(Sender).caption + ' Pressed'); 
end;

But if I change the OnClick,

OnClick := CustomButtonClick; ==> OnClick := DeleteLastButtonClick;

it will generate an error message. How could this happen ...???

+5  A: 

Of course it goes boom--that's what's liable to happen when you cut off the branch you're sitting on.

You can't kill a control inside an event handler spawned by that control.

Note that the sample you're working from did NOT point the CustomButtonClick at the delete routine!

Loren Pechtel
I think I know what you mean, can you give a little script, so I better understand?
Josef J
A script for what? The issue is you're doing something that can't be done, period. Look at Mason's answer for a way to do it correctly.
Loren Pechtel
Let me put it to you this way Josef: Please enter a phone booth, then while you are inside the phone booth, please give the order to have a ten tonne weight dropped on the phone booth. Now do you understand?
Warren P
A: 

Do you have a solution to this problem, can you give a little script?

Thank's

Josef J
hello and welcome to SO, please consider posting questions to answers as comments instead of as an answer.
pastacool
@pastacool - you probably mean that he should post **comments** to answers.
Alexander
@alexander: yes, "questions to [already given] answers" should be handled by a comment
pastacool
Wow. People are being really harsh with this response. *Three* down-votes? Personally, I don't bother voting people down for a *faux pas* like this when they have such low reputation already, especially when it's the first offense. I think it makes Stack Overflow look unfriendly to newcomers. Just give a gentle explanation of how things work around here, and leave it at that.
Rob Kennedy
Note sir.. i'm sorry for my response.
Josef J
@Rob He's getting downvoted because he's consistently asking for code rather than understanding. Reading helps but you learn by writing, not by cutting and pasting.
Loren Pechtel
He wasn't *consistently* doing anything, @Loren. This is his first-ever question here, and he got all three down-votes before he "consistently" requested "scripts" in comments to your and Mason's answers. I interpret a request for a "script" as a non-English-speaker's way of asking for code to solve the problem. Essentially, "Can you show me how to fix this problem?" No reason to fault him for that. Mason's follow-up comments do a good job encouraging a different behavior, though.
Rob Kennedy
I wish you could comment on your own questions even without a reputation > 100. It would solve this problem.
Warren P
+3  A: 

An event handler is called by a function on the control's object, and it could have more code to execute once the event handler finishes. If you delete the control, then any code that references that object is likely to raise an access violation.

What you need to do is get your program to delete the control after it's done with all the code it's currently running. For that, you need to post a message. If you don't know about messages, this is a good opportunity to learn.

You need to create a new message type ID. WM_USER + 1 should work. One of the params will be the address of the control to be deleted. Set up a message handler on your form that handles that message type and frees the control referenced in the message param. And then in the event handler, have it PostMessage that message to your form. That should work without causing access violations.

Mason Wheeler
Thanks Wheeler..OK, what do you mean "new message type ID"??maybe you can give me a little script that I more understood.
Josef J
If I just post a code sample for you to copy and paste, you won't actually learn anything about using messages, and then you won't know what to do the next time posting a message might come in handy. That's why StackOverflow is really not a "please give me working code" site. It's really pretty simple if you look it up, and I've given you everything you'll ned once you understand the principles involved.
Mason Wheeler
Now I know what you mean it, I get an article on who you mean "http://www.cryer.co.uk/brian/delphi/ howto_send_custom_window_message.htm" I have learned more thanks so much...
Josef J
@josef: That's a pretty good article. One mistake, though. SendMessage does not post the message to the end of the message queue; it sends it directly to the recipient to be executed immediately. But aside from that mistake it looks like a good article. What you want in this case is PostMessage, which will wait until all the other messages (some of which might need to use the button) are finished before processing the new one.
Mason Wheeler
@Mason: Thanks for your advice.Mason, you seem to understand many things about Delphi. Maybe you can suggest to me, a book / web site what is good for me to learn?
Josef J
+1  A: 

It is easy think see the reason, when you consider that the system must somehow redraw the button after you release the mouse button / key. Since you're deleting the button object already during the click, this will fail.

Hence you need to find a way to somehow delete the button after the processing of the onClick event has occurred and successfully finished.

Schedler
So... how??? any advice??
Josef J
A: 

Instead of

procedure TForm1.DeleteLastButtonClick(Sender: TObject);
begin
  if ControlCount>2 then
    TButton (Controls[ControlCount-1]).Destroy;
end;

you can try this (tested with Delphi 2009):

procedure TForm1.DeleteLastButtonClick(Sender: TObject);
begin
  if ControlCount>2 then
    RemoveControl (Controls[ControlCount-1]);
end;

Important: this does not destroy the object, it is just no longer a child control of the form:

Applications should not call RemoveControl directly. Child controls are automatically inserted and removed when added or deleted at design time. At runtime, use the Parent property of the child control to remove it from the Controls array.

http://docwiki.embarcadero.com/VCL/en/Controls.TWinControl.RemoveControl

mjustin
This will orphan the control. Delphi is *NOT* garbage collected!
Loren Pechtel
@mjustin: Thanks, i will remember this
Josef J
@Loren: a control without parent is not orphaned, the form is still its owner, so the control can be acessed via the forms Components list (and freed when necessary).
mjustin