Monday, February 24, 2014

Shortcomings of the GUIDE-inspired approach to programming Matlab GUIs

- Gautham

It is very difficult to make a Matlab GUI that can be reused and comprehended easily (i.e. clean) using the standard approach to building them. This approach is I believe inspired by the type of code produced by GUIDE, Matlab's automatic GUI code generation tool. GUIDE starts from a GUI layout made by the user and creates a code template for it. This approach necessarily puts the GUI front and center, to the detriment of the application design. In most important applications, the GUI should be a detail, not the organizing principle.

To illustrate, here is a code example for one of the simplest GUIs one could imagine: a click counter.

A GUI that counts the number of times the button has been clicked
Imagine that instead of counting clicks, the application is showing you a view of your carefully collected experimental data, and as you press buttons and click here and there, it makes modifications to your data and overwrites them in the disk. In other words, imagine that you care about what the application does.

Before going into the code for how to make such a GUI, first a few helper functions to lay out the GUI components.
Contents of gpndemos.makeFigure.m:
function figH = makeFigure()
    figH = figure('Position', [1 1 200 200]);

Content of gpndemos.makeClickMeButton.m
function uihandle = makeClickMeButton()
    uihandle = uicontrol('Style','pushbutton', ...
        'Position', [50 125 100 50], ...
        'String', 'Click me!');
Contents of gpndemos.makeTextBox.m
function uihandle = makeTextBox()
    uihandle = uicontrol('Style', 'text', ...
        'Position', [50 50 100 50], ...
        'FontSize', 40);

The code to launch the GUI is in gpndemos.makeClassicMatlabGUI.m (note that the second function, buttonCallback, is a local function contained within the file that defines makeClassicMatlabGUI):
function makeClassicMatlabGUI()
    figH = gpndemos.makeFigure();
    Hs.textbox = gpndemos.makeTextBox();
    button = gpndemos.makeClickMeButton();

    Hs.numberOfTimesClicked = 0;
    guidata(figH, Hs)

    set(button, 'Callback', @buttonCallback);

function buttonCallback(hObject, eventdata)
    Hs = guidata(hObject);
    Hs.numberOfTimesClicked = Hs.numberOfTimesClicked + 1;
    set(Hs.textbox, 'String', ...
    guidata(hObject, Hs);

Then you run from the command line:
>> gpndemos.makeClassicMatlabGUI()
and the GUI will pop up and you can click the button and it will update the display.

It was a mystery to me initially how the single file above can produce a working GUI that persists after the function executes. The main function does three things. First it builds the GUI elements. Then it stores some data in the figure itself, and lastly it sets the action that should occur when the user presses the "Click me!" button. The second and third commands are the mysterious ones. A Matlab figure can be associated with a piece of data called the "guidata" - this thing is a regular Matlab structure (struct) and in our lab the convention is to give it the variable name "Hs". The lines:

Hs.textbox = gpndemos.makeTextBox();
Hs.numberOfTimesClicked = 0;
guidata(figH, Hs)

make sure that someone who has access to the figure will be able to figure out how many times it has been clicked on and the address of the textbox. That interested party is the function that executes when you press the button. Speaking of which, here is the line that tells Matlab what to do when the button is pressed:

set(button, 'Callback', @buttonCallback);

What is intriguing about this is that the function "buttonCallback" is a local function within the "makeClassicMatlabGUI" file, and you can never call it yourself from the command line or any other program you write, unlike the main makeClassicMatlabGUI() function. However, Matlab can call it when you click on the button. Matlab's rule is approximately that a GUI Callback can be set to any function that is "in scope" (accessible) at the moment you do the set(…) operation itself. In our program, we set the button's 'Callback' while running the "makeClassicMatlabGUI" file, and the buttonCallback local function is certainly available at that time. By this mechanism, the button can execute a local function within makeClassicMatlabGUI long after makeClassicMatlabGUI() has finished executing.

Looking above at the code for buttonCallback, it does the following: First, it "beams down" the struct of data held by the figure. Then it increments the click count by one, and then it tells the textbox to display the new click count. Lastly, it "beams up" the updated struct to the figure.
There are many ways to explain the shortcomings of this scheme when building larger applications, but they are all consequences of the fact that the application is trapped within the GUI:

The GUIDE-inspired GUI design traps the application within the GUI

The "business logic" of the program is inaccessible from anywhere outside the GUI. In our example the business logic is just storing and incrementing a counter. There is nothing "GUI" about the concept of storing and incrementing a counter, but the standard design traps that idea within the GUI. To add features to this application you must directly modify the code, since there is no easy way to use or manipulate this application from another program. And since there is no easy way to manipulate the application from another program, there is no easy way to write tests for the business logic.
The first step to a better design is making the application directly accessible to the user, or at least to the programmer:

A freed application is accessible to the user directly.

GUIs are not the only interactive programs one can build in Matlab. In fact, Matlab has an ample and easy to use set of tools to create clean, reusable interactive program through its support for object oriented programming. We'll see how to do that for our "Click me!" example in a next blog post.

No comments:

Post a Comment