GUI For Managing Persistent Data

About the Example

This example shows how to manage MAT-file data using local functions in a GUIDE GUI. It steps you through the code that reads and displays data from a MAT-file. In addition, the GUI provides a File menu for saving a MAT-file (or loading a new one), and a Contact menu for adding entries to the MAT-file.

To get and view the example code:

  1. Copy the example FIG-file and code file to your current (writeable) folder and open the FIG-file in GUIDE:

    copyfile(fullfile(docroot, 'techdoc','creating_guis',...
    		'examples','addr*.*')),...
     		fileattrib('addr*.*', '+w');
    guide address_book.fig;
    
  2. In the GUIDE Layout Editor, click the Editor button .

    The address_book.m code file opens in the MATLAB® Editor.

Calling Syntax

The address_book_OpeningFcn code in address_book.m interprets the input arguments:

  • If you call the GUI, address_book, with no arguments, the GUI uses the default address book MAT-file.

  • If you invoke the GUI with a pair of arguments (for example, address_book('book','my_list.mat')) , the first argument, 'book', is a key word that the code looks for in the opening function. If the key word matches, the code uses the second argument as the MAT-file for the address book.

address_book_OpeningFcn

function address_book_OpeningFcn(hObject, eventdata, ...
	handles, varargin)

% Choose default command line output for address_book
handles.output = hObject;

% Make figure non-dockable                   
% set(hObject,'DockControls','off')          
set(hObject,'WindowStyle','normal')          
set(hObject,'HandleVisibility','callback')   

% Update handles structure
guidata(hObject, handles);
if nargin < 4 
    % Load the default address book
    Check_And_Load([],handles);
    % If first element in varargin is 'book' and the second element is a
    % MATLAB file, then load that file
elseif (length(varargin) == 2 && ...
		strcmpi(varargin{1},'book') && ...
		(2 == exist(varargin{2},'file')))
	Check_And_Load(varargin{2},handles);
else
    errordlg('File Not Found','File Load Error')
    set(handles.Contact_Name,'String','')
    set(handles.Contact_Phone,'String','')
end	  

MAT-file Validation

To be a valid address book, the MAT-file must contain a structure called Addresses that has two fields called Name and Phone. The Check_And_Load function in address_book.m validates and loads the data as follows:

  • Loads the specified file or the default if no file is specified.

  • Determines if the MAT-file is a valid address book.

  • Displays the data if it is valid. If the data is not valid, displays an error dialog box (errordlg).

  • Returns 1 for valid MAT-files and 0 if invalid (used by the Open menu callback).

  • Saves the following items in the handles structure:

    • The name of the MAT-file

    • The Addresses structure

    • An index pointer indicating which name and phone number are currently displayed in the GUI

Check_And_Load

function pass = Check_And_Load(file,handles)

% Initialize the variable "pass" to determine if this is 
% a valid file.
pass = 0;

% If called without any file then set file to the default 
% file name. Otherwise if the file exists then load it.
if isempty(file)
    file = 'addrbook.mat';
    handles.LastFile = file;
    guidata(handles.Address_Book,handles)
end

if exist(file,'file') == 2
    data = load(file);
end

% Validate the MAT-file
% The file is valid if the variable is called "Addresses" 
% and it has fields called "Name" and "Phone"
flds = fieldnames(data);
if (length(flds) == 1) && (strcmp(flds{1},'Addresses'))
    fields = fieldnames(data.Addresses);
    if (length(fields) == 2) && (strcmp(fields{1},'Name'))...
 && (strcmp(fields{2},'Phone'))
        pass = 1;
    end
end

% If the file is valid, display it
if pass
    % Add Addresses to the handles structure
    handles.Addresses = data.Addresses;
    % Display the first entry
    set(handles.Contact_Name,'String',data.Addresses(1).Name)
    set(handles.Contact_Phone,'String',data.Addresses(1).Phone)
    % Set the index pointer to 1
    handles.Index = 1;
    % Save the modified handles structure
    guidata(handles.Address_Book,handles)
else
    errordlg('Not a valid Address Book','Address Book Error')
end

GUI Behavior

Open and Load MAT-File

The address book GUI contains a File > Open menu option for loading address book MAT-files.

When you select this option, Open_Callback in address_book.m opens a dialog box that enables you to browse for files.

