GUI

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: