What's the best practice in the following scenario:
I have loop that starts upon button press and during execution the iterations should respond to inputs through the GUI (e.g. slider value change). This is not possible in an uninterruptible loop so therefore, a working but clumsy solution is to use a pause such that:
while true
get(handles.slider1,'value')
pause(0.5)
end
Are there alternative, more elegant solutions to this problem?
Edit: as clarification, a variant that does NOT work (i.e. does not output changing slider values):
while true
get(handles.slider1,'value')
end
You can update the slider value to a global variable.
function slider1_Callback(hObject, eventdata, handles)
global SliderValue;
SliderValue = hObject.Value;
Then access SliderValue from any function.
function pushbutton1_Callback(hObject, eventdata, handles)
global SliderValue;
while a<100
a = SliderValue + a;
end
Related
I'm trying to make a timer that counts down from 20 to 0 (seconds) in GUIDE. In the meantime the user will perform a simple action (clicking a radio button in a group button) and at the end of that 20 seconds a message will appear (depending on which button the user clicked).
I looked around but it seems that there isn't a timer object for GUIDE (why don't they make one since it's so useful??). However I tried to make one and below there's the result, it doesn't work.
I initialised setappdata in MyGUI_OpeningFcn:
% Initialize setappdata
timeout = 20;
setappdata(handles.figure1,'timeout', timeout);
Next_calculation is radio button and timerBox is a static text.
function Next_calculation_Callback(hObject, eventdata, handles)
[..]
timeout = getappdata(handles.figure1,'timeout');
t = timer('Period', 1.0,... % 1 second
'StartFcn', set(handles.timerBox,'String',num2str(timeout)), ...
'ExecutionMode', 'fixedRate', ... % Starts immediately after the timer callback function is added to the MATLAB execution queue
'TasksToExecute', timeout, ... % Indicates the number of times the timer object is to execute the TimerFcn callback
'TimerFcn', #my_timer ... % callback to function
);
start(t)
Once the timer begins, it calls TimerFcn that calls my_timer. I should pass a handle to my_timer, but I don't know exactly how.
function my_timer(hObject, eventdata)
% I think I'm supposed to pass (hObject, eventdata) to my_timer
% handles should be getting the current figure from hObject
handles = guidata( ancestor(hObject, 'figure1') );
timeout = getappdata(handles.figure1,'timeout');
t_left = timeout - 1.0;
% show the updated time
set(handles.timerBox,'String',num2str(t_left));
% update 'timeout'
setappdata(handles.figure1,'timeout',t_left)
You need to use a custom anonymous function for the TimerFcn to pass the necessary data to your timer callback
set(t, 'TimerFcn', #(s,e)my_timer(hObject, handles))
You can then define your my_timer callback as
function my_timer(hObject, handles)
% Do stuff with handles
end
I created an interface which automatically reads in data through the serial port, hence the reason I implemented the BytesAvailableFcn Callback
handles.fileID.BytesAvailableFcnMode = 'terminator';
handles.fileID.BytesAvailableFcn = {#streamData_fastTrak, handles};
The data that is read is displayed in a table chosen by the user (through use of radio buttons in the GUI). When an option is chosen a callback occurs to save the selected radio button to a variable which is saved in the handles struct. I have followed the program step for step and I am sure this callback does occur and that the variable is saved. However when the serial callback occurs the handles struct still has the old option value.
Here is the serial callback code:
function handles = streamData_fastTrak(hObject, eventdata, handles)
handles.num = handles.num + 1;
%receive data through serial
line = transpose(fscanf(handles.fileID, ' %f ' ));
table_data = get(handles.uitable1, 'data');
table_data_style = get(handles.uitable4, 'data');
display(handles.butt_state);
display(handles.num);
if(fix(line(1)) == 1 && strcmp(handles.butt_state, 'style_button'))
table_data_style(handles.select_Indices(1), 2:(length(line)+1)) = num2cell(line);
set(handles.uitable4, 'data', table_data_style);
display(handles.select_Indices);
elseif(fix(line(1)) > 1 && strcmp(handles.butt_state, 'stat_button'))
table_data(line(1)-1, 1:length(line)) = num2cell(line);
set(handles.uitable1, 'data', table_data);
if(line(1) == countStates(handles))
streamSensor_1_2_3(hObject, handles);
handles.time_step = handles.time_step + 1;
end
end
And the radio button callback:
function uipanel2_SelectionChangeFcn(hObject, eventdata, handles)
handles.butt_state = get(get(handles.uipanel2,'SelectedObject'), 'tag');
display(handles.butt_state);
guidata(hObject, handles);
The way I see it there are 2 ways to approach the problem:
The first way (I don't recommend this as much as the second one) is to pass the data you want updates to a string control and have it read back by your serial port function.
The other way that i recommend is to include a dummy button with a call back that calls
handles.fileID.BytesAvailableFcn = {#streamData_fastTrak, handles};
Again - this will update the new "handles" data to the callback function
For example
Assuming a dummy push button with tag PB1
function handles = streamData_fastTrak(hObject, eventdata, handles)
%% do stuff here
%% update handles data
PB1_Callback (handles.PB1,event,dat)
guidata(handles.PB1,handles) %% function ends
%% dummy button callback function%%
function PB1_Callback(hObject,event,handles)
handles.fileID.BytesAvailableFcn = {#streamData_fastTrak, handles};
guidata(hObject,handles) %% dummy button function ends
You can make the dummy button invisible by making the background color of the button same as that of the UI.
When you first declare your callback function for the ByteAvailableFcn in the line:
handles.fileID.BytesAvailableFcn = {#streamData_fastTrak, handles};
Matlab assign the function handle to the event and also pass the handles stucture at this point of time. This is now frozen into the private workspace of the callback. If you change the handles structure later on in your code (as you do when you try to attach the variable handles.butt_state), the callback doesn't know it, it still use the handles structure that was passed when you declared the callback.
There are several ways of getting this value correctly but I'll give 2 of them:
1) get the value from the radio button when needed
in your streamData_fastTrak callback function, query the button state directly from the uicontrol (instead of checking for a saved value)
handles.butt_state = get(get(handles.uipanel2,'SelectedObject'), 'tag');
This way you are sure to get the latest state of the radio button.
2) Store value in appdata
Every time the button state is changed, store the value somewhere, but you still have to query this value when your callback want to execute. A good place to save values are in the appdata (accessed using setappdata and getappdata).
So in your button callback:
function uipanel2_SelectionChangeFcn(hObject, eventdata, handles)
butt_state = get(get(handles.uipanel2,'SelectedObject'), 'tag');
setappdata(handles.uipanel2, 'butt_state' , butt_state );
display(butt_state);
And in your streamData_fastTrak callback:
function handles = streamData_fastTrak(hObject, eventdata, handles)
butt_state = getappdata(handles.uipanel2, 'butt_state' );
%// and the rest of your code ...
First of all, i have to say that i run a Gui that has to axes, axes1, axes2 .Then i open a file an animate it in the axes1.Then i open another file and animate in axes2. The problem is that i have a button,that when it pushed it must provoke the animation of both files in both axes simultaneously. Is it possible ?
My code for the button is
function compare_animation_Callback(hObject, eventdata, handles)
play_Callback(hObject,eventdata, handles);%axes1
play_results_Callback(hObject,eventdata, handles) %axe2
The problem is in the animation code, because the only file that is animated is the second.The code for the animation is:
handles = startAnimation(~,handles)
global VARS_GLOBAL_ANIM
% pause the animation that might currently be running
pauseAnimation;
% prepare animation controls
frames_total = max([handles.mot.nframes]);
set(handles.slider_animate, 'Enable','on','Max', frames_total+.001,'Value', 1,'SliderStep', [1/frames_total 1/frames_total]);
set(handles.slider_speed, 'Enable','on');
speed = get(handles.slider_speed,'Value');
% set some properties for the animation
%not important
%rehash path
set(gcf,'CurrentAxes',handles.axes_animate);
cla reset;
% start animation
VARS_GLOBAL_ANIM.animation_paused = false;
new_animate(handles.skel,handles.mot,1,speed,{},{},{'updateAnimationSlider',handles.slider_animate},true);
Last the last function that is called is new_animate (...), at the end of the function Start_animation. In the function the only code that refers to figure or axes is :
new_animate_initGraphics;
desired_frame_time = VARS_GLOBAL_ANIM.mot(1).frameTime;
if (~isempty(VARS_GLOBAL_ANIM.figure_camera_file))
h = str2func(VARS_GLOBAL_ANIM.figure_camera_file);
feval(h, gca);
end
if (~isempty(VARS_GLOBAL_ANIM.figure_position))
set(gcf,'position',VARS_GLOBAL_ANIM.figure_position);
end
The function new_animate_initGraphics(if is needed i can upload it)is to big to have it all..Of course the code isn't mine. I can't understand how to use the gca properly in order not to lose one animation and both simultaneously??
Any advice will be helpful.
My program is a question/answer task:
-participant must press on space (keyboard) bar to play a sound
-participant must after that press on one of the two button (mouse)
-participant must press on space bar to play a sound
...
The problem is, I want to allow only one press on space bar, because user can press many time on space and play the sound x times.
How to block the figure1_KeyPressFcn while waiting the mouse response, and once we have the mouse response we reactivate the function ?
function figure1_KeyPressFcn(hObject, eventdata, handles)
switch eventdata.Key
case 'space'
%% processing x task
%playing sounds 1000 ms
soundsc(y,Fs);
guidata(hObject, handles); %%// Save handles data
otherwise
disp('error');
end
end
function pushbutton1_Callback(hObject, eventdata, handles)
%processing task
guidata(hObject, handles); %%// Save the handles data
end
function pushbutton2_Callback(hObject, eventdata, handles)
%processing task
guidata(hObject, handles); %%// Save the handles data
end
You could use a global flag, something like this:
global clicked;
clicked = true;
Then, on your figure1_KeyPressFcn function, you only call the switch if the user has clicked, like this:
global clicked;
if clicked
switch eventdata.Key
case 'space'
clicked = false;
%% processing x task
...
end
end
And on both your pushbutton?_Callback's, you add this:
global clicked;
clicked = true;
To set clicked to true and allow figure1_KeyPressFcn to process space keys again.
Background: I have several portion in an GUI to handle different tasks. In 1 portion (portion1) I have text input and send button. So once I will click send it would send one data to my serial port. Another portion (portion2) would receive signal from serial port which have been received from other devices. Both the portions are using two buttons; one to start the work of that particular portion and one to stop the work. I have used global variables and while loop (with boolean) to exit the infinite loop, as I need to send the data or receive data continuously.
My question: Problem is when I am using global variables and the above mentioned way of using infinite loop, if I click portion1, portion1 will start iterate. Now if I click portion2 then portion1 will be stopped. But I need to use them both at the same time and both of them will continuously send and receive data until I click other button (s) to exit the infinite loop.
My sample code for clarification:
function send_Callback(hObject, eventdata, handles)
global firstFlag;
firstFlag = true;
while firstFlag
%Required codes
end
function haltSend_Callback(hObject, eventdata, handles)
global firstFlag;
firstFlag = false;
function receive_Callback(hObject, eventdata, handles)
global secondFlag;
secondFlag = true;
while true
%Required codes
end
function stopReceive_Callback(hObject, eventdata, handles)
global secondFlag;
secondFlag=false;
I have tried to find a solution referring to internet, but most of the solutions are using global variable. It would be better if I could work without global variables. But even if my requirements are fulfilled (as per my question) it would work.
Global or not is not the issue here.
Callbacks in Matlab all run on the same thread.
So when callback1 is running and you trigger callback2, callback2 will interrupt callback1.
callback1 will then only proceed once callback2 is finished.
You can only slightly modify this procedure using the BusyAction property:
http://www.mathworks.de/de/help/matlab/ref/uicontrol_props.html#bqxoija
But this won't help in your case.
In a "proper" programming language, you'd have a sending and a receiving thread running in parallel. You can't do this matlab however - unless you're e.g. willing to write java-code.
If you want to stick with matlab, the closest to thread would be timers.
These would replace your while loops.
E.g. as in the following minimalistic example:
function cbtest()
try close('cbtest');end
f = figure('name', 'cbtest');
% create the timers:
period = 0.2; % period in seconds, in which the timer shall execute
sendTimer = timer('TimerFcn', #sendFcn, 'ExecutionMode', 'fixedDelay', 'Period', period, 'TasksToExecute', Inf);
recvTimer = timer('TimerFcn', #recvFcn, 'ExecutionMode', 'fixedDelay', 'Period', period, 'TasksToExecute', Inf);
uicontrol(f, 'position', [10 10 100 25], 'Callback', #(a,b) start(sendTimer), 'string', 'start1');
uicontrol(f, 'position', [120 10 100 25], 'Callback', #(a,b) stop(sendTimer), 'string', 'stop1');
uicontrol(f, 'position', [10 50 100 25], 'Callback', #(a,b) start(recvTimer), 'string', 'start2');
uicontrol(f, 'position', [120 50 100 25], 'Callback', #(a,b) stop(recvTimer), 'string', 'stop2');
end
function sendFcn(hTimer, timerEvt)
% your send-loop-code
disp('sending');
end
function recvFcn(hTimer, timerEvt)
% your receive-loop-code
disp('receiving');
end
sendFcn and recvFcn here then should contain the code you have within your according while loops.
You can of course lower the period to your needs, I chose the above for testing purposes.
Your problem is that you're executing the loop inside the Callbacks. So when the second button is clicked, the second callback will start and loop infinitely, until it is ended. The first loop will wait for the second Callback to terminate until it can resume. What you need is a main program where you execute your loop. Nesting the Callbacks will make sure they can access and change the local variables without making them global. If you're fine with building a GUI programmatically, try this:
function main()
sendFlag = false;
receiveFlag = false;
while true
if sendFlag
% Your Sending code
end
if receiveFlag
% Your Receiving code
end
end
function send_Callback(~,~,~)
sendFlag = true;
end
% other Callbacks
end
Then in the Callbacks for your Buttons (you could use Toggle Buttons by the way), you simply set the sendFlag and receiveFlag, respectively.
Using GUIDE, you want to use toggle buttons.
I don't exactly know how GUIDE handles the OpeningFcn, so you should probably put a "Start" button into your GUI which basically executes the above program with a few changes:
function startbutton_Callback(hObject,~,handles)
while true
handles = guidata(hObject); % Updates handles structure
sendFlag = get(handles.sendtoggle, 'Value');
if sendFlag
% Your Sending code
end
receiveFlag = get(handles.receivetoggle, 'Value');
if receiveFlag
% Your Receiving code
end
end
Also, create send and receive toggle buttons and set their 'Min' to 0 and 'Max' to 1. This will make them switch their 'Value' property (which you read in the above loop) between 0 and 1 when you click them. In the Callback, you can change what they display:
function send_Callback(hObject,~,handles)
on = get(hObject,'Value');
if on
set(hObject, 'String', 'Stop sending');
else
set(hObject, 'String', 'Start sending');
end
guidata(hObject,handles); % Update GUI data
Now your main function, which starts when you click the start button, runs the loop and just checks the toggle buttons' states to determine whether to send and receive.