Model View Controller (MVC) for MATLAB GUIs

28. Aug. 2016

The other day I attended a MATLAB training course to make pretty GUIs. It was very interesting, yet the way MathWorks recommended to share data between multiple windows was slightly off-putting for me. In short, they showed all attendees two ways to exchange data. Either have a main windows that holds all data, or the data is stored alongside all windows' handles (handles are used in MATLAB to access properties of displayed objects). The former works well if there is always an main window open, and I don't see any inherent problem here. Nonetheless, the latter approach requires duplicate data to be stored, which seems very wasteful, unstable, and difficult to expand. My solution: the Model View Controller (MVC - link). Next I will show you the differences and how the MVC can be programmed.

GUI

Let's start by making a Graphical User Interface (GUI). The quickest way to do this is with MATLAB's guide tool. Simply enter guide into the command window and replicate the following interface:

This window consist of a "Static Text" containing the string "Change Value", a "Slider", and another "Static Text" containing the string "n/a". The slider and bottom static text are tagged as (so they become identifiable) as follows:

That's pretty much it for the time being.

Data Handling

Now let me quickly address the data handling. Here, the idea is that the GUI can be instantiated several times and does not hold any data at all. In fact, the GUI simply displays what it told to display, and when the user interacts with the GUI, it notifies something (i.e. its controller) that a changed occurred. It is then the controller's job to evaluate the user's input and update the GUI(s) correct. I tried to capture this interaction in the following sketch:

Here, the controller forwards data to the guis 1, 2 & 3, which is then displayed. If the user interacts with any of the guis (asterisk), then they notify the controller of that change it updates the guis correspondingly. Additionally, and to complete the MVC, I have added a data container, which may be a database, website or whatever you want it to be. The idea is, that only the controller can interact with the data to avoid making the guis wait, freeze and ruining the user experience.

Additionally, if the user decides to close a window whilst data is being processed, the controller will notice this and not crash the app by trying to assign a nonexistent variable. Clever, right?

Controller Code

Now you know the in-and-outs of the guide it is time to write up the controller class (yes, I love classes in MATLAB).

The class itself will contain the following functionality:

So let's define the bare-bone of the class:

classdef Controller
    properties
        % Store the handle to all windows
        windows
    end

    methods

        function self = Controller(num_windows)
            % Contains the constructor code and opens num_windows windows
        end

        function close_all(self)
            % Closes all windows that are stored in self.windows
        end

        function update(self)
            % Obtains the data from all windows and updates them
        end

    end
end

Now let's write the constructor method:

function self = Controller(num_windows)

    if num_windows > 5 || num_windows < 0
        warndlg('The maximum number of windows to open is 5');
        return
    end

    % Initialise the
    self.windows = nan(num_windows, 1);

    % First create all windows
    for i = 1:num_windows
        % Open a new window and assign it
        self.windows(i) = slider_window();
    end

    % Then assign data (otherwise some may have false copies or so)
    for i = 1:num_windows
        % Get the handles for sliders and value labels
        all_data = guidata(self.windows(i));
        % Update the figure title to something meaningful
        all_data.figure.Name = sprintf('Figure [val %d]', i);
        % Add a delegate function to notify of an update
        all_data.delegate = @self.update;
        guidata(self.windows(i), all_data);
    end

end

The closing function is quite straight forward, too. But since some windows might have been closed we need to include some try-catch statements to prevent the code attempting to access nonexistent figure properties.

function close_all(self)
    % Closes all windows that are held by the controller
    for i = 1:length(self.windows)
        % Try to close the figure if it exists
        try
            close(self.windows(i));
        catch
            disp(['Cannot close figure ', num2str(i)]);
        end
    end
end

And finally, the heart of the class; the update method. As already stated, this method will obtain the slider values from all figures and list them in each figure's "value" text field.

function update(self)
    % The update data on all windows

    % Get data from each window and populate the data string
    data_string = [];
    for i = 1:length(self.windows)
        % Try and retrieve the data from the slider in the figure
        try
            all_data = guidata(self.windows(i));
            data_string = [data_string, ...
                'val ', num2str(i), ': ', num2str(all_data.slider.Value), char(10)];
        catch
            disp(['Figure ', num2str(i), ' does no longer exist']);
            data_string = [data_string, ...
                'val ', num2str(i), ': n/a', char(10)];
        end
    end

    % Set data for each window
    for i = 1:length(self.windows)
        % Try and set the value string in the figure
        try
            all_data = guidata(self.windows(i));
            all_data.value.String = data_string;
        catch
            disp(['Cannot update figure ', num2str(i)]);
        end
    end
end

Final Step

Now to bring it all together, let's write a simple script that initializes a Controller object.

clear
close all
clc

% Create however many windows you want
c = Controller(5);
% Update the windows, i.e. set them to defaults
c.update();

% Wait for an input in the terminal to trigger closing
pause

% Close all windows
c.close_all();

After wiggling all sliders on all figures, this is what you should see:

And when changing any of the figures' sliders, the values on all the others update, too. Cool, right? But what if the user decided to close one of the figures? The result would look like this:

When changing any of the sliders, the value from (here) figure 4 could no longer be obtained, hence the controller substitutes n/a and outputs a friendly reminder to the terminal:

Figure 4 does no longer exist
Cannot update figure 4

Also, when continuing the execution of main.m by pressing any key to continue, the still remaining figures are all closed and another friendly warning is prompted:

Cannot close figure 4

So, how cool and handy are MVCs? Very! At least that's what I think... If you want to have a copy of this code, you can download a zip from here. Enjoy!