MATLAB User Interfaces
Passing Data Around User Interface
Most advanced user interfaces require the user to be able to pass information between the various functions which make up a user interface. MATLAB has a number of different methods to do so.
guidata
MATLAB’s own GUI Development Environment (GUIDE) prefers to use a struct
named handles
to pass data between callbacks. This struct
contains all of the graphics handles to the various UI components as well as user-specified data. If you aren’t using a GUIDE-created callback which automatically passes handles
, you can retrieve the current value using guidata
% hObject is a graphics handle to any UI component in your GUI
handles = guidata(hObject);
If you want to modify a value stored in this data structure, you can modify but then you must store it back within the hObject
for the changes to be visible by other callbacks. You can store it by specifying a second input argument to guidata
.
% Update the value
handles.myValue = 2;
% Save changes
guidata(hObject, handles)
The value of hObject
doesn’t matter as long as it is a UI component within the same figure
because ultimately the data is stored within the figure containing hObject
.
Best for:
- Storing the
handles
structure, in which you can store all the handles of your GUI components. - Storing “small” other variables which need to be accessed by most callbacks.
Not recommended for:
- Storing large variables which do not have to be accessed by all
callbacks and sub-functions (use
setappdata
/getappdata
for these).
setappdata
/getappdata
Similar to the guidata
approach, you can use setappdata
and getappdata
to store and retrieve values from within a graphics handle. The advantage of using these methods is that you can retrieve only the value you want rather than an entire struct
containing all stored data. It is similar to a key/value store.
To store data within a graphics object
% Create some data you would like to store
myvalue = 2
% Store it using the key 'mykey'
setappdata(hObject, 'mykey', myvalue)
And to retrieve that same value from within a different callback
value = getappdata(hObject, 'mykey');
Note: If no value was stored prior to calling getappdata
, it will return an empty array ([]
).
Similar to guidata
, the data is stored in the figure that contains hObject
.
Best for:
- Storing large variables which do not have to be accessed by all callbacks and sub-functions.
UserData
Every graphics handle has a special property, UserData
which can contain any data you wish. It could contain a cell array, a struct
, or even a scalar. You can take advantage of this property and store any data you wish to be associated with a given graphics handle in this field. You can save and retrieve the value using the standard get
/set
methods for graphics objects or dot notation if you’re using R2014b or newer.
% Create some data to store
mydata = {1, 2, 3};
% Store it within the UserData property
set(hObject, 'UserData', mydata)
% Of if you're using R2014b or newer:
% hObject.UserData = mydata;
Then from within another callback, you can retrieve this data:
their_data = get(hObject, 'UserData');
% Or if you're using R2014b or newer:
% their_data = hObject.UserData;
Best for:
- Storing variables with a limited scope (variables which are likely to be used only by the object in which they are stored, or objects having a direct relationship to it).
Nested Functions
In MATLAB, a nested function can read and modify any variable defined in the parent function. In this way, if you specify a callback to be a nested function, it can retrieve and modify any data stored in the main function.
function mygui()
hButton = uicontrol('String', 'Click Me', 'Callback', @callback);
% Create a counter to keep track of the number of times the button is clicked
nClicks = 0;
% Callback function is nested and can therefore read and modify nClicks
function callback(source, event)
% Increment the number of clicks
nClicks = nClicks + 1;
% Print the number of clicks so far
fprintf('Number of clicks: %d\n', nClicks);
end
end
Best for:
- Small, simple GUIs. (for quick prototyping, to not have to implement the
guidata
and/orset/getappdata
methods).
Not recommended for:
-
Medium, large or complex GUIs.
-
GUI created with
GUIDE
.
Explicit input arguments
If you need to send data to a callback function and don’t need to modify the data within the callback, you can always consider passing the data to the callback using a carefully crafted callback definition.
You could use an anonymous function which adds inputs
% Create some data to send to mycallback
data = [1, 2, 3];
% Pass data as a third input to mycallback
set(hObject, 'Callback', @(source, event)mycallback(source, event, data))
Or you could use the cell array syntax to specify a callback, again specifying additional inputs.
set(hObject, 'Callback', {@mycallback, data})
Best for:
- When the callback needs
data
to perform some operations but thedata
variable does not need to be modified and saved in a new state.
Making a button in your UI that pauses callback execution
Sometimes we’d like to pause code execution to inspect the state of the application (see Debugging). When running code through the MATLAB editor, this can be done using the “Pause” button in the UI or by pressing Ctrl+c (on Windows). However, when a computation was initiated from a GUI (via the callback of some uicontrol
), this method does not work anymore, and the callback should be interrupted via another callback. Below is a demonstration of this principle:
function interruptibleUI
dbclear in interruptibleUI % reset breakpoints in this file
figure('Position',[400,500,329,160]);
uicontrol('Style', 'pushbutton',...
'String', 'Compute',...
'Position', [24 55 131 63],...
'Callback', @longComputation,...
'Interruptible','on'); % 'on' by default anyway
uicontrol('Style', 'pushbutton',...
'String', 'Pause #1',...
'Position', [180 87 131 63],...
'Callback', @interrupt1);
uicontrol('Style', 'pushbutton',...
'String', 'Pause #2',...
'Position', [180 12 131 63],...
'Callback', @interrupt2);
end
function longComputation(src,event)
superSecretVar = rand(1);
pause(15);
print('done!'); % we'll use this to determine if code kept running "in the background".
end
function interrupt1(src,event) % depending on where you want to stop
dbstop in interruptibleUI at 27 % will stop after print('done!');
dbstop in interruptibleUI at 32 % will stop after **this** line.
end
function interrupt2(src,event) % method 2
keyboard;
dbup; % this will need to be executed manually once the code stops on the previous line.
end
To make sure you understand this example do the following:
- Paste the above code into a new file called and save it as
interruptibleUI.m
, such that the code starts on the very first line of the file (this is important for the 1st method to work). - Run the script.
- Click Compute and shortly afterwards click either Pause #1 or on Pause #2.
- Make sure you can find the value of
superSecretVar
.
Passing data around using the “handles” structure
This is an example of a basic GUI with two buttons that change a value stored in the GUI’s handles
structure.
function gui_passing_data()
% A basic GUI with two buttons to show a simple use of the 'handles'
% structure in GUI building
% Create a new figure.
f = figure();
% Retrieve the handles structure
handles = guidata(f);
% Store the figure handle
handles.figure = f;
% Create an edit box and two buttons (plus and minus),
% and store their handles for future use
handles.hedit = uicontrol('Style','edit','Position',[10,200,60,20] , 'Enable', 'Inactive');
handles.hbutton_plus = uicontrol('Style','pushbutton','String','+',...
'Position',[80,200,60,20] , 'Callback' , @ButtonPress);
handles.hbutton_minus = uicontrol('Style','pushbutton','String','-',...
'Position',[150,200,60,20] , 'Callback' , @ButtonPress);
% Define an initial value, store it in the handles structure and show
% it in the Edit box
handles.value = 1;
set(handles.hedit , 'String' , num2str(handles.value))
% Store handles
guidata(f, handles);
function ButtonPress(hObject, eventdata)
% A button was pressed
% Retrieve the handles
handles = guidata(hObject);
% Determine which button was pressed; hObject is the calling object
switch(get(hObject , 'String'))
case '+'
% Add 1 to the value
handles.value = handles.value + 1;
set(handles.hedit , 'String', num2str(handles.value))
case '-'
% Substract 1 from the value
handles.value = handles.value - 1;
end
% Display the new value
set(handles.hedit , 'String', num2str(handles.value))
% Store handles
guidata(hObject, handles);
To test the example, save it in a file called gui_passing_data.m
and launch it with F5.
Please note that in such a simple case, you would not even need to store the value in the handles structure because you could directly access it from the edit box’s String
property.
Performance Issues when Passing Data Around User Interface
Two main techniques allow passing data between GUI functions and Callbacks: setappdata/getappdata and guidata (read more about it). The former should be used for larger variables as it is more time efficient. The following example tests the two methods’ efficiency.
A GUI with a simple button is created and a large variable (10000x10000 double) is stored both with guidata and with setappdata. The button reloads and stores back the variable using the two methods while timing their execution. The running time and percentage improvement using setappdata are displayed in the command window.
function gui_passing_data_performance()
% A basic GUI with a button to show performance difference between
% guidata and setappdata
% Create a new figure.
f = figure('Units' , 'normalized');
% Retrieve the handles structure
handles = guidata(f);
% Store the figure handle
handles.figure = f;
handles.hbutton = uicontrol('Style','pushbutton','String','Calculate','units','normalized',...
'Position',[0.4 , 0.45 , 0.2 , 0.1] , 'Callback' , @ButtonPress);
% Create an uninteresting large array
data = zeros(10000);
% Store it in appdata
setappdata(handles.figure , 'data' , data);
% Store it in handles
handles.data = data;
% Save handles
guidata(f, handles);
function ButtonPress(hObject, eventdata)
% Calculate the time difference when using guidata and appdata
t_handles = timeit(@use_handles);
t_appdata = timeit(@use_appdata);
% Absolute and percentage difference
t_diff = t_handles - t_appdata;
t_perc = round(t_diff / t_handles * 100);
disp(['Difference: ' num2str(t_diff) ' ms / ' num2str(t_perc) ' %'])
function use_appdata()
% Retrieve the data from appdata
data = getappdata(gcf , 'data');
% Do something with data %
% Store the value again
setappdata(gcf , 'data' , data);
function use_handles()
% Retrieve the data from handles
handles = guidata(gcf);
data = handles.data;
% Do something with data %
% Store it back in the handles
handles.data = data;
guidata(gcf, handles);
On my Xeon W3530@2.80 GHz I get Difference: 0.00018957 ms / 73 %
, thus using getappdata/setappdata I get a performance improvement of 73%!
Note that the result does not change if a 10x10 double variable is used, however, result will change if handles
contains many fields with large data.