i am using GUI to call a terminal command. By using dos(my_command, '-echo') i can get the command output in Matlab's Command Window. Is there anyway to replicate that -echo in a static text in my GUI?
Currently, with my_command, I write the output to a log file, and update the static text's String by this log file after process finishes, but what I want is a live-view like in Command Window: output is displayed line by line in real-time. Thanks.
Update:
#Hoki: another question is: when the console commands are being executed, if I close the GUI then Matlab returns errors unstopable, how can I freeze the whole GUI until the commands are finished?
Thanks to Yair Altman info in this article, I got something to work but it involves hacking into Matlab Java base objects (namely the command window).
It involves attaching a listener to the Matlab command window. Now be careful, save your work often and be prepared to kill Matlab process quite a few times until you get it right ... because every time you have an error in the code you are stuck in a kind of infinite loop (the error/warning is sent to the command window, which triggers your listener, which re-trigger the error etc ...). I had to restart Matlab a good dozen of times just to get the example below to work in a stable way.
That's also the reason why I only attach the listener temporarily. just before sending the dos command and I remove the listener directly after. You can leave the listener permanently or adjust that to your needs but remember the advice from just above. Also consider that the command window can hold a massive amount of character, which you may not want all in your textbox, so you have to manage the text that you get from it (take a subset as in the example), and consider if you want to append or simply refresh the text in the textbox.
The example below seems to be stable as it is, any modification at your own risks ;-)
After request in comment I added 3 functions:
An onCleanup. This is a Matlab functionality to allow last resort action in case something goes wrong (a kind of "catch all" mechanism). Heavily recommended for this kind of program using undocumented functions.
A myCloseRequestFcn which intercept the closing action of the window to remove the listener and avoid error loops.
A scroll_to_bottom function. This one allows to move the text box caret to the end of the text (= to scroll to the bottom in case there is more text than visible space).
Warning: The last functionality could deserve a separate question and again call for undocumented functionality (so the compatibility is never guaranteed). To be able to implement it you need to have the findjobj function available in your Matlab path. If you do not want to download external component, then delete/comment the part of code that uses it and the subfunction scroll_to_bottom (and forget about scrolling the text box, there is no way to do that in pure Matlab). Or you can pick the previous version of the code by looking at the edit history of the post.
function h = gui_MirrorCmdWindow
%% // just to remove the listener in case something goes wrong
closeup = onCleanup(#() cleanup);
%% // create figure and uicontrol
h.f = figure;
h.txtOut = uicontrol(h.f,'Style','edit','Max',30,'Min',0,...
'HorizontalAlignment','left',...
'FontName','FixedWidth',...
'Units','Normalized',...
'Enable','On',...
'Position',[.05 .2 .9 .75]);
h.btnPing = uicontrol(h.f,'Style','Pushbutton',...
'String','Ping',...
'Units','Normalized',...
'Position',[.05 .05 .9 .1],...
'Callback',#btnPing_callback);
guidata(h.f,h)
%// intercept close request function to cleanup before close
set(gcf,'CloseRequestFcn',#myCloseRequestFcn)
%% // Get the handle of the Matlab control window
jDesktop = com.mathworks.mde.desk.MLDesktop.getInstance;
jCmdWin = jDesktop.getClient('Command Window');
jTextArea = jCmdWin.getComponent(0).getViewport.getView;
%% // Get the handle of the jave edit box panel component
jtxtBox = findjobj(h.txtOut) ;
jTxtPane = jtxtBox.getComponent(0).getComponent(0) ;
%// Save these handles
setappdata( h.f , 'jTextArea', jTextArea ) ;
setappdata( h.f , 'jTxtPane', jTxtPane ) ;
function btnPing_callback(hobj,~)
h = guidata(hobj) ;
jTextArea = getappdata( h.f , 'jTextArea' ) ;
my_command = 'ping google.com -n 10' ;
startPos = jTextArea.getCaretPosition ;
set(jTextArea,'CaretUpdateCallback',{#commandWindowMirror,h.f,startPos}) ;
dos( my_command , '-echo' ) ;
pause(1) %// just to make sure we catch the last ECHO before we kill the callback
set(jTextArea,'CaretUpdateCallback',[]) ;
scroll_to_bottom(h.f)
function commandWindowMirror(~,~,hf,startPos)
h = guidata(hf) ;
jTextArea = getappdata( h.f , 'jTextArea' ) ;
%// retrieve the text since the start position
txtLength = jTextArea.getCaretPosition-startPos ;
if txtLength > 0 %// in case a smart bugger pulled a 'clc' between calls
cwText = char(jTextArea.getText(startPos-1,txtLength) ) ;
end
%// display it in the gui textbox
set( h.txtOut, 'String',cwText ) ;
scroll_to_bottom(h.f)
function scroll_to_bottom(hf)
%// place caret at the end of the texbox (=scroll to bottom)
jTxtPane = getappdata( hf , 'jTxtPane' ) ;
jTxtPane.setCaretPosition(jTxtPane.getDocument.getLength)
function myCloseRequestFcn(hobj,~)
cleanup ; %// make sure we remove the listener
delete(hobj) ; %// delete the figure
function cleanup
jDesktop = com.mathworks.mde.desk.MLDesktop.getInstance;
jCmdWin = jDesktop.getClient('Command Window');
jTextArea = jCmdWin.getComponent(0).getViewport.getView;
set(jTextArea,'CaretUpdateCallback',[]) ;
Matlab does not offer a native way to fetch results continuously during the execution of dos, unix and system. Nevertheless there is a possibility to achieve the desired behaviour.
This is the proposed work-around:
Execute your command and pipe the output to a file.
Use the & character to continue execution of the Matlab-code.
Read the contents of this file continuously and update the GUI accordingly.
Introduce a second temporary file indicating the end of the command to stop the update-process.
Here is the code:
% create figure and uicontrol
fh = figure;
txtbox = uicontrol(fh,'Style','edit','Max',30,'Min',0,...
'HorizontalAlignment','left',...
'FontName','FixedWidth',...
'Position',[30 30 450 200]);
% store current path
path = cd;
% delete token to make sure the loop continues as expected
warnstate = warning('off','MATLAB:DELETE:FileNotFound');
delete(fullfile(path,'temp_cmdlog_done'));
warning(warnstate); % restore original warning state
% execute dos-command in background
cmd = 'ping -c 5 192.168.200.1';
dos([cmd,' > temp_cmdlog && echo > temp_cmdlog_done &']);
% refresh text in uicontrol until dos-command is done
while 1
out = fileread('temp_cmdlog');
set(txtbox,'String',out);
if exist(fullfile(path,'temp_cmdlog_done'),'file')
break;
end
pause(0.2); % adjust to suit your needs
end
% delete the temporary files
delete(fullfile(path,'temp_cmdlog'));
delete(fullfile(path,'temp_cmdlog_done'));
% indicate that process finished
set(txtbox,'ForegroundColor',[0,0.5,0]);
Related
I need to wait for a process to finish with basically unknown duration. I call this process via:
output = system('foo.cmd')
Process foo is blocking any further execution of code. The process takes often about 10 seconds to finish and does not return any status but its output value afterwards. Sometimes it takes a second, sometimes a minute or more. To simulate the progress to the user I would like to implement a waitbar in advance of the excution, somehow like:
function f = my_waitbar()
f = waitbar(0,'Please wait...');
for i=1:10
pause(1)
waitbar(i/10,f,'Executing foo');
end
waitbar(1,f,'Finishing, please wait...'); % should wait now and do nothing
end
Then after foo.cmd finished I would close the bar.
f = my_waitbar() % blocks my code from execution, therefore is pointless
output = system('foo.cmd')
close(f)
But by just calling function f = my_waitbar() in advance, the process would not be triggered parallel. How do I execute my_waitbar() without waiting for it to be finished?
Note that everything after system('foo.cmd') is depending on its execution, therefore further code execution has to wait for it as it does now, so system('foo.cmd &') would not work well for me.
EDIT
Please assume that I have no influence on the content of foo.cmd
It's not a great UX choice to have a percentage-based progress bar which may "complete" long before your process does, or never complete because your process finishes sooner. See this question on UX.stackexchange which discusses the alternatives. In short, a spinner is more conventional.
You can create a custom load spinner fairly easily. This uses an undocumented but simple method to display an animated gif within a figure without having to use code to advance the frames (which would not work for an asynchronous task!).
There are several options for animated gifs already shipped with MATLAB, I've chosen to use this one:
Here is the code and the result (note that in reality the result is animated!)
function hFig = loadspinner()
% Main path for installed MATLAB images (and other files)
fp = fullfile(matlabroot,'toolbox','matlab');
% Path to the image we want, there are other options in this folder...
fp = fullfile(fp, 'sourcecontrol\release\images\spinner.gif');
% Create the figure
hFig = figure('Color', 'w', 'NumberTitle', 'Off', ...
'Resize', 'Off', 'Menubar', 'None');
% Get image size to reduce hard-coding in case we change image
sz = size( imread( fp ) );
% Insert the animated gif into a HTML pane to enable the animation
je = javax.swing.JEditorPane('text/html', ['<html><img src="file:/', fp, '"/></html>']);
[~, hc] = javacomponent(je,[],hFig);
% resize figure and image
hFig.Position(3:4) = [220,sz(2)+35];
set(hc, 'pos', [(220-sz(1))/2-2,6,sz(1)+4,sz(2)+4])
% Add text
annotation( hFig, 'textbox', [0,0.9,1,0], ...
'String', 'Loading, please wait...', ...
'LineStyle','none', ...
'margin', 0, ...
'verticalalignment', 'top', ...
'horizontalalignment', 'center' );
end
You could use this the same as you showed for the waitbar
f = loadspinner();
output = system('foo.cmd')
close(f);
As Wolfie already mentionned, a standard progressbar is not suited to wait for a process with unknown duration (or unknown iterations). In these cases you better use a spinner (gif file), a circular waitbar (good choice on the File Exchange: cProgress), or an indefinite progressbar. This is what I used for this example:
Now how to make it possible. Since I cannot replicate your exact process foo.cmd, I replaced it by the dos command dir. So for the base line example:
tic
command = 'dir' ;
[~,cmdout] = system(command) ;
toc
>> Elapsed time is 1.547987 seconds.
1.5 second is enough to notice, and cmdout does indeed contain the list of files and directories. So I'll assume this is as close as your case than can be.
To be able to monitor the end of the process, I will package the call to dir (or in your case the call to foo.cmd) into a batch file which will:
Check if a file ("MyProcessIsFinished") exist. If yes delete it.
Call the process of interest, redirect the output to a file.
Create an empty file "MyProcessIsFinished"
This allow MATLAB to call the batch file without waiting for the result (using &). You then make MATLAB wait until it detects the file. When detected, you know the process is finished, you can close your waitbar and continue execution of your code. You can then read the file containing the results you used to get in cmdout.
% initialise flag
processFinished = false ;
% start the continuous waitbar
hw = mywaitbar(0.5,'Please wait','Waiting for response ...',true);
% call the process in the batch file, without waiting for result
command = 'mycommand.bat &' ;
system(command) ;
% wait for the process to be finished
while ~processFinished
if exist('MyProcessIsFinished','file')
processFinished = true ;
end
end
close(hw) ; % close the wait bar
% now read your results
cmdout = fileread('outputfile.txt') ;
The file mycommand.bat is now:
#echo off
if exist MyProcessIsFinished del MyProcessIsFinished
dir > outputfile.txt
copy nul MyProcessIsFinished > nul
exit
Don't forget to replace the line dir > outputfile.txt by a call to your process and a redirection to a suitable file name. It could look like:
foo.cmd > ReceivedRequest.json
The continuous waitbar: I picked up mywaitbar.m from the file exchange: mywaitbar. The code is nice but I had to change a couple of things to improve the timer management so if you want a working version there will be a couple of changes:
Line 109: Add 'CloseRequestFcn',#closeRequestFcn to the properties of the new figure.
Line 120: Add 'Name','CircularWaitbarTimer' to the properties of the new timer
Then at the bottom of the file, add the following function:
function closeRequestFcn(hobj,~)
% Delete the timer
t = timerfindall('Name','CircularWaitbarTimer') ;
if strcmpi('on',t.Running)
stop(t) ;
end
delete(t)
delete(hobj)
That will make a more stable waitbar utility and will get rid of the annoying warning/error messages about timers not managed properly.
I am currently trying to write an experiment in MATLAB. As part of it, it should accept and record a key response, either 1 or 0. The problem is that I only need the keys to be recorded at one specific time slot, and to be ignored in the other parts of the experiment. The response has to be recorded along with the time it took to respond, and it should be done once only, the earliest (so that once the user has pressed a key, the program does not record the subsequent ones).
So far, I have attempted numerous ways. These are probably nooby workarounds, but I am not good with object oriented programming.
One option is to use
set(gcf,'KeyPressFcn',#KeyDownListener)
where KeyDownListener is
function KeyUpListener(key_hand, key_obj, starting_time)
toc(starting_time)
key_pressed = key_obj.Key; return; end
However, there are two problems: 1) I am struggling trying to get the value from this function back to the calling script; 2) Once MATLAB reads this set(...) piece of code, it keeps capturing every single key pressed. So, basically, if there are 100 trials (each consisting of, say, 5 stages, of which the keypress should only be accepted at stage 4) in the experiment put in a loop, the set(...) will be ignored at the first run at stages 1-3 before it first appears, but will then be present in all the runs from the second, at every stage, 1-5.
Then I tried to put both the calling script and the called function into another function called from an outer script, so that once the control is returned to that higher level script, I put in another
set(gcf,'KeyPressFcn',#mute)
by which mute function performs no actions whatsoever. This seems to work for problem No 2, but it still does not allow me to obtain the value for the keypress callback, and, as I am using pause(..) to allow the user time for response, it does not interrupt with the first key pressed, it waits for the entire time allocated in the brackets for pause.
Passing variable around:
I recommend using setappdata and getappdata to pass variables around between callbacks of a GUI.
You can also read Share Data Among Callbacks for more informations about this aspect.
Assigning and disabling callbacks:
To disable a callback function, you do not need to re-assign the callback to a function that does nothing, you can simply assign an empty array:
% assign the 'KeyDownListener' function and pass one parameter ('var1') with it
set( h.fig, 'KeyPressFcn',{#KeyDownListener,var1})
% then later when you don't need it anymore:
% Disable the 'KeyPressFcn listener, assign 'empty' to it
set( h.fig, 'KeyPressFcn',[])
Full example:
Below is a minimal GUI which demonstrate how to capture a single key press (and the time), then save the data collected into the appdata space of the application, where they are collected again by a 'display' function (where you could also do whatever you want with the data collected).
code for SingleKeyPressDemo.m:
function h = SingleKeyPressDemo
% create a minimal figure with a button and 2 text fields
h.fig = figure ;
h.btn = uicontrol('Style','Pushbutton','String','Start capture',...
'units','Normalized','Position',[0.1 0.6 0.8 0.3],...
'Callback',#StartKeyCapture) ;
h.txtKey = uicontrol('Style','text','String','Key pressed:',...
'units','Normalized','Position',[0.1 0.4 0.8 0.1]) ;
h.txtTime = uicontrol('Style','text','String','Elapsed time:',...
'units','Normalized','Position',[0.1 0.3 0.8 0.1]) ;
% assign a callback to the KeyRelease event to intercept passing the
% eventdata to the command window
set(h.fig,'KeyReleaseFcn',#KeyReleased)
% initialise appdata variables to hold the captured key and the time
setappdata( h.fig , 'CapturedKey' , [] )
setappdata( h.fig , 'Elapsed_time' , [] )
% save the handle structure
guidata( h.fig , h)
function StartKeyCapture(hobj,~)
h = guidata( hobj ) ; % retrieve the handle structure
StartTime = tic ; % Start a stopwatch
% assigne the callback funtion, passing the stopwatch in parameter
set(h.fig,'KeyPressFcn',{#KeyDownListener,StartTime})
% disable the button until a key is pressed (also makes it loose the
% focus, which is handy otherwise the button keeps the focus and hides
% the 'KeyPressedFcn'
set( h.btn , 'Enable','off')
function KeyDownListener(hobj, key_obj, starting_time)
% Detect key pressed and time elapsed
Elapsed_time = toc(starting_time) ;
key_pressed = key_obj.Key;
h = guidata( hobj ) ; % retrieve the handle structure
setappdata( h.fig , 'CapturedKey' , key_pressed ) ; % save the captured key
setappdata( h.fig , 'Elapsed_time' , Elapsed_time ) ; % save the elapsed time
set(h.fig,'KeyPressFcn',[]) % remove listener so new key press will not trigger execution
set( h.btn , 'Enable','on') % re-enable the button for a new experiment
% (optional) do something after a key was pressed
display_results(h.fig) ;
function display_results(hobj)
h = guidata( hobj ) ; % retrieve the handle structure
% retrieve the saved data
CapturedKey = getappdata( h.fig , 'CapturedKey' ) ;
Elapsed_time = getappdata( h.fig , 'Elapsed_time' ) ;
% update display
set( h.txtKey , 'String', sprintf('Key pressed: %s',CapturedKey) ) ;
set( h.txtTime , 'String', sprintf('Elapsed time: %f ms',Elapsed_time) ) ;
function KeyReleased(~,~)
% do nothing
% The only purpose of this function is to intercept the KeyReleased event
% so it won't be automatically passed on to the command window.
Creating GUI programmatically is full of verbose in Matlab, focus on the code in the 2 functions StartKeyCapture and KeyDownListener to achieve what you were asking.
This example will produce the following GUI:
Additional note:
I would also recommend against the use of gcf in a GUI application. It is a handy shortcut when debugging or working in the console with figures opened around, but in a GUI the calls to its own elements should be as self-contained as possible. MATLAB offers way to save the handles of all your GUI elements (all the uicontrols, including the main figure), so you can call them explicitely when you need them. This reduces the risk of errors (imagine your user is also running other figures in the same MATLAB session, if your callback triggers and call gcf when the user was fiddling with another figure, you are going to try to execute code on a different figure than it was intended to ... which can easily result in errors).
Read the documentation on guidata for more information (and/or observe how I've used it in the example above).
Edit:
To avoid the focus coming back to the command window at every key pressed, you also have to also define a callback function to the corresponding KeyRelease event, otherwise the event will automatically be forwarded to the command window (which will take the focus).
A clean way to do is to simply add callback assignment
set(h.fig,'KeyReleaseFcn',#KeyReleased)
once and for all in the figure definition (you don't have to set/undo this callback at each experiment), and then define an empty function function KeyReleased(~,~) which does nothing. This way is implemented in the modified code example above.
Another way to do without the extra empty function, would be to simply define the callback in line at the assignment time:
set(h.fig,'KeyReleaseFcn','disp([])')
This way you do not need to have an empty KeyReleased function.
Note however that a callback function must be defined (either inline or later in the code). Simply assigning empty to the callback will not work. (e.g. both options below will fail to intercept the event and will forward it to the command window:
set(h.fig,'KeyReleaseFcn','') % not working
set(h.fig,'KeyReleaseFcn',[]) % not working either
I have Matlab GUI that uses push button that executes certain code.
all of that works, except that when code reaches for loop, execution does not seem to enter that.
1) Any idea how for loop is implemented within framework of Matlab GUIDE code?
2) is there a way to debug GUI code ( only way I could was trough 'disp' statements,) using breakpoints, as when I run the GUI, breakpoints get removed.
after having read the solutions, I found that
(1) happened because the for loop index was not changing as length of array
that I was indexing on, was not changing. I explain this in the code.
(2) My bad, I was putting breakpoints before running the GUI, when I did the other way around, it breaks fine.
% Opening func
function Regression_OpeningFcn(hObject, ~, handles, varargin)
NoiseMin = -12;
NoiseMax = 10;
NoiseRes = 2;
handles.noiseMin = NoiseMin;
handles.noiseMax = NoiseMax;
handles.noiseRes = NoiseRes;
%**this executed when value changed in edit text box***
function noiseMinDbEditText_Callback(~, ~, handles)
handles.noiseMin = str2num(get(handles.noiseMinDbEditText,'String'));
% When GUI is running, following shows change from -12 sucessfully
disp(strcat('Noise Min = ',num2str(handles.noiseMin)));
function noiseMinDbEditText_CreateFcn(hObject, ~, ~)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
%Similar code for noiseMax and noiseRes(not shown here)
`% Here, code enters the Pushbutton callback
function StartRegressionPushButton_Callback(~, ~, handles)
snr_res = handles.noiseRes;
% here, snr_vecs still shows [-12 2 10]
snr_vecs = [handles.noiseMin:handles.noiseRes:handles.noiseMax];
So, basically, when GUI runs, entering and changing the values in text box corresponding to min, max and res snr variables shows me the change, but as soon as I enter the pushbutton dialog box, the changed values( that were captured in global variable "handles") do not show up as I break in the code there.
Any help with this? [for loop problem arose as I was indexing based on snr_vecs array, which is not changing, and so for loop was executing,but not as it should be]
sedy
Using Matlab guide
Guide is basically only a tool to create fig. files. All you can do with guide you could do yourself programmatically. Creating ui-elements works by hand just as easy as with guide (I actually prefere creating gui elements programmatically, since I think guide is very poorly coded)...
Every guide-elements has its callbacks which have to be coded somewhere, usually (I think 100% of the time) the fig file has the same name as the .m file. Find the corresponding .m-file and go to the callback you are interessted in. There you can place breakpoints just as easy as in any other piece of code.
Note: you can even change the code without having to reopen the fig file!
For loops or anything that works in regular code works for ui-element code.
The reason for handles not getting updated was missing following statement at end of function callback whose variable needs to be used in other callback.
guidata(hObject,handles);
I fixed that and it works nicely.
sedy
In a Matlab function, I would like to know the last time a user interacted with the Matlab GUI. By matlab GUI, I mean basically, a user typing in the command window, or in the editor.
The algorithm I wish to implement is essentially:
If it's been a while, the function will not grab focus, but operate in the background.
If the user has recently interacted, presumably he/she is interested "right now" in the results, and the function will grab focus.
This is a tough one ! Here is a proposition to do what you want with the command window only, based on this undocumented code and persitent variables.
I used two functions: CW_listen and CW_callback. A call to CW_listen (or CW_listen(true)) starts to listen to the command window, while a call to CW_listen(false) stops listening. While listening is on, any action performed on the command window trigs a call toCW_callback.
Here are the two functions:
function CW_listen(b)
% Default value
if ~exist('b', 'var'), b = true; end
% Get the reference handle to the Command Window text area
jDesktop = com.mathworks.mde.desk.MLDesktop.getInstance;
try
cmdWin = jDesktop.getClient('Command Window');
jTextArea = cmdWin.getComponent(0).getViewport.getComponent(0);
catch
commandwindow;
jTextArea = jDesktop.getMainFrame.getFocusOwner;
end
% Instrument the text area's callback
if b
set(jTextArea,'CaretUpdateCallback',#CW_callback);
else
set(jTextArea,'CaretUpdateCallback',[]);
end
and
function CW_callback(varargin)
% Define a persistent variable
persistent last_call;
if isempty(last_call)
last_call = clock;
else
ts = clock;
Dt = etime(ts, last_call);
% Update the status bar
dt = javaMethod('getInstance', 'com.mathworks.mde.desk.MLDesktop');
if dt.hasMainFrame
dt.setStatusText(['Ellapsed time: ' num2str(Dt) 's']);
end
if Dt>5
fprintf('So long !\n');
last_call = ts;
else
% Do nothing
end
end
I also dislayed the ellapsed time in the status bar, it was useful for developping the code and adds a quite cool feature.
You can replace the time in seconds (here 5s) and the fprintf('So long !\n'); by any action of your choice. Be aware that inserting any kind of display outside of this if statement will result in an infinite display loop ...
For the moment I don't see how one could transpose this to the editor, but if you search in Undocumented Matlab you may find how to do it ;)
I'm designing a GUI using GUIDE in MATLAB R2014b. My program has a long loop (takes 2~5h to process). I want have a button in my GUI that the user stop the process every time he/she want (The GUI is updating graphs and texts continuously based on result of loops). Something like pressing Control+C not after ending a loop. How can I implement this?
PS.
I don't want MATLAB remove my workspace. The user can continue the process with previous loaded data and workspace by changing some options in GUI.
Here is a trick that should work: Somewhere in the GUI, like in its OpeningFcn for instance, initialize a flag named for example StopNow to false and store it in the handles structure of the GUI. Then in the loop that takes long to execute, put an if statement with a call to return whenever the flag is set to true. That will stop the execution of the loop and you will have access to your data. You can make a pushbutton to change the flag value.
Sample code: I made a simple GUI that starts enumerating digits in a for loop and printing them in a text box. When you press the STOP button, the flag is set to true and the loop stops. If something is unclear please tell me.
function StopGUI
clear
clc
close all
%// Create figure and uielements
handles.fig = figure('Position',[440 500 400 150]);
handles.CalcButton = uicontrol('Style','Pushbutton','Position',[60 70 80 40],'String','Calculate','Callback',#CalculateCallback);
handles.StopButton = uicontrol('Style','Pushbutton','Position',[250 70 80 40],'String','STOP','Callback',#StopCallback);
%// Initialize flag
handles.StopNow = false;
handles.Val1Text = uicontrol('Style','Text','Position',[150 100 60 20],'String','Value 1');
handles.Val1Edit = uicontrol('Style','Edit','Position',[150 70 60 20],'String','');
guidata(handles.fig,handles); %// Save handles structure of GUI. IMPORTANT
function CalculateCallback(~,~)
%// Retrieve elements from handles structure.
handles = guidata(handles.fig);
for k = 1:1000
if handles.StopNow == false
set(handles.Val1Edit,'String',num2str(k));
pause(.5) %// The pause is just so we see the numbers changing in the text box.
else
msgbox('Process stopped');
return
end
end
guidata(handles.fig,handles); %// Save handles structure of GUI.
end
function StopCallback(~,~)
%// Retrieve elements from handles structure.
handles = guidata(handles.fig);
handles.StopNow = true;
guidata(handles.fig,handles); %// Save handles structure of GUI.
end
end
Screenshot after pressing the STOP button:
Hope that helps!
Best solution would be to use separate threads (one for the user interface and one for the processing) maybe using parallel toolbox. Anyhow this would be then quite complex to synchronise both.
So, here is a simple solution that only relies on anonymous functions (to delegate interface refreshing outside the processing block) and on drawnow function (to force the graphical interface to process its messages).
Sample application
The sample application to work with is very basic. It contains:
A settings panel (with only one setting, the number of iterations for the processing block)
A progress bar
A Go/Cancel button
NB: Source code is quite long (about 250 lines, mainly due to gui creation), so I dropped it here.
GUI creation is not important. The most important parts are the processing block, the anonymous functions to instrument the code and the callbacks to react to the GUI events. I will thus detail theses only.
The processing block
Processing block is a simple routine:
function [] = processing(count, instrumentation)
%[
for ki = 1:count,
instrumentation.CheckCancel();
instrumentation.Progress((ki-1)/count);
if (ki > 1), pause(1); end
instrumentation.CheckCancel();
instrumentation.Progress(ki/count);
end
%]
end
The only special thing about it is that it takes an additional instrumentation structure. This structure has two fields that points to two anonymous functions defined by the caller (i.e. the graphical interface). We'll see shortly how.
CheckCancel is a function in charge of raising an error if the user wants to stop the processing.
Progress is a function on which to delegate progression report.
Here is how the anonymous functions are connected to the graphical interface (see doProcessing sub-function in the code):
% Connect instrumentation callbacks with the gui
instrumentation.CheckCancel = #(ratio)onCheckCancel(dlg);
instrumentation.Progress = #(ratio)onProgress(dlg, ratio);
% Perform the processing
processing(settings.NumberOfIterations, instrumentation);
Progress and CheckCancel handlers
Here is the handler defined by the GUI for Progress:
function [] = onProgress(dlg, ratio)
%[
% Update the progress bar value
data = guidata(dlg);
uiprogress(data.handles.pbProgress, ratio);
% Force interface to refresh
drawnow();
%]
end
This is simple, it just updates progressbar control and forces the GUI to refresh (remind that matlab is single-threaded and is currently executing the processing block, so we need to force GUI refreshing).
Here is the handler for CheckCancel:
function [] = onCheckCancel(dlg)
%[
% Force interface to process its events
drawnow();
% Check 'UserData' has not been modified during events processing
guiState = get(dlg, 'UserData');
if (~isempty(guiState) && ....
strcmp(guiState, 'CancelRequested') || strcmp(guiState, 'CloseRequested'))
error('System:OperationCanceledException', 'Operation canceled');
end
%]
end
Again, this is quite simple. We force the GUI to process events (buttons clicks, etc...) and then we read if UserData was modified to some expected value. If so, we raise an exception to stop the processing. Remind again that currently executing code is the processing block.
GUI events handlers
The GUI has only two interesting events. Either the user clicks the close button, either he/she clicks the Go/Cancel button.
NB: Remind that even if matlab is locked in executing the processing block, GUI events are still processed because processing block is calling drawnow from time to time (by the mean of the instrumentation delegates).
Here is the code for the close button:
function [] = onCloseRequest(dlg)
%[
% If already in computation mode
if (~isempty(get(dlg, 'UserData')))
% Just indicate close is requested and leave immediatly
set(dlg, 'UserData', 'CloseRequested');
data = guidata(dlg);
set(data.handles.btnGoCancel, 'Enable', 'off', 'String', 'Cancelling...');
return;
end
% Immediate close
delete(dlg);
%]
end
This is simple, if we are in running mode, we just signal we want to stop, else we close the dialog immediately.
Here is the code for the go/cancel button:
function [] = onGoCancelClick(dlg)
%[
% If already in computation mode
if (~isempty(get(dlg, 'UserData')))
% Just indicate cancel is requested and leave immediatly
set(dlg, 'UserData', 'CancelRequested');
data = guidata(dlg);
set(data.handles.btnGoCancel, 'Enable', 'off', 'String', 'Cancelling...');
return;
end
% Go into computation mode
[settings, err] = tryReadSettings(dlg);
if (~isempty(err))
waitfor(msgbox(err.message, 'Invalid settings', 'Error', 'Modal'));
else
enterComputationMode(dlg);
err = doProcessing(dlg, settings);
leaveComputationMode(dlg, err);
end
%]
end
It's a little longer, anyhow this is the same. If we're in running mode, we just indicate we want to stop; else, the interface is in normal mode, and we start the processing.
Functions tryReadSettings, enterComputationMode and leaveComputationMode are just glue to update controls in the interface and nicely report for errors or cancellation.
Conclusion
We have designed a responsive graphical interface relying only on drawnow and anonymous functions. This is of course just a trick and a better solution would be to use multitasking.
The graphical interface was created here programmatically. Principle is the same if build with GUIDE or with the help of the GUI Layout toolbox.
Instrumentation callbacks can further be improved, for instance, by adding a Log field to report processing details in a textbox or to some back-end similar to Log4Net (with just a message level and message value). Or by adding callback for intermediate results.
Interface can also be improved or modified, for instance why not to run processing whenever a setting is modified (i.e. just stopping any current run and without the need to manually click on a 'Go/Cancel' button each time).
There are a lot of possibilities. Here, just providing some ground application to start with (or not ...).