The dialog box returns the file name and the path to the file, which are passed to fullfile to ensure the path is properly constructed for any platform. The Check_And_Load function validates and loads the new address book.

For information on creating the menu, see Create Menus for GUIDE GUIs.

Open_Callback.  

function Open_Callback(hObject, eventdata, handles, varargin)
[filename, pathname] = uigetfile( ...
    {'*.mat', 'All MAT-Files (*.mat)'; ...
        '*.*','All Files (*.*)'}, ...
    'Select Address Book');
% If "Cancel" is selected then return
if isequal([filename,pathname],[0,0])
    return
    % Otherwise construct the full file name and _and load the file.
else
    File = fullfile(pathname,filename);
    % if the MAT-file is not valid, do not save the name
    if Check_And_Load(File,handles)
        handles.LastFIle = File;
        guidata(hObject,handles)
    end
end

Retrieve and Store Data

The GUI's Contact Name text box displays the name of the address book entry. If you type in a new name and press enter, the Contact_Name_Callback in address_book.m does the following:

  • If the name exists in the current address book, the corresponding phone number displays.

  • If the name does not exist, a question dialog box asks you if you want to create a new entry, or cancel and return to the name previously displayed.

  • If you create a new entry, you must save the MAT-file using the File > Save menu.

The Contact_Name_Callback callback uses the handles structure to access the contents of the address book and to maintain an index pointer (handles.Index) that enables the callback to determine what name is displayed before you enter a new one. The index pointer indicates what name is currently displayed. The Check_And_Load function adds the address book and index pointer fields when you run the GUI.

If you add a new entry, the callback adds the new name to the address book and updates the index pointer to reflect the new value displayed. The updated address book and index pointer are again saved (using guidata) in the handles structure.

Contact_Name_Callback.  

function Contact_Name_Callback(hObject, eventdata, handles, varargin)
% Get the strings in the Contact Name and Phone text box
Current_Name = get(handles.Contact_Name,'string');
Current_Phone = get(handles.Contact_Phone,'string');

% If empty then return
if isempty(Current_Name)
    return
end

% Get the current list of addresses from the handles structure
Addresses = handles.Addresses;

% Go through the list of contacts
% Determine if the current name matches an existing name
for i = 1:length(Addresses)
    if strcmp(Addresses(i).Name,Current_Name)
        set(handles.Contact_Name,'string',Addresses(i).Name)
        set(handles.Contact_Phone,'string',Addresses(i).Phone)
        handles.Index = i;
        guidata(hObject,handles)
        return
    end
end

% If it's a new name, ask to create a new entry
Answer=questdlg('Do you want to create a new entry?', ...
    'Create New Entry', ...
    'Yes','Cancel','Yes');			
switch Answer
case 'Yes'
    Addresses(end+1).Name = Current_Name; % Grow array by 1
    Addresses(end).Phone = Current_Phone; 
    index = length(Addresses);
    handles.Addresses = Addresses;
    handles.Index = index;
    guidata(hObject,handles)
    return			
case 'Cancel'
    % Revert back to the original number
    set(handles.Contact_Name,'string',Addresses(handles.Index).Name)
    set(handles.Contact_Phone,'String',Addresses(handles.Index).Phone)
    return
end			

Data Update Confirmation

The Contact Phone # text box displays the phone number of the entry listed in the Contact Name text box. If you type in a new number and click one of the push buttons, Contact_Phone_Callback in address_book.m opens a question dialog box that asks you if you want to change the existing number or cancel your change.

This callback uses the index pointer (handles.Index) to update the new number in the address book and to revert to the previously displayed number if you click Cancel in the question dialog box. Both the current address book and the index pointer are saved in the handles structure so that this data is available to other callbacks.

Contact_Phone_Callback.  

function Contact_Phone_Callback(hObject, eventdata, handles, varargin)
Current_Phone = get(handles.Contact_Phone,'string');

% If either one is empty then return
if isempty(Current_Phone)
    return
end

% Get the current list of addresses from the handles structure
Addresses = handles.Addresses;
Answer=questdlg('Do you want to change the phone number?', ...
    'Change Phone Number', ...
    'Yes','Cancel','Yes');			
switch Answer
case 'Yes'
    % If no name match was found create a new contact
    Addresses(handles.Index).Phone = Current_Phone; 
    handles.Addresses = Addresses;
    guidata(hObject,handles)
    return			
