Related
I am running a long data preload within a timer callback, and I would like to be able to stop the callback halfway through with an outside input (for instance, the click of a GUI button).
The stop() function will stop the timer calls, but not the callback function itself.
Here is a simple example:
timerh = timer('TimerFcn' , #TimerCallback,'StartDelay' , 1, 'ExecutionMode' , 'singleShot');
NeedStopping = false;
start(timerh)
disp('Running')
pause(1)
disp('Trying to stop')
NeedStopping = true;
function TimerCallback(obj, event)
% Background data loading in here code in here; in this example,
% the callback simply displays numbers from 1 to 100
for k = 1 : 100
drawnow(); % Should allow Matlab to do other stuff
NeedStopping = evalin('base' , 'NeedStopping');
if NeedStopping
disp('Should stop now')
return
end
disp(k)
pause(0.1)
end
end
I expect this script to display numbers between 1 and (approximately) ten, but the timer callback does not stop until 100.
Strangely, the code reaches the line just before before the pause(1) and correctly prints 'Running', but then it stops there and waits for the timer to finish.
Even more perplexing, if I change the 1 second pause to 0.9 seconds, the timer stops immediately with this output:
Running
Trying to stop
Should stop now
I am aware that Matlab is mostly single-threaded, but I thought the drawnow() function should allow it to process other stuff.
Edit: Specific use behind my question:
I have a GUI with a "next" button that loads several images and shows them all side by side. The images are large so loading takes time; therefore, while the user looks at the pictures, I want to preload the next set. This can be done in the background with a timer, and it works.
However, if the user clicks "next" before the preloading has finished, I need to stop it, show the current images, and launch the preloading for the next step. Hence, the timer needs to stop during callback execution.
This is a demo of how to set up an interruptible callback. The way your example is set up I didn't see the need for an actual timer so I made it as a standard button callback.
note: If you are dead set on using it for a timer you can use exactly the same solution, just assign the startProcess callback to the timer instead of the gui button.
function h = interuptible_callback_demo
% generate basic gui with 2 buttons
h = create_gui ;
guidata( h.fig , h )
% create application data which will be used to interrupt the process
setappdata( h.fig , 'keepRunning' , true )
end
function startProcess(hobj,~)
h = guidata( hobj ) ;
% set the 'keepRunning' flag
setappdata( h.fig , 'keepRunning' , true )
% toggle the button states
h.btnStart.Enable = 'off' ;
h.btnStop.Enable = 'on' ;
nGrainOfSand = 1e6 ;
for k=1:nGrainOfSand
% first check if we have to keep running
keepRunning = getappdata( h.fig , 'keepRunning' ) ;
if keepRunning
% This is where you do your lenghty stuff
% we'll count grains of sand for this demo ...
h.lbl.String = sprintf('Counting grains of sands: %d/%d',k,nGrainOfSand) ;
pause(0.1) ;
else
% tidy up then bail out (=stop the callback)
h.lbl.String = sprintf('Counting interrupted at: %d/%d',k,nGrainOfSand) ;
% toggle the button states
h.btnStart.Enable = 'on' ;
h.btnStop.Enable = 'off' ;
return
end
end
end
function stopProcess(hobj,~)
h = guidata( hobj ) ;
% modify the 'keepRunning' flag
setappdata( h.fig , 'keepRunning' , false )
end
function h = create_gui
h.fig = figure('units','pixel','Position',[200 200 350 200]) ;
h.btnStart = uicontrol('style','pushbutton','string','Start process',...
'units','pixel','Position',[50 100 100 50],...
'Callback',#startProcess) ;
h.btnStop = uicontrol('style','pushbutton','string','Stop process',...
'units','pixel','Position',[200 100 100 50],...
'Callback',#stopProcess,'enable','off') ;
h.lbl = uicontrol('style','text','string','','units','pixel','Position',[50 20 200 50]) ;
end
To see it in action:
I have an external function say "external_func" (seperate .m file)
Inside this function a while loop is called, and this while loop update a variabl named "update_prog"
Now I will pass this value into the GUIDE using
assignin('base', 'update_prog', update_prog); % passing to workspace
I am making this
"update_prog" as global variable and calling it into GUIDE .m file
function pb1_Callback(hObject, eventdata, handles)
global update_prog
% hObject handle to pb1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% else
% set(handles.pb1,'enable','on');
% end
% update_prog first value prints, but it wont updates as the loop in external_func goes on.
drawnow;
set(handles.slider1,'Value',update_prog)
external_func;
so here in the GUIDE .m file I can get the value
"update_prog" but it wont keep up with the while loop. I used "drawnow" but its of no use.
How can I refine this value "update_prog" as the while loop in the "external_func" goes through multiple iterations. [Note: The updated values are there in the callback function of GUIDE, but unless there is a callback the callback function wont update the "update_prog"], so how can I acheive this real-time update inside a call_back function.
[Note: passing the variables through function input is not possible here in my case, so I am looking for alternatives]
Edit1: please consider this link, which has an exampleWhich may clarify you what I am trying to acheive
What I am doing here is
Passing the variable(which is being updated in the while loop of the externel function) into GUI.
I will use this variable to show the progress on the progress bar(Slider).
What is the problem?
1. The variable inside the GUI callback(Consdier I will press a push button and then it will call the function with while loop) will put the updated values into the set(handles.slider,'Value',variable)
By doing this I cant move the slider.
Why?
Callback updates the variable only when I press the push button, next all the updates to the variable will not be updated, so progress bar/slider wont move.
I wouldn't recommend to pass your variable in 3 steps with an intermediate workspace (external->base workspace->GUI). I would rather recommend to pass your variable directly (external->GUI).
Each Matlab figure offers a space to store variables (Application Data) of any type. I would suggest reading the article Share Data Among Callbacks and read the documentation for the 3 functions:
guidata
setappdata
getappdata
This way will offer you much more control over the scope of your variables and you won't need any global declaration.
Below is an example of a simple gui. The gui declare the variable in it's user space (with setappdata), then uses a timer to periodically read this variable (with getappdata).
The external function do whatever you want it to do (just a random number in the example), then to update the variable you use the same setappdata. The only thing you need for that is the handle of the main GUI figure, so in the example I give it as input of the external function.
The GUI also has two buttons to start and stop the update.
The code for the main example GUI 'theGui.m' is :
function h = theGui
%// basic GUI with 2 buttons and 1 slider
h.fig = figure('Position',[433 434 500 100],'Menubar','none','CloseRequestFcn',#my_closefcn) ;
h.sld = uicontrol('Style','Slider','Position',[20 20 460 20]) ;
h.btnStart = uicontrol('Style','pushbutton','String','Start updating','Callback',#btnStart_callback,'Position',[20 50 200 30]);
h.btnStop = uicontrol('Style','pushbutton','String','Stop updating','Callback',#btnStop_callback,'Position',[280 50 200 30],'Max',1,'Min',0);
%// Define the timer
h.t = timer ;
h.t.Period = 0.1 ; %// 0.1s refresh interval
h.t.TimerFcn = {#timer_callback,h.fig} ;
h.t.ExecutionMode = 'fixedSpacing' ;
%// initialise the variable to update in the GUI appdata
update_prog = 0 ;
setappdata( h.fig , 'update_prog' , update_prog ) ;
%// save handles
guidata( h.fig , h );
function btnStart_callback(hobj,~)
h = guidata( hobj ) ; %// retrieve handles
if strcmp('off',h.t.Running) %// Start timer (only if not already running)
start(h.t)
end
function btnStop_callback(hobj,~)
h = guidata( hobj ) ; %// retrieve handles
stop(h.t) %// Stop timer
function timer_callback(~,~,hfig)
update_prog = getappdata( hfig , 'update_prog' ) ; %// retrieve the 'update_prog' variable value
h = guidata( hfig ) ; %// retrieve handles
set(h.sld , 'Value' , update_prog) ; %// update the slider object with the retrieved value
function my_closefcn(hobj,~)
%// this function is only to clean up when the GUI will be closed.
%// It is recommended to delete the timer manually
h = guidata( hobj ) ; %// retrieve handles
stop(h.t) %// Stop timer (in case it is still running)
delete(h.t) ; %// delete the timer
delete(h.fig) ; %// destroy the figure
And the code for external_func.m
function external_func( guiMainFigureHandle )
%// This function will only generate random numbers and push them into the
%// variable 'update_prog' contained in the GUI appdata.
%// This is why this function NEEDS the handle of the gui to be able to
%// access the Application Data space of the gui.
for k = 1:100
randomValue = rand(1) ; %// generate a random value
hfig = ancestor( guiMainFigureHandle , 'figure' ) ; %// make sure the handle provided is the top level figure
setappdata( hfig , 'update_prog' , randomValue) ; %// update the variable value
pause(0.1) ;
end
Edit:
I place this in edit instead of changing the code above because I don't recommend messing with the root object if you don't need to. But in your case it can be a way round your problem.
If your external function doesn't have access to the GUI, it can always update a part of memory which is available for all the programs running in a given Matlab session, namely the root object. The handle for it is reserved and is the same for any program: 0 (although since v2014b there is another way to invoke it : groot, it is still always the same handle for all Matlab).
So in the example above, in theGui.m, use instead:
setappdata( 0 , 'update_prog' , update_prog ) ;
in the main routine, then in the subfunction function timer_callback(~,~,hfig), use:
update_prog = getappdata( 0 , 'update_prog' ) ; %// retrieve the 'update_prog' variable
And your function external_func() doesn't need any extra argument, the update only needs one line:
setappdata( 0 , 'update_prog' , update_prog) ; %// update the variable value
I "suspect" that your update_prog variable in the base workspace is not a global (you must define it to be global in every workspace that you want to use it).
Since your using globals (there are many better ways to do this - but thats not your question) - why don't you simply define the update_prog variable to be global in your external_func function (replace the assign call).
edit put a drawnow in your external_func function. That way when you click on the button it will update.
edit 3
I think I know what you want to do, try this example and see if it does what you want - updated to show how you find the slider object in your code and update inside your loop:
function mygui
% create a figure
f = figure;
% create a uicontrol slider - note that I give it a tag.
uicontrol ( 'style', 'slider', 'Position', [0 200 200 40], 'tag', 'MYSLIDER', 'backgroundcolor', 'white', 'parent', f );
% create a push button which we can press to update
uicontrol ( 'string', 'Press 2 start', 'callback', #(a,b)myLoop(), 'Position', [0 0 200 50] )
end
% This replicates your "external_func" function.
function myLoop()
% since you cant pass in any var -> you need to find the slider object
% you can do this by using findobj and search for the tag of the item
uic = findobj ( 0, 'tag', 'MYSLIDER' );
% find the figure handle (only needed for this demo)
f = ancestor ( uic, 'figure' );
% initialise the value which will be used to update the slider
count = 0;
% create your loop
while ishandle(f)
% incrememt the count variable -> this will end up on the slider value
count = count + 1e-5;
% reset count if > 1 -> so the slider cant go out of range.
if count >= 1
count = 0;
end
set ( uic, 'Value', count );
% initiate a drawnow -> this allows matlab to process GUI events
drawnow();
end
end
The downside of this is you insert a drawnow in your loop -> which could slow it down somewhat.
If this doesn't fix your problem you need to explain better what you want to do... (in my view)
function demo1()
H.f = figure('Name','DEMO1');
set(H.f,'Units','Pixels','Position',get(0,'ScreenSize'));% adjust figure size as per the screen size
H.pb1 = uicontrol('style','push',...
'units','pixels',...
'position',[400 800 280 30],...
'fontsize',14,...
'string', datestr(now)); % datestr(now) is used to get current date and time
end
how i can get the real time clock in gui
This is not really an easy task to perform and you may want to think about your design. This could be wrapped in a class, but not necessarily. In case you do not want to do this you may be able to modify the underlying Java objects, but that seems to be overworking this. I have instead structured this in a more C-style manner with placing all data in a struct and writing functions taking the struct as an argument. However, since matlab does not support passing memory locations you need to return the struct after modifying it.
I have chosen to use a timer object for this. You need to store the timer object somewhere since it needs to be deleted when you are not using it anymore. Further, I have added some code for using a start function and stop function as well. For the timer object. This for seeing that the object actually gets finished during development phase. This is not needed for the final project. Further, you may want to handle the case where the window is closed. This will cause the current implementation to crash since the timer is independent of the figure and does not stop when the figure is closed. You probably want to call stop_timer on closing the figure. Anyway here is the code:
function test()
h.f = figure('Name','DEMO1');
set(h.f,'Units','Pixels','Position',get(0,'ScreenSize'));% adjust figure size as per the screen size
h.pb1 = uicontrol('style','push',...
'units','pixels',...
'position',[400 800 280 30],...
'fontsize',14,...
'string', datestr(now)); % datestr(now) is used to get current date and time
h = start_clock(h);
pause on;
pause(15);
pause off;
h = stop_clock(h);
end
function obj = start_clock(obj)
%TasksToExecute calls the timer object N times
t = timer('StartDelay', 0, 'Period', 1,... % 'TasksToExecute', inf, ...
'ExecutionMode', 'fixedRate');
t.StartFcn = {#my_callback_fcn, 'My start message'};
t.StopFcn = { #my_callback_fcn, 'My stop message'};
t.TimerFcn = {#set_time, obj};
obj.t = t;
start(obj.t);
end
function obj = stop_clock(obj)
stop(obj.t);
delete(obj.t);
end
function set_time(obj, event, arg)
arg.pb1.String = datestr(now);
end
function my_callback_fcn(obj, event, arg)
txt1 = ' event occurred at ';
txt2 = arg;
event_type = event.Type;
event_time = datestr(event.Data.time);
msg = [event_type txt1 event_time];
disp(msg)
disp(txt2)
end
I created a MATLAB Gui using GUIDE. In that GUI I use sliders to set some parameters. I set some reasonable limit to the slider, but I want the user to be able to increase the value over that initial limit.
For example the slider by default has the limits min 1 and max 10. If it is placed at 10 and the user clicks the arrow for increasing, I want to set a new value for max as 11.
To be able to do so, I want to specify the type of user interaction within the sliders' callback function. I want to check, whether the user clicked the increasing button on the slider, and if so, in case the slider is at max value, I want to change the max property.
Is there a way of getting this information about the user interaction?
I recognized that, if the slider is already the maximum value, and the user clicks the sliders' button for increasing, the sliders' callback isn't called. It seems, that the callback function is only called, if the slider is actually moved. So I would guess there are seperate callback function for the different buttons of the slider and I would need to access these.
As you found out yourself, the Matlab callback is not called when the value is already at the maximum (or minimum).
One way would be to retrieve the java handle of the slider, and act on the event that will be fired when you click the slider button, but this would be half way into the 'undocumented' functionalities and has a chance (albeit small in this case) of not being compatible in future releases.
A pure Matlab way to get round your problem is to use another event available from the slider, the KeyPressedFcn.
For example, you could decide that action with the mouse would only move the slider values between the set boundaries, but hitting + or - on the keyboard could override the maximum/minimum and reset them a bit further away.
This is implemented in this minimal example. Save the code below into a single slidertest.m file, then run it. Try to navigate to the min/max with the mouse, then with the + and - keys and see how it works. You should be able to implement more complex behaviour relatively simply if you need to.
Simple slider with expendable boundaries :
function h = slidertest
h.f = figure('Position',[200 200 500 150],'Menubar','none') ;
h.sld = uicontrol('style','slider','position',[20 20 460 30],...
'Min',0 , 'Max',10 , 'SliderStep',[0.01 0.1] , 'Value', 1 , ...
'Tooltip','Use the `+` and `-` keys to override min and max boundaries') ;
h.txt = uicontrol('style','text','position',[20 80 460 40],'String','1','Fontsize',20) ;
set(h.sld,'Callback', {#sld_callback,h} ) %// set the Callback function for the slider
set(h.sld,'KeyPressFcn', {#sld_KeyPressFcn,h} ) %// set the KeyPress function for the slider
function sld_callback(hobj,~,h)
val = get(hobj,'Value') ;
set( h.txt,'String', num2str(val) )
%// put here whatever code has to be executed when you change the slider value
function sld_KeyPressFcn(hobj,evt,h)
minval = get(hobj,'Min') ;
maxval = get(hobj,'Max') ;
val = get(hobj,'Value') ;
keyIncrement = 1 ; %// define that to what suits you
switch evt.Character
case '+'
%// check if we have to increase the 'Max' before we change the value
if (val+keyIncrement) > maxval
set( hobj , 'Max' , maxval+keyIncrement ) ;
end
%// increment the value
set( hobj , 'Value' , val+keyIncrement ) ;
case '-'
%// check if we have to decrease the 'Min' before we change the value
if (val-keyIncrement) < minval
set( hobj , 'Min' , minval-keyIncrement ) ;
end
%// decrement the value
set( hobj , 'Value' , val-keyIncrement ) ;
otherwise
%// if you think about other cases ...
end
%// this is called just to update the display
%// in your case it would insure whatever callback code you have for the
%// slider is executed with the new value
sld_callback(hobj,[],h)
I have a simple, sort of GUI code shown below.
This "test_keypress" function creates a figure, and it responses to the keypress (space).
Now, I want to add a constraint so that Matlab accepts only one keypress for a certain period of time (say, 1 second).
In other words, I want to reject a keypress if it happens within 1 sec after the previous keypress occurred.
How can I do that?
function test_keypress
f = figure;
set(f,'KeyPressFcn',#figInput);
imshow(ones(200,200,3));
end
function figInput(src,evnt)
if strcmp(evnt.Key,'space')
imshow(rand(200,200,3));
end
end
You can store the current time, and only evaluate the imshow command if the key-press occurs at least 100 seconds after the last one.
function test_keypress
f = figure;
set(f,'KeyPressFcn',#figInput);
imshow(ones(200,200,3));
%# initialize time to "now"
set(f,'userData',clock);
end
function figInput(src,evnt)
currentTime = clock;
%# get last time
lastTime = get(src,'UserData');
%# show new figure if the right key has been pressed and at least
%# 100 seconds have elapsed
if strcmp(evnt.Key,'space') && etime(lastTime,currentTime) > 100
imshow(rand(200,200,3));
%# also update time
set(src,'UserData',currentTime)
end
end