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 |
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]); end
Content of gpndemos.makeClickMeButton.m
function uihandle = makeClickMeButton() uihandle = uicontrol('Style','pushbutton', ... 'Position', [50 125 100 50], ... 'String', 'Click me!'); end
function uihandle = makeTextBox() uihandle = uicontrol('Style', 'text', ... 'Position', [50 50 100 50], ... 'FontSize', 40); end
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); end function buttonCallback(hObject, eventdata) Hs = guidata(hObject); Hs.numberOfTimesClicked = Hs.numberOfTimesClicked + 1; set(Hs.textbox, 'String', ... num2str(Hs.numberOfTimesClicked)) guidata(hObject, Hs); end
Then you run from the command line:
>> gpndemos.makeClassicMatlabGUI()
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 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. |
No comments:
Post a Comment