Abort from function called by live updating SetVariable or Slider

I have a situation where I would like to, in some cases, abort a function called by a SetVariable or Slider where I have enabled "live = 1". It seems that function containing the Abort statement is being called continuously before the function is actually aborted, so I get an endless loop of Abort windows, and my only option is to kill the Igor instance through the task manager. I don't want to reproduce the code for the entire GUI here, but hopefully a few lines will be enough to realize what is happening.

First, in the function that creates the GUI, I have (for example):
SetVariable $("setvar0"+windownum) title="X-Index/Position",pos={115,10},size={135,20},value=xnumlocal,disable=2,win=$(windowname)#btm;
SetVariable $("setvar0"+windownum) limits={0,dimsize(matrixin,0)-1,1},proc=kviewsetvarproc_0,live=1


This calls the procedure:
Function kviewSetVarProc_0(ctrlName,varNum,varStr,varName) : SetVariableControl
    String ctrlName
    Variable varNum
    String varStr
    String varName
    setdatafolder root:kview
    SVAR windowname=windowname
    windowname=winname(0,1)
    SVAR windownum=windownum
    windownum=windowname[12,strlen(windowname)]
    NVAR roworcol=$("roworcol"+windownum)
    kviewupdate(roworcol)
    setdatafolder root:
End


Which calls (most of the function omitted):
Function kviewupdate(roworcol)
   
    variable roworcol // 0 for column, 1 for row, 2 for stack
    setdatafolder root:kview
    SVAR windowname=windowname
    windowname=winname(0,1)
    SVAR windownum=windownum
    windownum=windowname[12,strlen(windowname)]
    NVAR oldlocal=$("old"+windownum)
       
    if(oldlocal==0)
        setdatafolder root:
        Abort "This Kview window will not work properly.\rPlease run the macro 'ConvertKviewWindows' to fix."
    endif
End


Any ideas how to get the Abort window to pop up only once and nicely abort this function?
Can you detect the obsolete window before there's any chance to interact with the GUI? Then you could put up an alert (maybe using DoAlert).

The code that detects the obsolete windows could remove the action procedure from the control that's calling the action procedure. I tried it with a button instead:
Function ButtonProc(ba) : ButtonControl
    STRUCT WMButtonAction &ba

    switch( ba.eventCode )
        case 2: // mouse up
            print "clicked"
            Button $(ba.ctrlname) win=$ba.win, proc=$""
            break
    endswitch

    return 0
End

This makes a button that only clicks once.