case 'Cancel'
    % Revert back to the original number
    set(handles.Contact_Phone,'String',Addresses(handles.Index).Phone)
    return
end			

Paging Through Entries — Prev/Next

By clicking the Prev and Next buttons you can page back and forth through the entries in the address book. The Callback property of both push buttons are set to call Prev_Next_Callback in address_book.m.

The Prev_Next_Callback defines an additional argument, str, that indicates which button, Prev or Next, is clicked. The Prev button Callback property includes 'Prev' as the last argument. The Next button Callback string includes 'Next' as the last argument. The value of str is used in case statements to implement each button's function.

The Prev_Next_Callback gets the current index pointer and the addresses from the handles structure and, depending on which button you click, the index pointer decrements or increments and the corresponding address and phone number display. The final step stores the new value for the index pointer in the handles structure and saves the updated structure using guidata.

Prev_Next_Callback.  

function Prev_Next_Callback(hObject, eventdata, handles, str)
% Get the index pointer and the addresses
index = handles.Index;
Addresses = handles.Addresses;

% Depending on whether Prev or Next was clicked,
%  change the display
switch str
case 'Prev'
    % Decrease the index by one
    i = index - 1;	
    % If the index is less than one then set 
		% it equal to the index of the 
    % last element in the Addresses array
    if i < 1
        i = length(Addresses);
    end
case 'Next'
    % Increase the index by one
    i = index + 1;
    
    % If the index is greater than the size of the array then point
    % to the first item in the Addresses array
    if i > length(Addresses)
        i = 1;
    end	
end

% Get the appropriate data for the index in selected

Current_Name = Addresses(i).Name;
Current_Phone = Addresses(i).Phone;
set(handles.Contact_Name,'string',Current_Name)
set(handles.Contact_Phone,'string',Current_Phone)

% Update the index pointer to reflect the new index

handles.Index = i;
guidata(hObject,handles)

Save File

