4

There's a container control, a TScrollBox, that parents multiple item controls.

Every item control, being compound itself, contains (parents & owns) a delete button. Pressing the button initiates the deleting of the item control.

The deleting involves freeing the component and so the actual operation should be extrinsic with regard to the item. The question is, what would be the best way to do it?

I actually know of a couple of options:

  • a timer with a small interval (which is started with the button click);
  • a hidden outside button (to which the mouse down & up messages are posted);
  • the form's custom message handler.

While I could confidently implement any of those methods, as I flatter myself, I'm not sure which one would be best. Besides, the timer option seems childish, the hidden button one hackish, and the custom message one a bit overkill. In short, all three seem equally half-acceptable, more or less.

I may simply be prejudiced and don't mind being convinced to the contrary. Still most of all I would like to know what is a common method to use in such cases (maybe something I've been missing all the time).

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Andriy M
  • 76,112
  • 17
  • 94
  • 154
  • I know I'm probably missing something here. If the item contains the controls, wouldn't its own destructor get rid of its child components? If this is true, why not just removing the item and freeing it? – Leonardo Herrera Mar 29 '11 at 13:07
  • The same reason I deleted my answer: The delete button Andriy mentions is part of the control to be deleted and would implement its own handling of the click on the delete button. So you would be doing it from one of the control's own event handlers and normally control's really don't like having the rug (the world) pulled out from underneath themselves... – Marjan Venema Mar 29 '11 at 13:23
  • @Marjan is there a problem with asking a control to free it's self ? – Najem Mar 29 '11 at 14:05
  • @Najem: Not in and of itself. Instances can free themselves. But it should be the last thing they do and all references to them should have been cleared before they do. Most of the time an instance doesn't know who/what is holding references to it and that means it could be asked to delete itself twice or more. Using an asynchronous message like in David's answer means that destruction gets postponed (preventing AV's) and once the control is freed, any further messages (including "delete yourself") to that control will/should be ignored as the handle is gone. (That about it @David?) – Marjan Venema Mar 29 '11 at 16:18
  • @Najem: Just spotted your question on this. Mason explains it better than I do... – Marjan Venema Mar 29 '11 at 16:20
  • @Marjan: I'm not yet absolutely sure about all things in the process, but I think I've got the one about the reason why freeing a control in a responce to a *posted* message works. It does, I think, because it's eventually not the control that does the job, even though it looks as if it uses it's own method (message handler) to do that. But in fact the message handler will be called in a so called message loop, thus within a different controlling mechanism, which will not interfere with the rest of your code when calling the subroutine that cuts the root. – Andriy M Mar 29 '11 at 16:42

2 Answers2

4

The normal approach is to post a message to the control that is to be freed. See how TForm.Release is implemented for example. In fact I see no reason why you can't even re-use the CM_RELEASE message.

The point about posting the message is that is goes to the back of the queue and only gets processed once any synchronous messages (i.e. those delivered by SendMessage) have completed processing. This avoids calling methods on an object after it has been freed which is obviously an error that you are clearly well aware of.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I just fixed a nasty bug in some code using this. It has a horrible smell, in any case more complex than a TEdit that is momentarily created in order to handle inplace editing in a string grid, or something like that. – Warren P Mar 29 '11 at 13:01
  • Not sure about what you mean by *re*-using (starting from TWinControl, TCustomForm is the only class using CM_RELEASE, and my container is TScrollBox; maybe that is not true for Delphi versions higher than 6), but the idea itself is much closer to what I have initially imagined it to be like, thanks. And yes, I've already been bitten by a misused SendMessage. – Andriy M Mar 29 '11 at 13:33
  • What I meant is that CM_RELEASE is a private VCL message. But since it's not used by anything your class inherits from, I see no reason why you could not just post a CM_RELEASE to the control you want to destroy and then respond to its arrival with a call to Free. – David Heffernan Mar 29 '11 at 13:35
  • Turned out brilliantly simple. Thanks! – Andriy M Mar 29 '11 at 14:15
  • I'm having a similar issue: I have a button on a row of controls that deletes the row, that is it deletes all the controls on the row including itself. I delete the button, like all the controls on the row, by calling each control's Free method in the call stack of the button's OnClick event. This creates an Access Violation error because, I'm assuming, the button's OnClick handler is still active when btnDeleteRow.Free executes. Calling btnDeleteRow.Perform(CM_RELEASE, 0, 0) does not cause an AV error, but the Button does not disappear like it does when you call btnDeleteRow.Free. – Jonathan Elkins May 27 '18 at 19:30
2

First, I would suggest that you write a custom control that inherits from TScrollBox, and that you provide the sub-control instantiation and removal as a feature inside that scroll box, and not something done "out there in the open" in your form. This code would go in its own unit, and only the public elements of it will be visible outside. That's just object oriented basics.

Secondly, if you are removing (deleting) controls from a scroll box, a Timer is just a source of chaos. It is possible that if you have also subclassed every control you put into that container, that you could use the mechanism used by TForm.Release(it sends them CM_RELEASE messages), and implement CM_RELEASE in a way that controls delete themselves when sent this message, however I find this ugly, except in the case of Edit controls that are destroyed when they lose focus.

I would directly delete these methods, without recourse to a timer, by subclassing both the TScrolLBox class, and any other classes that I want to put into it, and then having the removal of a control be handled by the parent object (TScrollBox), rather than by outside manipulation of any kind.

Warren P
  • 65,725
  • 40
  • 181
  • 316
  • I wouldn't call it ugly, but it was some revelation for me to see how TCustomForm can shoot itself by simply stating `Free;` in its own method. However, maybe message handlers are special case where it is allowed. Thanks for the idea about parent controls removing their own items. Presently it requires a bit more effort than necessary, but I did consider the idea too and your explanation has helped me to understand things better. – Andriy M Mar 29 '11 at 13:58
  • message handlers are where it can't call Free. Hence the need for release. When it calls Free on itself and that returns, the object is not accessed again. – David Heffernan Mar 29 '11 at 15:41
  • @David: Sorry, but I think TCustomForm does call Free in the CM_RELEASE message handler. And Release actually posts the message, in response to which the Free method is called eventually (in the message handler). – Andriy M Mar 29 '11 at 16:45