progress bar in app design (GUI)

Hi!
I need to create a progress bar like the following bar with the APP DESIGN:
% Button pushed function: ProcessDataButton
function ProcessDataButtonPushed(app, event)
% Change button name to "Processing"
app.ProcessDataButton.Text = 'Processing...';
% Put text on top of icon
app.ProcessDataButton.IconAlignment = 'bottom';
% Create waitbar with same color as button
wbar = permute(repmat(app.ProcessDataButton.BackgroundColor,15,1,200),[1,3,2]);
% Black frame around waitbar
wbar([1,end],:,:) = 0;
wbar(:,[1,end],:) = 0;
% Load the empty waitbar to the button
app.ProcessDataButton.Icon = wbar;
% Loop through something and update waitbar
n = 10;
for i = 1:n
% Update image data (royalblue)
currentProg = min(round((size(wbar,2)-2)*(i/n)),size(wbar,2)-2);
app.ProcessDataButton.Icon(2:end-1, 2:currentProg+1, 1) = 0.25391;
app.ProcessDataButton.Icon(2:end-1, 2:currentProg+1, 2) = 0.41016;
app.ProcessDataButton.Icon(2:end-1, 2:currentProg+1, 3) = 0.87891;
% Pause to slow down animation
pause(.3)
end
% remove waitbar
app.ProcessDataButton.Icon = '';
% Change button name
app.ProcessDataButton.Text = 'Process Data';
end
I need the progress bar to update AFTER calling my function, so I changed it into something like that:
% Button pushed function: ProcessDataButton
function ProcessDataButtonPushed(app, event)
% Change button name to "Processing"
app.ProcessDataButton.Text = 'Processing...';
% Put text on top of icon
app.ProcessDataButton.IconAlignment = 'bottom';
% Create waitbar with same color as button
wbar = permute(repmat(app.ProcessDataButton.BackgroundColor,15,1,200),[1,3,2]);
% Black frame around waitbar
wbar([1,end],:,:) = 0;
wbar(:,[1,end],:) = 0;
% Load the empty waitbar to the button
app.ProcessDataButton.Icon = wbar;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Loop through something and update waitbar
my_function(do things...)
<events?>
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% remove waitbar
app.ProcessDataButton.Icon = '';
% Change button name
app.ProcessDataButton.Text = 'Process Data';
end
My problem: I don't know how to keep trace of my updates. Do I need to create an event? I followed this guide: Define Custom Event Data - MATLAB & Simulink - MathWorks Italia, but I did not manage to solve my problem: I need intermediate output before the end of the function to trace my advancements (hope to explain myself, my technical language is poor in this field). Is my path ok or not?
Do you have suggestions? Thank you

5 Comments

@Veronica Taurino it depends on what's controlling your updates. Are the updates done in a loop? Are the updates done in sections of code?
Also, are the updates done in the app file or outside of the app?
Veronica Taurino
Veronica Taurino on 19 Feb 2021
Edited: Veronica Taurino on 19 Feb 2021
The updates are done outside the app, in a loop code within an external function (image) called by the buttoncallback.
See the 3 easy steps below.
Would be nice if this shipped with App Designer, it is a common thing for User Interfaces.
@GT see uiprogressbar which is not embedded in a button but otherwise the same thing as this.

Sign in to comment.

 Accepted Answer

Adam Danz
Adam Danz on 19 Feb 2021
Edited: Adam Danz on 15 Aug 2023
Step 0: Set up the app button
Add a button to your app and add a ButtonPushed callback function. Importantly, the height of the button must be extended to have enough space for the progress bar.
Step 1: Send button handle to external function
Call the external function from within the ButtonPushed callback function and pass the button handle to the pseudo-progress-bar as an input to the external function. I call it a pseudo-progress-bar because it's not a typical waitbar or uiprogressdlg.
app.ProcessDataButton is the button handle.
function ProcessDataButtonPushed(app, event)
myFunction(___, app.ProcessDataButton)
end
Also, define the additional input within the external function.
function myFunction(___, processDataButtonHandle)
Step 2: Set up the progress bar within the external function
Obviously this needs set up prior to the loop that will update the progress bar. Some of this block of code differs from the original example. Thanks to onCleanup in the last line below, the button will return to its original state after the loop or if an error prematurely ends the function.
% Store original button text
originalButtonText = processDataButtonHandle.Text;
% When the function ends, return the original button state
cleanup = onCleanup(@()set(processDataButtonHandle,'Text',originalButtonText,'Icon',''));
% Change button name to "Processing"
processDataButtonHandle.Text = 'Processing...';
% Put text on top of icon
processDataButtonHandle.IconAlignment = 'bottom';
% Create waitbar with same color as button
wbar = permute(repmat(processDataButtonHandle.BackgroundColor,15,1,200),[1,3,2]);
% Black frame around waitbar
wbar([1,end],:,:) = 0;
wbar(:,[1,end],:) = 0;
% Load the empty waitbar to the button
processDataButtonHandle.Icon = wbar;
Step 3: Update the progress bar within a loop
Place the code within the for-loop at the beginning or end of the loop depending on whether you want the progress bar to update before or after your loop processes. This example updates the progress bar at the end of the loop. (note: Code in step 3 was updated on Feb-11-2022 to improve efficiency).
This section uses variables:
  • n - the number of iterations
  • i - the loop variable