Another possibility (that's not really very good) is to create a global variable that flags the fact that you already put up the alert. You have to check it every time the action procedure runs.

John Weeks
WaveMetrics, Inc.
support@wavemetrics.com
Does this happen if your abort handler outside the setvariable or slider control?

Function DoSlider() : Slider
      …
      if (kviewupdate(roworcol) != 0)
          domyabortcodehandler()
      endif
      return 0
end

Function kviewupdate()
     …
      if (oldlocal == 0)
         return -1
      endif
      return 0
end


As a general note, you might also try trapping the cycle by using the structure form of setvariable or slider control functions and checking for mouse up or mouse down sequences before doing things inside of the control function.

--
J. J. Weimer
Chemistry / Chemical & Materials Engineering, UAHuntsville
Would the blockRentry structure field be of use here? To use it you would have to change your control procedure to use the new style of calling with a structure.
Thanks for these ideas, John. Here's what I've found:

Detecting the obsolete window before there is any chance to interact with the GUI is a good idea; however, I don't see how to do this. If I could get some code to execute upon compiling the procedure file, it would be easy... is there a way to do this?

Your second suggestion could be possible, but is not very efficient or practical for me, since there are many controls that would need to be updated.

The third suggestion I was able to implement with some success. Still, the behaviour is a little strange: the Abort window pops up only once now, but the SetVariable or Slider is still reacting as if I was holding down the mouse button (i.e. continues updating) until I close the Abort window and click once more. I'm guessing this is because the control reacts immediately when I press the button down, and the Abort statement is encountered before the button release occurs. Do you see any way around this?

johnweeks wrote:
Can you detect the obsolete window before there's any chance to interact with the GUI? Then you could put up an alert (maybe using DoAlert).

The code that detects the obsolete windows could remove the action procedure from the control that's calling the action procedure. I tried it with a button instead:
Function ButtonProc(ba) : ButtonControl
    STRUCT WMButtonAction &ba

    switch( ba.eventCode )
        case 2: // mouse up
            print "clicked"
            Button $(ba.ctrlname) win=$ba.win, proc=$""
            break
    endswitch

    return 0
End

This makes a button that only clicks once.

Another possibility (that's not really very good) is to create a global variable that flags the fact that you already put up the alert. You have to check it every time the action procedure runs.

John Weeks
WaveMetrics, Inc.
support@wavemetrics.com

Thanks for your suggestion. The behaviour does still occur if I abort the handler outside the control. I am unfamiliar with the structure form of setvariable (just read about it in the help file since you mentioned it), and while it may offer some advantages, implementing it in the current code would require a significant rewrite that I'd like to avoid if possible!

jjweimer wrote:
Does this happen if your abort handler outside the setvariable or slider control?

Function DoSlider() : Slider
      …
      if (kviewupdate(roworcol) != 0)
          domyabortcodehandler()
      endif
      return 0
end

Function kviewupdate()
     …
      if (oldlocal == 0)
         return -1
      endif
      return 0
end


As a general note, you might also try trapping the cycle by using the structure form of setvariable or slider control functions and checking for mouse up or mouse down sequences before doing things inside of the control function.

--
J. J. Weimer
Chemistry / Chemical & Materials Engineering, UAHuntsville

It looks like this might be very useful; however, as I replied to the previous suggestion, I'd like to avoid the amount of rewriting that would be involved in converting to the new style of calling with a structure. Does anything similar exist for the old style controls?

jtigor wrote:
Would the blockRentry structure field be of use here? To use it you would have to change your control procedure to use the new style of calling with a structure.

billyblack wrote:
It looks like this might be very useful; however, as I replied to the previous suggestion, I'd like to avoid the amount of rewriting that would be involved in converting to the new style of calling with a structure. Does anything similar exist for the old style controls?


I don't think this is available with the old style calling method. Would it be difficult to wrap your existing function with a new function using the structure style calling method? The new function could extract the relevant parameters from the structure and pass them on to the old function. The old function wouldn't know it wasn't being called directly and the new function might be able to block the multiple calls.

Another thought would be to call your fixit function (ConvertKviewWindows) from your code instead of aborting and having the user call it. Don't know enough about your code to know if this is feasible.

Finally, your question to John about running code on compile... there may be an onCompile hook (I don't recall), but this would run anytime you compile any function (I think) as long as the code using the hook is loaded and may not be what you want. Could you do a check for a bad window in some kind of an initialization routine when your GUI is created?

You are trying to deal with an upgrade issue where your old version didn't put in any hooks to deal with the new version. It is always a good idea (and hard to anticipate) to put in such hooks.

Igor does not have an onCompile hook. It would probably be difficult to implement without causing lots of issues; I don't really know.

One solution could be to have a window hook- handle the Activated event to check if the window is an old version. But since your original code didn't install a window hook (or did it?) it would require a time machine to install it...

You might try using Execute/P to actually put up the alert. That will cause it to be put up only when nothing else is happening. Create a function to put up the alert (using DoAlert instead of Abort). Call that function using Execute/P; have that function also create a global variable that flags the fact that the alert has already been displayed:
Function ObsoleteKViewVersionAlert()

    NVAR/Z displayed = root:ObsoleteVersionAlertDisplayed
    if (NVAR_Exists(displayed))
        return 0
    endif
   
    DoAlert 0, "This Kview window was made by an older version and will not work properly.\rPlease run the macro 'ConvertKviewWindows' to fix it."
   
    Variable/G root:ObsoleteVersionAlertDisplayed=1     // initialization isn't actually necessary.
end

You can now put a line like this in your code:
Execute/P/Q "ObsoleteKViewVersionAlert()"
Your code can now exit without interfering with anything; the alert will pop up shortly. The /Q flag prevents littering the history with the invocations of ObsoleteKViewVersionAlert().

The only feasible way I can think of to make your obsolete windows stop working would be to rename all your action procedures. That way, the old controls can't call their action procedures. You could provide new versions of the old action procedures that simply contain the Execute/P invocation. The new versions of the window will install the new-named action procedures. I don't know if that is more intrusive than you want.

John Weeks
WaveMetrics, Inc.
support@wavemetrics.com
johnweeks wrote:
Igor does not have an onCompile hook. It would probably be difficult to implement without causing lots of issues; I don't really know.


Igor 6.20 *does* have an AfterCompiledHook. Using it is pretty hardcore, though.

Advanced Topics Help File wrote:
AfterCompiledHook( )

AfterCompiledHook is a user-defined function that Igor calls after the procedure windows have all been compiled successfully.

You can use AfterCompiledHook to initialize global variables or data folders, among other things.

The function result from AfterCompiledHook must be 0. All other values are reserved for future use.

See Also

SetIgorHook, User-Defined Hook Functions

--Jim Prouty
Software Engineer, WaveMetrics, Inc.
Quote:
Igor 6.20 *does* have an AfterCompiledHook.

jeez. It's the first one in the list of hooks (DisplayHelpTopic "User-Defined Hook Functions"). I looked at that list yesterday before writing that reply...

John Weeks
WaveMetrics, Inc.
support@wavemetrics.com
Thank you all, this thread has been very informative for this novice programmer. Using Execute/P without an Abort in the code still caused an error to occur before the warning DoAlert popped up. If I included an Abort after the Execute/P statement, I still got the same problem of the mouseup not being recognized. While this solution is okay, using the AfterCompiledHook worked beautifully and transparently to the end user. This way the end users of this code can have their existing GUIs (in pxp files) immediately updated when they have the new version of the GUI .ipf.