when do control procedures receive kill events?
tony
If I make a panel with a button that executes KillWindow to kill its host, none of the control procedures for controls in the panel are called with eventcode -1. But, if I use PauseForUser to turn the panel into a modal dialog, the -1 events are reported. If I run Execute "KillWindow..." in the control procedure I don't see the -1 event, but I do with Execute /P. And if I execute KillWindow from commandline the -1 events are reported. That is not what I expected and now I'm wondering in which circumstances kill events aren't reported. The manual is not exactly clear on this, or maybe I'm not looking in the right place?
Same for DoWindow /K.
I can tell you what's happening, but it will take me some more time before I can tell you if this is something you just have to live with, or if it's something we need to fix.
Internally, Igor checks for the control action proc runner code being called in a reentrant way. Usually preventing re-entry is fine- if more than one event is queued up for your control, it simply waits a bit and gets another chance to run the action proc.
But if the first event (say, mouse up in a button control) calls KillWindow, the call to KillWindow queues the -1 event. Then KillWindow finishes, the window is dead. The queue now gets to the -1 event in the queue but the window is dead, so the action proc can't be called.
This is why Execute/P "KillWindow" fixes the problem: doing that puts off calling KillWindow until the current action proc call has finished, so the -1 event can be delivered as soon as KillWindow is called.
So it seems like I would have to allow your action proc to be called re-entrantly in order for the -1 event to be properly delivered. That's not necessarily bad depending on how you write the action proc. It means that the -1 case in your action proc will run before the mouse-up case is finished, so it depends what comes after the call to KillWindow.
Just to make this more complicated (it was *you* that brought up PauseForUser!) to make PauseForUser work correctly, it was necessary to allow re-entrant calls to the action proc runner because PauseForUser often involves events for two panels at the same time. That is, one panel creates a PauseForUser panel during a button click, and that mouse-up event runs the entire time PauseForUser is running. So by running a panel under PauseForUser you are using a loophole in my protection code. So now I have to figure out if my re-entrancy preventer is being too draconian, or if the PauseForUser exception needs to be refined.
Using Execute/P "KillWindow "+s.win during PauseForUser fails for other reasons...
June 7, 2021 at 02:36 pm - Permalink
Thanks, John, for the explanation.
To be clear, the current behaviour is not a problem, I was mainly looking for clarification. I reused old code that created a modal panel with a cancel button that simply executed KillWindow, which in turn triggered a cleanup procedure. Made it non-modal and the cleanup didn't happen. It seemed obvious that it was to do with reentrance, and simply calling the cleanup procedure after KillWindow worked. Maybe the manual could include a note in the explanation for eventCode -1 that states that it is designed to catch kill events originating outside of control procedures associated with the host window?
Because using KillWindow to trigger cleanup seemed to work so well, it was too tempting to use the same method everywhere without thinking. It also means that in more complicated code that also includes a window hook, it's better to catch the kill event in the hook function than in control action procedures.
June 8, 2021 at 12:45 am - Permalink
Seems like the scenario you outline should work. I usually get nauseous feelings while writing documentation like you are suggesting! In fact, as I wrote the explanation above I was getting slightly ill.
Seems like the same code SHOULD be usable in both cases. I'll see what I have to do when I've finished preventing BUG messages when you call ControlUpdate on a custom control...
June 8, 2021 at 10:28 am - Permalink
I have just added code to allow KillWindow and DoWindow/K to cause re-entrant calls to the control action proc. In the next nightly build for both Igor 8 and 9.
June 8, 2021 at 02:34 pm - Permalink
Thanks, John.
So now the reentrant call happens after the window has been killed?
June 11, 2021 at 03:51 am - Permalink
... and now, as you might expect, I find that I have other code that relies on the old behavoiur!
Specifically, if I want to rebuild a panel by killing and making it afresh, I might not want the cleanup procedure to fire because it wipes out current settings.
I can deal with this, but I'm wondering whether other existing code could be broken like this - perhaps you want to reconsider this change?
Actually, to be specific, the code in question kills and rebuilds panel A while pauseforuser is active for panel B. Your change now allows panel A control procedures to see the kill event, where I don't think they previously did?
June 11, 2021 at 08:42 am - Permalink
Speaking from a galaxy far, far away from the re-entrant language here, I never fully understood the reason that a control procedure needed an option to handle a kill event. I still do not. When do folks kill controls (versus disabling or hiding them). I also am not sure why a kill control event should be passed to the meta-processing of a kill panel controller code. The approach that always made the most sense to me is that actions that must be triggered when panels are to be killed are to be handled by a WindowHook on the panel itself. They should not be handled by "hiding" them in a kill control event.
So perhaps I should ask ...
* When should a user have to program for a kill control event (and never have to consider the bigger kill panel event)?
* Is this discussion about Igor not recognizing the difference between the two kill calls (kill control versus kill panel) in a control action procedure?
June 11, 2021 at 10:15 am - Permalink
The history is complicated.
A long time ago, no check for re-entering that code was made, be we also didn't have the -1 control about to die event, either. That event is relatively recent (for my at 68 years old, that means within the last 10 or 15 years). The change to control re-entrancy was made in response to an internal bug report Feb 12, 2016, so really recent in old-person time.
OK, I looked at the source code with subversion blame, and it pre-dates when we started using subversion, which was in 2005.
So I think what's going on here is that your use-case is a product of smallish fractions: those who use modern action procs * those who kill windows from button action procs * those who use the control being killed event.
But it does suggest that this is a change in behavior and as such should be restricted to Igor 9 if not deep-sixed entirely. The changes shouldn't be difficult- if you don't want to use the control killed event, simply don't respond to it. You are using conditional or a switch so that you only respond to the exact events you want, right?
The scenario you outline (if you can post a simple example, I would look into it) includes the use of PauseForUser, which also makes a re-entrancy exception. But the blocker counts levels of re-entrancy, so it's possible that, indeed, you have two levels of re-entrancy. Add another term to my product of smallish fractions: those who use PauseForUser. My head is beginning to hurt...
It does seem proper that controls should receive their About to Die event, and that you should be able to use the same code with PauseForUser as you use for other cases, so I'm inclined to keep the change for Igor 9. How many places does this affect you?
June 11, 2021 at 10:18 am - Permalink
@jjweimer We were both composing our responses at the same time, and I won the race...
You might want a Control Being Killed event to clean up stuff related to a particular control, like waves for a listbox. Perhaps Tony can tell us what he uses it for.
The control killed event is not a past-tense killed. That's why I have been using phrases like "Control About to Die"- it is delivered via the controls action procedure *before* the control dies, so you can still use ControlInfo and anything else that requires the presence of the control. But it is the last time the action proc will be called!
Your window hook doesn't get control killed events, and your control action procedure doesn't get window killed events. So there *shouldn't* be confusion. But I can easily see that this could get confusing.
June 11, 2021 at 10:26 am - Permalink
In reply to Thanks, John. So now the… by tony
The control being killed event comes at the start of the code that implement KillWindow. So strictly speaking, it is *during* the call to KillWindow, and *before* the window actually dies. If you put a breakpoint on your handler for the -1 event you will see that the stack includes the first call to your control action proc stopped at the KillWindow line (see the little blue arrow in the debugger code window).
June 11, 2021 at 10:30 am - Permalink
OK, for the next nightly build, I have reverted the change to Igor 8. So at the least there is an easy way to write conditional code for the change by checking IgorVersion() >= 9.
June 11, 2021 at 10:39 am - Permalink
Thanks John. In summary
--> Control About to Die gets passed to the control procedure, the control procedure does what it needs, and then the control is removed
--> Window About to Die passes Control About to Die to all controls, all controls have a party queuing up their procedures, all controls are removed, the window control procedure does its actions, and finally the window is removed.
I am still curious why anyone absolutely needs to kill a control (versus just disabling or hiding it). I understand why a control should have a flag to handle actions if it is about to be killed *because the window containing it is being killed*.
And I see the issue as handling the case where for example a Button procedure calls KillWindow on its own window but also has other code in its action procedure either before or after the KillWindow call?
Gosh, now my head is spinning.
June 11, 2021 at 10:45 am - Permalink
In reply to Thanks John. In summary -->… by jjweimer
Yes, that's right.
Well, part of the destruction routine for destroying a window is to first kill all the controls it contains. And that involves sending each control the About to Die event.
There is also the KillControl operation. That results in just the one control getting the About to Die message.
In my personal style I haven't used KillControl much. For tab controls I tend to use a panel subwindow inside the tab, and hide the subwindow when the tab isn't active. There is a style for tab controls where you actually kill the controls.
But there are also some use cases that involve creating a variable number of, say, SetVariable controls, maybe one for each channel in an instrument that might have different numbers of channels in different models. You might kill and rebuild the controls to change the number of channels.
Exactly. It's pretty common to have a Done button or (if you want it to look like an Igor dialog) a Do It button. And the panel goes away when you click that button. It is straight-forward to simply call KillWindow $(s.win) from the button's action procedure. The problem was that if the action procedure isn't re-entrant, the KillWindow call finishes, and the window is dead, before the action procedure returns. And then the queued-up Control About to Die events no longer have the window or the controls they go with so they are discarded.
So my fix, which Tony asked for, and which apparently bit him in the posterior, was to allow a re-entrant call to the action proc so that KillWindow could send Control About to Die events *before* the window actually died.
I have torsion injuries in my neck!
One thing we have learned over the last 30 years is that if you make a feature available, our customers will find *very* creative uses. Any time we call user code for things like a window hook, we have to bracket it with tests for the window still being alive. We have had crashes from windows being killed in the most bizarre circumstances!
June 11, 2021 at 04:19 pm - Permalink
In reply to @jjweimer We were both… by johnweeks
Let's say I make a panel-based GUI for some operation, and associated with that is a temporary package folder that should not persist beyond the lifetime of the panel. If I want to catch the window being killed in order to clean up the package folder, it's convenient to use the control procedure kill event. Of course one could write a separate window hook, but most likely I already have a control procedure for, say, a button that I can use to catch any creative way that the user finds to kill the panel (like killing the host of an external panel, for instance). That's my typical use of the kill event.
Now let's say I have a 'Do It' and a 'Cancel' button. As things were, the cancel button control procedure should 1. kill the panel and 2. clean up.
My confusion arose from the PauseForUser exception, which allowed me to get away with a Cancel button that simply does 1. kill the panel (because in this case the kill event catches the kill event and does the cleanup). I wasn't asking for a change, just trying to understand the possibilities.
Provided the 'Do It' and 'Cancel' button procedures both kill the panel and cleanup, they should work with both 'old' and 'new' behaviour.
Now let's say I make a second, modal panel to change some package settings, as an example a setting for whether the first panel is an external panel on the left or right side of its host. That's where I might want to kill the first panel and rebuilt it without losing the package folder. Under the old behaviour that works because the control procedures don't see the kill event. Under the new behaviour I add a check for existence of the modal panel before the cleanup, and it will work under both old and new paradigms.
June 12, 2021 at 02:40 am - Permalink
In reply to Speaking from a galaxy far,… by jjweimer
Point taken. If that were the least of my sins I would be happy.
June 12, 2021 at 02:45 am - Permalink
A real world example, filled no doubt with examples of my dodgy programming:
https://www.wavemetrics.com/comment/21761#comment-21761
Here I use KillControl quite a bit because I create temporary buttons in a graph window as attachments to the marquee.
June 12, 2021 at 03:29 am - Permalink
Admittedly, this discussion is causing me to rethink where and how I should use the KillWindow command to remove panels with controls. I have some of the same needs (e.g. using buttons to reposition an external panel on the left/right sides of its host).
June 12, 2021 at 05:44 am - Permalink
@tony So it sounds like whether I make this change or not, you will have cases where you need to code carefully. I tend to think that the same code ought to work whether you use PauseForUser or not, so I'm inclined to keep the change to Igor 9.
But making the change in Igor 9 and not in Igor 8 might leave you needing different code for the two versions?
June 14, 2021 at 12:39 pm - Permalink
I think that I can use a 'belt and braces' approach that will work either way:
Explicitly call my cleanup function if I kill a panel from within a control procedure - backwardly compatible
Check for existence of modal window when reacting to kill event - forwardly compatible
So I think your greater concern should be for the code out there in the wild that could be broken. Usually it's you saying that.
June 15, 2021 at 12:47 am - Permalink
In reply to I think that I can use a … by tony
Yes, that's why I reverted the change in Igor 8. If a change in behavior seems compelling (like the current behavior is just wrong) then we often take a major upgrade as an opportunity to correct old mistakes. Even so, I would probably hesitate on this one if I thought lots of folks would be affected. I suspect that most folks don't even know about event -1.
June 15, 2021 at 09:54 am - Permalink