When you make changes to an address book, the File submenus Save and Save As enable you to save the current MAT-file, or save it as a new MAT-file. These menus were created with the Menu Editor (Tools > Menu Editor, and use the same callback, Save_Callback.

Save_Callback in address_book.m uses the menu Tag property (also specified in the Menu Editor) to identify whether Save or Save As is the callback object (that is, the object whose handle is passed in as the first argument to the Save_Callback).

The handles structure contains the Addresses structure, which the GUI must save (handles.Addresses) as well as the name of the currently loaded MAT-file (handles.LastFile). When you change a name or number in the GUI, the Contact_Name_Callback or the Contact_Phone_Callback updates handles.Addresses.

If you select Save, the save function is called to save the current MAT-file with the new names and phone numbers.

If you select Save As, a dialog box displays which enables you to select the name of an existing MAT-file or specify a new file. The dialog box returns the selected file name and path. The final steps include:

  • Using fullfile to create a platform-independent path name.

  • Calling save to save the new data in the MAT-file.

  • Updating the handles structure to contain the new MAT-file name.

  • Calling guidata to save the handles structure.

Save_Callback.  

function Save_Callback(hObject, eventdata, handles, varargin)
% Get the Tag of the menu selected
Tag = get(hObject,'Tag');

% Get the address array
Addresses = handles.Addresses;

% Based on the item selected, take the appropriate action
switch Tag
case 'Save'
    % Save to the default addrbook file
    File = handles.LastFile;
    save(File,'Addresses')
case 'Save_As'
    % Allow the user to select the file name to save to
    [filename, pathname] = uiputfile( ...
        {'*.mat';'*.*'}, ...
        'Save as');	
    % If 'Cancel' was selected then return
    if isequal([filename,pathname],[0,0])
        return
    else
        % Construct the full path and save
        File = fullfile(pathname,filename);
        save(File,'Addresses')
        handles.LastFile = File;
        guidata(hObject,handles)
    end
end

Clear GUI Fields

The Create New menu clears the Contact Name and Contact Phone # text fields to facilitate adding a new name and number. The New_Callback callback sets the text String properties to empty strings:

function New_Callback(hObject, eventdata, handles, varargin)
set(handles.Contact_Name,'String','')
set(handles.Contact_Phone,'String','')

Overall GUI Characteristics

The GUI is nonblocking and nonmodal because it is designed to be displayed while you perform other MATLAB tasks. GUI options, which you can view by selecting Tools > GUI Options in GUIDE specify the following:

  • Resize behavior: Other (Use SizeChangedFcn)

    This sets the figure's SizeChangedFcn property to:

    @(hObject,eventdata)address_book('Address_Book_SizeChangedFcn',...
                                      hObject,eventdata,guidata(hObject))
  • Command-line accessibility: Off

  • Generate FIG file and MATLAB file (selected)

  • Generate callback function prototypes (selected)

  • GUI allows only one instance to run (singleton) (selected)

GUI Resize Behavior

When you resize the GUI, MATLAB calls the SizeChangedFcn callback. In this case, the name of the SizeChangedFcn callback is Address_Book_SizeChangedFcn.

The SizeChangedFcn callback enables you to make the GUI wider, so it can accommodate long names and numbers. However, you cannot make the GUI narrower than its original width and you cannot change the height. These restrictions simplify the callback, which must maintain the proper proportions between the figure size and the components in the GUI.

When the you resize the figure and release the mouse, the SizeChangedFcn callback executes. Unless you have maximized the figure, the SizeChangedFcn callback enforces the height of the GUI and resets the width of the Contact Name field. The following sections describe how this calculation works.

Width Changes.  If the new width is greater than the original width, set the figure to the new width.

The size of the Contact Name text box changes in proportion to the new figure width. This is accomplished by:

  • Obtaining the figure width as a ratio of its original width.

  • Expanding or contracting the width of the Contact Name field proportionally.

If the new width is less than the original width, use the original width. The code relies on the fact that the original width of the Contact Name field is 72 character units.

Height Changes.  The height and width of the GUI is specified in pixel units. Using units of pixels enables maximizing and minimizing the figure to work properly. The code assumes that its dimensions are 470-by-250 pixels. If you attempt to change the height, the code restores the original height. However, because the resize function is triggered when you release the mouse button after changing the size, the resize function cannot always determine the original position of the GUI on screen. Therefore, the resize function applies a compensation to the vertical position (second element in the figure Position vector) by adding the vertical position to the height when you release the mouse and subtracting the original height.

When you resize the GUI from the bottom, the GUI stays in the same position. When you resize from the top, the GUI moves to the location where you release the mouse button.

SizeChangedFcn.  

% uicontrol units are in 'characters'
Figure_Size = get(hObject,'Position');
% This is the figure's original position in pixel units
Original_Size = [350 700 470 250];
% If the figure seems to be maximized, do not resize at all
pix_pos = get(hObject,'Position');
scr_size = get(groot,'ScreenSize');
if .99*scr_size(3) < pix_pos(3)    % Apparently maximized
    % When docked, get out
    return
end
% If resized figure is smaller than original figure, then compensate.
% However, do not change figure size if it is docked; just adjust
% uicontrols
if ~strcmp(get(hObject,'WindowStyle'),'docked')
    if Figure_Size(3) < Original_Size(3)
        % If the width is too small then reset to origianl width
        set(hObject,'Position',[Figure_Size(1) ...
                                Figure_Size(2) ...
                                Original_Size(3) ...
                                Original_Size(4)])
        Figure_Size = get(hObject,'Position');
    end
    
    if abs(Figure_Size(4) - Original_Size(4)) > 10 % pixels
        % Do not allow the height to change 
        set(hObject,'Position',[Figure_Size(1) ...
            Figure_Size(2)+Figure_Size(4)-Original_Size(4) ...
            Figure_Size(3) ...
            Original_Size(4)])
    end
    movegui(hObject, 'onscreen')
end

% Get Contact_Name field Position for readjusting its width
C_N_pos = get(handles.Contact_Name,'Position');
ratio = Figure_Size(3) / Original_Size(3);
% Reset it so that its width remains proportional to figure width
% The original width of the Contact_Name box is 72 (characters)
set(handles.Contact_Name,'Position',[C_N_pos(1) ...
                                     C_N_pos(2) ...
                                     ratio * 72 ...
                                     C_N_pos(4)])

Keeping Resized Figure On Screen.  The SizeChangedFcn callback calls movegui to ensure that the resized GUI is on screen regardless of where you release the mouse.

The first time it runs, the GUI displays at the size and location specified by the figure Position property. This property was set with the Property Inspector when the GUI was created and it can be changed it in GUIDE at any time.

Was this topic helpful?