n = 10;
for i = 1:n
% % % % % % % % % % % % % % % % % %
% % YOUR LOOP STUFF GOES HERE % %
% % % % % % % % % % % % % % % % % %
% Update progress bar
currentProg = min(round((size(wbar,2)-2)*(i/n)),size(wbar,2)-2);
RGB = app.processDataButtonHandle.Icon;
RGB(2:end-1, 2:currentProg+1, 1) = 0.25391; % (royalblue)
RGB(2:end-1, 2:currentProg+1, 2) = 0.41016;
RGB(2:end-1, 2:currentProg+1, 3) = 0.87891;
app.processDataButtonHandle.Icon = RGB;
% Pause to slow down animation (OPTIONAL)
% pause(.3)
end
To clear the psueo-progress-bar and return the button to normal you have two options
  • Do nothing. The bar will be cleared and the button will return to normal after the external funtion is complete.
  • Exectue clear('cleanup') at any point after the loop within the same function to return the button state.

14 Comments

Thank you, it works! I thought about it... with your solution I mixed the graphic part with the code, so later I may have trouble with changes in the workflow but for now it works fine! Thank you!! I can move on!
I will post another solution if I change it in the future
These are the basic steps to understand how the pseudo-progressbar is created and controlled. it can be adapted to fit your needs.
> with your solution I mixed the graphic part with the code
With any solution, the section of code that controls the progess will also control the progress bar.
Another approach is to create the progress bar from within the app rather than from within the external function. That just requires moving the existing code around. Then your code could merely call an update function to update the progress bar.
There are several ways to control the pseudo-progress bar once you understand what each section in my answer does.
Veronica Taurino
Veronica Taurino on 23 Feb 2021
Edited: Veronica Taurino on 23 Feb 2021
Thank you. Your solution is clear, I get all of it (it is close to my initial idea to solve my issue). I am talking about do not mix the graphic and the code part because an IT engineer I work with told me to use ''events'' to do not mix it, but I have never used events, therefore my previous doubts. I have already written an alternative solution but I get few errors for now. I going to post it if I solve them.
I see. It would help knowing what your loop is going. What would the be the event that triggers the progress update? Is it a variable value change? A graphics update? And does the app have access to that event?
How to make the progress bar (along with the "processing..." text) to be less jittering....
It looks as it the who thing is being "deleted" and "re-drawen" during each iteration, creating an unnatural look&feel appearance of the progress bar.
In the example the "processing..." is rock solid, and I see only the progress bar advances.
THANK YOU
@Mark Golberg I know what you mean. There are things we can do to reduce the flicker but I can't remove it completely.
I just attached a simple app that demonstrates my ideas to this thread. You'll see that it flickers but less frequently.
Why is it flickering?
The progress bar is actually created with 3D image data and when the image is updated, it appears that the entire icon (or image) is cleared and re-rendered. This happens under the hood. However, the transition doesn't always appear as a flicker so at least sometimes it happens fast enough to not notice it. We have no control over this but I'd love to hear anyone else's ideas.
Two changes that will decrease frequency of flicker
1. Assign the RGB image once instead of separately for each R/G/B channel. I should have done this in the first place. This will reduce the rendering cycles by 1/3. Replace these lines,
currentProg = min(round((size(wbar,2)-2)*(i/n)),size(wbar,2)-2);
processDataButtonHandle.Icon(2:end-1, 2:currentProg+1, 1) = 0.25391; % (royalblue)
processDataButtonHandle.Icon(2:end-1, 2:currentProg+1, 2) = 0.41016;
processDataButtonHandle.Icon(2:end-1, 2:currentProg+1, 3) = 0.87891;
with this more efficient method,
currentProg = min(round((size(wbar,2)-2)*(i/n)),size(wbar,2)-2);
RGB = app.processDataButtonHandle.Icon;
RGB(2:end-1, 2:currentProg+1, 1) = 0.25391; % (royalblue)
RGB(2:end-1, 2:currentProg+1, 2) = 0.41016;
RGB(2:end-1, 2:currentProg+1, 3) = 0.87891;
app.processDataButtonHandle.Icon = RGB;
2. If your loop has many iterations and the cycles are fast, you can update the embedded progress bar evey n cycles instead of every cycle. For example, if your loop has 100 iterations, you could update the graphic every 5 or 10 iterations. To achieve this, move the progress bar update code to a conditional block demonstrated below. Use mod(i,n)==0 to determine if iteration i is a multiple of n .
% Update every 10 cycles
for i = 1:n
% % % % % % % % % % % % % % % % % %
% % YOUR LOOP STUFF GOES HERE % %
% % % % % % % % % % % % % % % % % %
if mod(i,10)==0 % update every 10 cycles; improve efficiency
currentProg = min(round((size(wbar,2)-2)*(i/n)),size(wbar,2)-2);
RGB = app.processDataButtonHandle.Icon;
RGB(2:end-1, 2:currentProg+1, 1) = 0.25391; % (royalblue)
RGB(2:end-1, 2:currentProg+1, 2) = 0.41016;
RGB(2:end-1, 2:currentProg+1, 3) = 0.87891;
app.processDataButtonHandle.Icon = RGB; % (royalblue)
end
unfortunately neither of the tow proposed solutions reduced the jitterging.
It's really a pity. I wanted to use the "old" progress bar, and then saw this cool thread about progress bar that open in a push button. really neat. not sure my clients of the GUI would be too happy if I leave this jittering going on...
Both significantly reduced the jittering when I tested it but neither elimiated it completely.
There are other embedded alternatives if you want to get creative. For example, you could customize a knob or guage that rotates from 0 to 100% or a slider that progresses from 0:100%. You could turn the editable property off so users know that the UI component is not meant for user interaction or you could turn on/off the visibility of the component as needed.
Adding to what Adam did above, I did the same thing but with a few changes.
I created a public function in the app that does all this for me so that a more complex loop elsewhere can run on its own. Additionally I am using an "image" and changing the "imageSource" property to fill the bar.
Note that app.Progress is public property of the app which is continually updated
function progressBarUpdate(app,arg,val)
% app
% arg - 'Setup', 'Reset', 'Increment', 'Set', 'Complete'
% val - 0 to 1, based on above selection
switch lower(arg)
case {'setup','reset'}
pos = app.ProgressBar.OuterPosition;
wbar = permute(repmat([1,1,1],pos(4)-2,1,pos(3)-2),[1,3,2]);
wbar([1,end],:,:) = 0;
wbar(:,[1,end],:) = 0;
app.ProgressBar.ImageSource = wbar;
app.progress = 0;
return
case 'increment'
curProg = app.progress + val;
case 'set'
curProg = val;
case 'complete'
curProg = 1;
end
if curProg > 1; curProg = 1; end
if curProg < 0; curProg = 0; end
wbar = app.ProgressBar.ImageSource;
currentProg = min(round((size(wbar,2)-2)*(curProg)),size(wbar,2)-2);
wbar(2:end-1, 2:currentProg+1, 1) = 0.25391; % (royalblue)
wbar(2:end-1, 2:currentProg+1, 2) = 0.41016;
wbar(2:end-1, 2:currentProg+1, 3) = 0.87891;
app.ProgressBar.ImageSource = wbar;
% The below line is for a percentage display which I have
% overlaid on top of the progressbar
app.ProgressBarText.Text = [num2str(100*curProg,'%0.1f'),'%'];
end
Thanks for sharing that, @Oren Lee!
@Oren Lee could you share the full code for the progress bar, please? I tried to follow the steps but the code does not work
@Adam Danz I am trying to use this inside startup function. I am on 2022b. I have used the following code as suggested, and there is no colour change or any colour on the progress button, could you please help me troubleshoot what am I overlooking, Thanks!
originalButtonText = app.ProgressButton.Text;
cleanup = onCleanup(@()set(app.ProgressButton,'Text',originalButtonText,'Icon',''));
app.ProgressButton.Text = 'Processing...';
app.ProgressButton.IconAlignment = 'bottom';
wbar = permute(repmat(app.ProgressButton.BackgroundColor,15,1,200),[1,3,2]);
wbar([1,end],:,:) = 0;
wbar(:,[1,end],:) = 0;
app.ProgressButton.Icon = wbar;
n = 10;
for i = 1:n
%Update progress bar
currentProg = min(round((size(wbar,2)-2)*(i/n)),size(wbar,2)-2);
RGB = app.ProgressButton.Icon;
RGB(2:end-1, 2:currentProg+1, 1) = 0.25391; % (royalblue)
RGB(2:end-1, 2:currentProg+1, 2) = 0.41016;
RGB(2:end-1, 2:currentProg+1, 3) = 0.87891;
app.ProgressButton.Icon = RGB;
% Pause to slow down animation (OPTIONAL)
pause(.3)
end
Tried using pause, and drawnow
Nothing changes.
Also, instead of a button can I use axis? and may be use plot function, by extracting the app.progress value?
What the value for app.ProgressButton.BackgroundColor before you make any changes in startup?
@Adam Danz Didnt assign any, however, a work around, was I used your idea of patch function and instead of i/n, I just used i in x values to update it, I added for loop in various sections of my startup function, so I know which position it is! Thanks Adam, your comments and replies are really useful.

Sign in to comment.

More Answers (0)

Categories

Find more on App Building 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!