GUIs and Callbacks
Matlab has a built in GUI program for constructing GUI applications, however it can be useful to program the GUI directly. More control is obtained by directly programming the GUI, and the GUI can be generalised more easily if it is constructed from a program.
Matlab version 7 introduces a number of very useful enhancements to the language, particularly relevant for GUIs are nested functions, function handles and anonymous functions.
Nested functions allow multiple functions to be defined in the one Matlab .m file, with the subfunctions defined in the file having access to the variables and functions defined at a higher level in the file. In previous versions of Matlab functions had to be defined in separate files (aside from simple inline functions), meaning that communicating variable values from one function to another required passing around extra parameters to function calls, or a struct of the required state variables. Defining the sub-functions all in the same file removes this requirement. Additionally the code becomes easier to read, since it is no longer required to switch between multiple files to follow the program logic.
Function handles allow functions to be passed around as variables to
other functions, such as plot or simulation routines.
Anonymous functions can be defined as
fhandle = @(arg1, arg2, ...) expr
The example below creates a GUI for a generalised fitting procedure where experimental data is fitted by hand to a fit function with an unspecified number of parameters. Here the advantage of using a program to create the GUI is readily apparent, as the program can determine at run time how many sliders are needed for the fit parameters and space them accordingly. In addition, good use is made of nested functions and anonymous functions.
Nested functions are used for the callbacks for the GUI elements, so the callback functions are able to access the state variables of the global function and do not have to pass around a global state variable handle.
Anonymous functions are used to write GUI element construction functions, such as the function that creates a slider for a particular variable of the fitting function.
function fit_gui(data_x,data_y,fitfun,fitparms,parmlabels,fitlims)
%fit_gui(data_x,data_y,fitfun,fitparms,parmlabels,fitlims)
% General gui interface for fitting data 'by hand'
% data_x, data_y are column vectors with the data
% fitfun is the function handle of the function to fit the data, taking
% arguments fitfun(x_values, fit_parameters)
% fitparms = starting fit parameters, column vector length numparms
% parmlabels = labels of the fit parameters, cell array of strings
% fitlims = nx2 matrix of lower and upper bounds on fit. If passed as a
% single column vector then lower limit is taken as zero, if left off then
% lower limit is taken as zero and upper as twice the initial value of the
% fit parameters
% AUTHOR: Matt McDonnell (www.matt-mcdonnell.com)
% LICENCE: public domain (simple tutorial example)
numparms=length(fitparms); % Number of parameters for the fit
if nargin<3 % If no limits specified take twice the
% starting guess parameters
fitlims=2*fitparms;
end
d=size(fitparms); % If only a vector is passed for the
%limits, take the lower limits as 0
if d(2)==1
fitparms=[zeros(numparms,1) fitparms];
end
%%%%%%%%%%%%%%%%%%%%%% Start of GUI section %%%%%%%%%%%%%%%%%%%%%%%%%%
% Define the figure dimensions and create a figure handle
fxmin=200; fymin=200; fwid=600; fht=400; %Figure dimensions
figdims=[fwid,fht,fwid,fht];
fh=figure('position',[fxmin,fymin,fwid,fht],'closerequestfcn',@close_fig);
% Define a vector of x values for the fit function plotting
xvals=linspace(min(data_x),max(data_x),1000)';
% Gui consists of two plot areas, one for fit+data and the other for
% residuals. Input is via numparms sliders with associated textfields for
% entry of the three fit parameters
% Axes handle for plot windows
ha1=axes('position',[0.05 0.55 0.4 0.4]); % Data + fit plot
ha2=axes('position',[0.05 0.05 0.4 0.4]); % residuals plot
%%%%%%%%%%%%%%%%%%%% Define the uicontrols %%%%%%%%%%%%%%%%%%%%%%%%%%%
% Slider controls for the fit parameters
make_slider=@(parnum, pos) uicontrol('style','slider','tag','slider01',...
'units','normalized','position',pos,...
'callback',@(s,e) slider_callback(s,e,parnum),...
'min',fitlims(parnum,1),'max',fitlims(parnum,2),...
'sliderstep',[(fitlims(parnum,2)-fitlims(parnum,1))/900,...
(fitlims(parnum,2)-fitlims(parnum,1))/90],...
'value',fitparms(parnum));
make_slide_text=@(parnum, pos) uicontrol('style','edit',...
'String',num2str(fitparms(parnum)),'value',fitparms(parnum),...
'units','normalized','tag','edit01','position',pos,...
'callback',@(s,e) edit_callback(s,e,parnum));
make_slide_label=@(label, pos) uicontrol('style','text',...
'units','normalized',...
'position',pos,'string',label);
hs=zeros(numparms,1); % Vector to store uihandles for sliders
ht=zeros(numparms,1); % Vector to store uihandles for edit text boxes
hl=zeros(numparms,1); % Vector to store uihandles for slider/text labels
% Slider and text heights as a fraction of total height
slider_height=0.35/numparms;
slider_gap=0.05/numparms;
text_height=0.35/numparms;
text_gap=0.1/numparms;
% Total height of a slider + text fields + gap to next group
tot_height=slider_height+slider_gap+text_height+text_gap;
% Loop to create the uicontrols and store their handles
for i=1:numparms
hs(i)=make_slider(i,[0.6 (0.1+(i-1)*tot_height) 0.3 (slider_height)]);
ht(i)=make_slide_text(i,[0.8 ...
(0.1+slider_height+slider_gap+(i-1)*tot_height) 0.1 text_height]);
hl(i)=make_slide_label(parmlabels{i},[0.6 ...
(0.1+slider_height+slider_gap+(i-1)*tot_height) 0.1 text_height]);
end
% Call the function that updates the model and replots data
updatemodel;
% Make the figure visible
set(fh,'visible','on');
function updatemodel
% Updates the state of the model when a fit parameter is changed
% and plot the new fits
axes(ha1)
plot(data_x,data_y,'b.',xvals,fitfun(xvals,fitparms),'r-')
title('GUI fit v2')
axes(ha2)
fit_data=fitfun(data_x,fitparms);
plot(data_x,data_y-fit_data,'b.')
chisqr=norm(data_y-fit_data);
title(['Residuals \chi^2=',num2str(chisqr)])
updatecontrols; % Update the uicontrols
end
function updatecontrols
for i=1:numparms
set(hs(i),'value',fitparms(i));
set(ht(i),'string',fitparms(i));
end
end
%%%%%%%%%%%%%%%%%%%%% Start of callback functions %%%%%%%%%%%%%%%%%%%%
% Callbacks are simple: there is the global variable fitparms that
% controls the shape of the curve that tries to fit the data, and each
% uicontrol just updates the corresponding element of the fitparms
% vector (after bounds checking). The task of updating other
% uicontrols (eg text fields associated with sliders) is relegated to
% the updatecontrols function that is called when the model is updated.
function slider_callback(source,eventdata,slidernum)
fitparms(slidernum)=get(source,'value');
updatemodel;
end
function edit_callback(source,eventdata,editnum)
newparm=str2num(get(source,'string'));
if newparm>fitlims(editnum,2)
fitparms(editnum)=fitlims(editnum,2);
set(source,'string',num2str(fitlims(editnum,2)));
elseif newparm<fitlims(editnum,1)
fitparms(editnum)=fitlims(editnum,1);
set(source,'string',num2str(fitlims(editnum,1)));
else
fitparms(editnum)=newparm;
end
updatemodel;
end
function close_fig(source,eventdata)
%save 'fileviewer_saved_parms.mat' dirname;
shh = get(0,'ShowHiddenHandles');
set(0,'ShowHiddenHandles','on');
currFig = get(0,'CurrentFigure');
set(0,'ShowHiddenHandles',shh);
delete(currFig);
end
end
General notes on GUI programs:
- The program consists of a model, views and controllers (MVC).
In the above example the model is the data, the fit function and the parameters of the fit, the view is the plot of data and fit, and the controllers are the sliders for the fit parameters. - Interaction between a controller and the model occurs through callback functions associated with each controller. These callbacks are called when the controller is activated and access/change the state variables of the model, then call the function that updates the view.
- The function that updates the view only depends on the current values of the global state variables, i.e. it shouldn't care which callback was used to call the update view function.