How to use 'CancelRequested' property for a progress bar?

I currently have an App Designer program set up to run calculations in batches. While running the batch calculations, I'd like to have a way to stop the process without getting a bunch of errors (which is what happens when I close the app or progress bar window). Using the 'Cancel' button in the dialog box as a way to kick the program out of the loop appears to be the right choice, but the documentation for this feature is too sparse to be of any use. Has anyone gotten this feature to work and willing to share how to code it?
Thanks in advance.

 Accepted Answer

fig = uifigure;
dlg = uiprogressdlg(fig,'Title','Wait',...
'Message','Starting','Cancelable','on','CancelText','Stop this');
wholeRandNum = 37;
for ii = 1:wholeRandNum
%you can put all your other code here
if isempty(findobj(fig)) == 1
dlg.CancelRequested = 1;
end
if dlg.CancelRequested == 1
disp('You Hit Cancel or Exited')
delete(fig)
return
end
dlg.Value = ii/wholeRandNum;
dlg.Message = sprintf('%12.2f%% complete',ii/wholeRandNum*100);
pause(0.1)
end

15 Comments

It works! Thanks for the help!
Is there any way to use the CancelRequested property without a for loop? I don't need a calculation to be repeated multiple times, so I used a while loop instead:
while preview_bar.CancelRequested == 0
%% Loading all the data for the display (video images, localizations, trajectories...)
[app.video_file,app.max_text] = LoadPreview(app);
%% Display properties
app.FrameEditFieldLabel.Enable = 'on';
app.FrameEditField.Enable = 'on'; %enables the frame edit field
app.FrameEditField.Value = 1; %sets it to the first frame
app.l.Position(1,1) = app.FrameEditField.Value; % syncronizes frame number in the slider and the frame edit field
app.ax.XLim(1,2) = size(app.video_file{1,3},3); %adjustment of the slider length to the frame number
app.ax.XTick = 1:((size(app.video_file{1,3},3))/100):(size(app.video_file{1,3},3)); %adjustment of the slider bins
app.FrameEditField.Limits(1,2) = app.ax.XLim(1,2);
%% Enabling of the different display options
app.IDnumberCheckBox.Enable = 'on';
app.LocalizationsCheckBox.Enable = 'on';
app.TrajectoriesCheckBox.Enable = 'on';
app.BrightFieldImageCheckBox.Enable = 'on';
preview_bar.Value = preview_bar.Value + 0.1;
%% Preview of the first frame:
lMoving(app);
preview_bar.Value = preview_bar.Value + 0.1;
close(preview_bar);
end
The value of the loading bar (preview_bar.Value) is updated after certain steps. However, I don't manage to make the cancel button work.
Thanks a lot!
"I don't need a calculation to be repeated multiple times, so I used a while loop instead"
I don't understand your point here. I'm assuming you want to avoid having to make a comparison each loop, but a while loop is functionally the same as a for loop with this at the beginning of each loop.
if (condition)
% Add loop code here
else
break
end
So either way you're going to have to make that comparison no matter what. If you don't want to iterate the code, you're better off just not using any loops at all and using an if/else statement instead
Also, you don't state any exit conditions (i.e. CancelRequested == 1) for your code. And you're using close(), which is for waitbars, not progressbars. Here's the documentation for progressbars:https://www.mathworks.com/help/matlab/ref/uiprogressdlg.html. Unless you switch to progressbars, I doubt much of this thread will be helpful to you.
Hey, thanks for your reply! I think I might have missunderstood the use of the while loop then.
Let's say I want to do some calculations (none of them including any for loop). Where should I put the exit condition (CancelRequested == 1)? Ideally, I would like that the code could break at any point, whenever the user presses the Cancel button.
Unfortunately I don't know of a way to use the exit condition as an interrupt. Your program needs to get to the point in the code where 'if (CancelRequested == 1)' is located in order to trigger.
However, you can make up for this by adding this check wherever you want in the code, and even in multiple places. The main answer to your question is to put it directly in front of any piece of code that you want the 'Cancel' button to stop
Here's how to interrupt using the cancel button:
app.UIFigure = uifigure();
pb = uiprogressdlg(app.UIFigure,'Cancelable','on');
drawnow
for i = 1:10
pb.Value = i/10;
if pb.CancelRequested
continue
end
disp(i)
pause(.5)
end
close(pb)
@Adam Danz That only works within loops. By "interrupt", I was referring to external commands that interrupts code in progress. Also, your code is backwards. If CancelRequested==1, you want the code to stop, not keep going.
> That only works within loops.
No, it works outside of loops as well. The condition that checks the value of pb.CancelRequested can be placed anywhere to check if the cancel button has been pressed.
> I was referring to external commands that interrupts code in progress
From your question, "Using the 'Cancel' button in the dialog box as a way to kick the program out of the loop appears to be the right choice," That's what my answer does.
> your code is backwards. If CancelRequested==1, you want the code to stop, not keep going.
That's incorrect. See documentation for the continue command. It passes control to the next iteration of a loop. So, if the cancel button is pressed, the rest of the for-loop will be skipped.
"No, it works outside of loops as well. The condition that checks the value of pb.CancelRequested can be placed anywhere to check if the cancel button has been pressed."
I already pointed that out earlier in the thread
"That's what my answer does."
Which question are you trying to answer here? My initial question? Cameron answered that a year ago. I'm currently trying to help Jessica, who asked a similar question under Cameron's answer.
"It passes control to the next iteration of a loop. So, if the cancel button is pressed, the rest of the for-loop will be skipped."
Yeah, it skips to the next loop iteration, it's just going to zip through all of the loop iterations while skipping the delays you added in. But in a more general scenario, you'll often have some code in the loop before that check, which will also end up running needlessly. A better solution would be to replace 'continue' with 'break', which will completely exit the loop without excess calculations. Another better solution is just to replace 'continue' with 'close()', though delete(fig) works better for uiprogressdlg in App Designer.
Yes, if you put the main parts of the loop before the cancel-check, then cancelling the progress bar would be ineffective. That's why the cancel-check would go at the top of the loop so that everything in the loop would be skipped after pressing cancel. Note that 1000 skipped iterations consumes less than 0.0001 sec (r2021a, Matlab Online) so once the cancel flag is set, the loop will effectively end.
I agree that a 'break' command could also be used instead of continue and may be better under many circumstances.
Replacing continue with close() or delete() is not a good solution. Closing the progress window does not stop execution of the code. Perhaps a return command would be an alternative depending on what you want to happen upon cancellation.
fig = uifigure();
h = uiprogressdlg(fig);
drawnow()
for i = 1:10
disp(i)
if i==3
close(h) % or delete(h)
end
end
"Yes, if you put the main parts of the loop before the cancel-check, then cancelling the progress bar would be ineffective. "
I was not referring to the main parts of the loop, sometimes you may need supporting code before the main part of the loop. And a 'break' command is better in most circumstances. While 'conitnue' has a use as a separate function, it's rarely needed.
"Closing the progress window does not stop execution of the code."
I was implying that closing the progress window would be done in conjuntion with 'break' or 'return'. And I was more focused on what was the best way to get to the cancel check. At this point you're splitting hairs.
I'll also reiterate that you're arguing over a question that was answered properly a year ago. This is pointless.
Levi, I haven't a need to argue.
I don't like this part of the original answer above,
if isempty(findobj(fig)) == 1
There's no need to destroy the entire figure just to quit a process.
I merely added a comment that shows another way to escape execution using the cancel button of a progress dialog and responded to your criticisms.
Thanks to both of you for your answers. As I mentioned, my progress bar shows how a code is being executed, and there is no for loop. Therefore, as Levi said, I will just introduce the if condition at multiple points of the code. Thanks a lot! :)
Hi guys, I'm having the same issue as Jessica was, I can't seem to trigger the cancel button on my porgress bar. All I want it to do is to terminate the serial connection to my hardware LDS1000 which should eventually lead the code to fail on the next iteration of the loop as it won't be able to write any data to it if the connection had been terminated, but whatever it is that I try I can't seem to get the cancel button to work. I have read all the comments on this thread and I still can't get it to work, any help with this issue would be massively appreciated! I'm sure there's a simple fix that I'm missing
function Plot(app)
for index = 1:1:app.n
pause(1.5)
writeline(app.LDS1000,'1TP'); % Measure the current value in the Y axis
data_y = readline(app.LDS1000); % Read the current value in the Y axis (with the command aatached to it) e.g. TP-1.2 or TP0.4
k_1 = [] ;
k_1 = strfind(data_y,'-','ForceCellOutput',true); % When the data is read and it isn't a 0.y value there is a dash between the command 'TP' and the numerical value,
if k_1{1} == 4 % hence the characters are separated at the dash, so the dash will be the 4th value hence k_1 == 4
d_1 = regexp(data_y,'\-','split'); % extract the numerical value
y = str2double(d_1); % convert in to a double
pause (0.5)
% However, when the data is extracted and it is a 0.y value this doesn't include a dash between the command 'TP' and the numercial value, hence the characters are separated at P
else k_1{1} == 0; %hence the characters are separated at P instead and this only occurs when k does not equal 4 as teh dash doesn't exist hence it is not the value
d_1 = regexp(data_y,'\P','split'); % extract the numerical value
y = str2double(d_1); % convert in to a double
pause (0.5)
end
writeline(app.LDS1000,'2TP'); % Repeat the whole precoess above however now for Y
data_z = readline(app.LDS1000);
k_2 = [] ;
k_2 = strfind(data_z,'-','ForceCellOutput',true);
if k_2{1} == 4;
d_2 = regexp(data_z,'\-','split');
z = str2double(d_2);
pause (0.5)
else k_2{1} == 0;
d_2 = regexp(data_z,'\P','split');
z = str2double(d_2);
pause (0.5)
end
% Reading curent vlues from Heidenhain
write(app.ND280,app.ESC,"char");
write(app.ND280,app.A0100_p,"char");
write(app.ND280,app.CR,"char");
Heid_x = read(app.ND280,16,"uint8");
Heid_X_2 = char(Heid_x);
Heid_Out = string(Heid_X_2);
H_1 = [];
H_1 = regexp(Heid_Out,'\s','split');
if H_1{1,5} ~= ""
H_2 = H_1{1,5};
H_3 = eraseBetween(H_2,6,6);
H_4 = str2double(H_3);
else H_1{1,4} ~= ""
H_2 = H_1{1,4};
H_3 = eraseBetween(H_2,7,7);
H_4 = str2double(H_3);
end
app.X_Heid(1,index) = H_4;
% Plotting and interpolating the read values
if index == 1 % To interpolate it requires to values in X and Y/Z
app.Z(1) = z(1,2); % hence, the initial values for Y/Z are just
app.Y(1) = y(1,2); % plotted which are Z(1) and Y(1)
xlim(app.UIAxes,[-app.s app.s]); % X limit is 50 to -50 on the figure
ylim(app.UIAxes,[-app.s app.s]); % Y limit id 50 to -50 on the figure
title(app.UIAxes,'Reflection Angle in Y and Z vs Stroke');
xlabel(app.UIAxes,'Slide Translation mm');
ylabel(app.UIAxes,'Reflection Angle \mu\theta');
h = animatedline(app.UIAxes,app.X(1,index),app.Y(1,index),'Color','red','LineStyle','-','Marker','*','MarkerSize',2);
g = animatedline(app.UIAxes,app.X(1,index),app.Z(1,index),'Color','green','LineStyle','-','Marker','o','MarkerSize',2);
addpoints(h,app.X(1,1),app.Y(1,1)); % Y(1) values plotted
addpoints(g,app.X(1,1),app.Z(1,1)); % Z(1) values plotted
drawnow;
else index >= 2; % Once an initial value has been obatined all future
app.Z(index) = z(1,2); % nodes will have a previous node, hence the Y/Z
app.Y(index) = y(1,2); % for the current node are read
xx1 = linspace(app.X(1,index),app.X(1,(index-1)),2); % Creating the sample points between the current node and the previous one
yy1 = interp1(app.X(1,(index-1):index),app.Y(1,(index-1):index),xx1); % Interpolating between the current node and the previous one in Y
zz1 = interp1(app.X(1,(index-1):index),app.Z(1,(index-1):index),xx1); % Interpolating between the current node and the previous one in Z
for ci= 2:-1:1
addpoints(h,xx1(ci),yy1(ci)); % Adding the interpolated points for Y
addpoints(g,xx1(ci),zz1(ci)); % Adding the interpolated points for Z
pause(0.1);
end
end
h = animatedline(app.UIAxes,app.X(1,index),app.Y(1,index),'Color','red','LineStyle','-','Marker','*','MarkerSize',2);
g = animatedline(app.UIAxes,app.X(1,index),app.Z(1,index),'Color','green','LineStyle','-','Marker','o','MarkerSize',2);
hold off
legend(app.UIAxes,{'Y','Z'});
drawnow
% Displaying current vlaues:
%y
y_current = num2str(app.Y(index));
app.YEditField.Value = y_current;
%z
z_current = num2str(app.Z(index));
app.ZEditField.Value = z_current;
%x_ACS
x_current = num2str(app.X(index));
app.XACSmmEditField.Value = x_current;
%x_Heid
x_Heid_current = num2str(app.X_Heid(index));
app.XHeidmmEditField.Value = x_Heid_current;
pause(1)
di = uiprogressdlg(app.progress,'Title','Progress Bar','Message','Progress','Cancelable',1);
di.Value = index/app.n;
Value = round(di.Value*100);
Vally = num2str(Value);
percent = '%';
if di.CancelRequested == 1
close(di)
app.LDS1000 = [];
elseif di.CancelRequested == 0
break
end
end
end
Joseph, why do you have break when di.CancelRequested == 0? I believe that would cause your code to kick out of the loop even when you haven't pressed the cancel button, which doesn't sound like what you want.
Have you tried moving break to take place under di.CancelRequested==1 after you close the figure and blank out app.LDS1000? Generally you shouldn't need a case for elseif di.CancelRequested==0.

Sign in to comment.

More Answers (1)

progressbar = waitbar(0,'Determining optimum slope','CreateCancelBtn','setappdata(gcbf,''Cancel'',1)');
wholeRandNum = 215;
for ii = 1:wholeRandNum
%you can put all your other code here
if getappdata(progressbar,'Cancel') == 1
delete(progressbar)
disp('You Hit Cancel or Exited')
return
end
pause(0.1)
waitbar(ii/wholeRandNum,progressbar,sprintf('%12.2f%% complete',ii/wholeRandNum*100));
end
delete(progressbar)

Categories

Find more on Interactive Control and Callbacks in Help Center and File Exchange

Products

Release

R2018a

Asked:

on 10 Jan 2020

Commented:

on 27 Apr 2023

Community Treasure Hunt

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

Start Hunting!