Interrupting a Callback with a Push Button

73 views (last 30 days)
Joseph
Joseph on 13 Aug 2019
Edited: Adam Danz on 25 Jan 2024
I have a while loop inside a certain callback function. Inside the while loop, I want to check whether a certain push button has been pressed. I tried using drawnow but it didn't not interrupt the current callback and process the push button code. Any ideas ?

Answers (2)

Adam Danz
Adam Danz on 13 Aug 2019
Edited: Adam Danz on 25 Jan 2024
"Inside the while loop, I want to check whether a certain push button has been pressed"
The callback function for the pushbutton should toggle a stored value from 0 to 1 (for example). How you store that value will depend on how your app was developed (guide, app designer, or uicontrol). Within the while-loop you can add a condition that checks the value of the variable altered by the pushbutton. If the value is 1, you can use "continue" to skip to the next iteration and you can set a flag that ends the while loop upon its next iteration. Alternatively, use "break" to exit the loop. You can then reset the stored value back to 0.
Note that the placement of this conditional within the while loop is important and you may want to place this condition at sevearal areas within the while loop. All execution of the while loop prior to the condition will still be executed.
If any of that is unclear, please add some detail so additional suggestions can be more concrete.
[update] Here's a functional demo you can run. It creates a simple GUI with two push buttons: Go & Stop. Press Go and watch the command window count to 1000 within a while-loop. Press stop to stop the process. Then press go again to restart from the beginning.
% Create GUI
h.fig = figure();
h.but1 = uicontrol(h.fig,'Style','PushButton','Units','Normalize','Position',[.1,.8,.2,.1],'String','Go');
h.but2 = uicontrol(h.fig,'Style','PushButton','Units','Normalize','Position',[.1,.6,.2,.1],'String','Stop');
h.but2Val = false; % Default stop-button-off
guidata(h.fig, h)
h.but1.Callback = {@button1CallbackFcn,h};
h.but2.Callback = {@button2CallbackFcn,h};
function button1CallbackFcn(hObj,event,handles)
% Responds to "go" button; counts to 1000 in command window
c = 1;
handles.but2Val = true;
while c < 1000
pause(0.1)
handles = guidata(hObj.Parent);
% Check if STOP buttons has been pressed
if handles.but2Val
c = 1000; % Short circuit the while-loop
handles.but2Val = false; % reset stop-button to off
guidata(hObj.Parent,handles)
continue % skip the rest of this loop
end
disp(c)
c = c+1;
end
end
function button2CallbackFcn(hObj,event,handles)
% Responds to "stop" button. Toggles the but2Val to TRUE
handles.but2Val = true;
guidata(hObj.Parent, handles)
end
For app designer
Here's a simple implementation of this idea in app designer. It requires a button that starts a process and a "state button" used to interrupt the process.
% Button pushed function: Button
function ButtonPushed(app, event)
% Count to 1000 unles interrupted
c = 1;
while c < 1000
pause(.1)
disp(c)
c = c+1;
% Check if state button was pressed (from false to true)
if app.StateButton.Value
c = 1001; % short circuit the while-loop
app.StateButton.Value = false; %rest the button state
end
end
end
  5 Comments
Bruno Luong
Bruno Luong on 16 Aug 2019
Edited: Bruno Luong on 16 Aug 2019
You can always control the callback entering and exit displaying something from the callback function (example in the video link).
Note that for GUI you have two properties that is important BusyAction and Interruptible
In your case I recommend to set the primary button Interruptible to 'on' and BusyAction to 'cancel', the Stop button Interruptible to 'off' and BusyAction to 'queue'.
However you cannot control finely the priority level of the tasks, and it entirely possible that the callback is triggered with large latency and too late for you to catch it.
Don't for get to call "drawnow" now and then within your calculation-loop. I believe the drawnow command might trigger some callbacks in the queue.
I can't speak for App Designer, I haven't yet use it (our GUI are fairely complex and use often java stuffs from Yair's undocumented blog so I'm not ready to jump to the new boat).
Beside using GUIDATA, or nested function suggested by Adam and Stephen, you can use SETAPPDATA/GETAPPDATA, SET(hObject,'UserData'), PERSISTENT variables or GLOBAL variable to set/check the state of the button and whereas user click on it. All those are common technique of GUI variable communication, and you select whatever the technique that you feel comfortable with.
Adam Danz
Adam Danz on 16 Aug 2019
@Joseph, a very simple implementation of this idea to App Designer has been added at the bottom of my answer. It only requires a regular button to start the process and a STATE BUTTON to interrupt the process. See the comments within the code for more detail.
State buttons take a value of TRUE or FALSE so when the state button becomes TRUE, the process is interrupted and the state button is set back to FALSE.

Sign in to comment.


Stephen23
Stephen23 on 16 Aug 2019
Edited: Stephen23 on 16 Aug 2019
" Is there another trick you recommend for this issue"
The simplest solution is to use nested functions: simply define a logical value in the main workspace, check it on each loop iteration, and change it using a (nested) callback function. Depending on how the loop is defined, you might also need to consider making the appropriate callbacks interruptible:
I use very similar methods to interrupt demo modes of GUIs, e.g. check out the colornames_cube GUI demo mode:
Important: nested functions do not work with GUIDE, I have no idea if they work with App Designer, but really writing your own GUI code gives much simpler code with much better control over your GUIs anyway...

Categories

Find more on Develop Apps Using App Designer in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!