What's the "right" way to organize GUI code?

The structure for my code is not the prettiest. Right now I have the engine segregated from the front-end (good!) but the GUI code is pretty spaghetti-like. Here's a skeleton of an "activity", to borrow Android-speak:

function myGui fig = figure(. ); % h is a struct that contains handles to all the ui objects to be instantiated. My convention is to have the first field be the uicontrol type I'm instantiating. See draw_gui nested function h = struct([]); draw_gui; set_callbacks; % Basically a bunch of set(h.(. ), 'Callback', @(src, event) callback) calls would occur here %% DRAW FUNCTIONS function draw_gui h.Panel.Panel1 = uipanel(. 'Parent', fig, . . ); h.Panel.Panel2 = uipanel(. 'Parent', fig, . . ); draw_panel1; draw_panel2; function draw_panel1 h.Edit.Panel1.thing1 = uicontrol('Parent', h.Panel.Panel1, . ); end function draw_panel2 h.Edit.Panel2.thing1 = uicontrol('Parent', h.Panel.Panel2, . ); end end %% CALLBACK FUNCTIONS % Setting/getting application data is done by set/getappdata(fig, 'Foo'). end 

I have previously-written code where nothing is nested, so I ended up passing h back and forth everywhere (since stuff needed to be redrawn, updated, etc) and setappdata(fig) to store actual data. In any case, I've been keeping one "activity" in a single file, and I'm sure this is going to be a maintenance nightmare in the future. Callbacks are interacting with both application data and graphical handle objects, which I suppose is necessary, but that's preventing a complete segregation of the two "halves" of the code base.

So I'm looking for some organizational/GUI design help here. Namely:

I'm not a software engineer by trade, I just know enough to be dangerous, so I'm sure these are fairly basic questions for seasoned GUI developers (in any language). I almost feel like the lack of a GUI design standard in MATLAB (does one exist?) is seriously interfering with my ability to complete this project. This is a MATLAB project that is much more massive than any I've ever undertaken, and I've never had to give much thought to complicated UIs with multiple figure windows, etc., before.

30.5k 6 6 gold badges 76 76 silver badges 134 134 bronze badges asked Nov 19, 2013 at 6:23 5,783 8 8 gold badges 52 52 silver badges 81 81 bronze badges Commented Nov 20, 2013 at 0:50

@Amro as this is a very useful question, as well as your answer, you may include this link suggestions in your answer rather than here in the comments. It's a nice start for further reading and a little lost in the comments ;)

Commented Nov 20, 2013 at 9:01

5 Answers 5

As @SamRoberts explained, the Model–view–controller (MVC) pattern is well-suited as an architecture to design GUIs. I agree that there are not a lot of MATLAB examples out there to show such design.

Below is a complete yet simple example I wrote to demonstrate an MVC-based GUI in MATLAB.

Note that the view and controller are written as regular functions, but you could write classes if you prefer fully object-oriented code.

It is a little extra work compared to the usual way of designing GUIs, but one of the advantages of such architecture is the separation of the data from presentation layer. This makes for a cleaner and more readable code especially when working with complex GUIs, where code maintenance becomes more difficult.

This design is very flexible as it allows you to build multiple views of the same data. Even more you can have multiple simultaneous views, just instantiate more views instances in the controller and see how changes in one view are propagated to the other! This is especially interesting if your model can be visually presented in different ways.

In addition, if you prefer you can use the GUIDE editor to build interfaces instead of programmatically adding controls. In such a design we would only use GUIDE to build the GUI components using drag-and-drop, but we would not write any callback functions. So we'll only be interested in the .fig file produced, and just ignore the accompanying .m file. We would setup the callbacks in the view function/class. This is basically what I did in the View_FrequencyDomain view component, which loads the existing FIG-file built using GUIDE.

GUIDE generated FIG-file

Model.m

classdef Model < handle %MODEL represents a signal composed of two components + white noise % with sampling frequency FS defined over t=[0,1] as: % y(t) = a * sin(2pi * f*t) + sin(2pi * 2*f*t) + white_noise % observable properties, listeners are notified on change properties (SetObservable = true) f % frequency components in Hz a % amplitude end % read-only properties properties (SetAccess = private) fs % sampling frequency (Hz) t % time vector (seconds) noise % noise component end % computable dependent property properties (Dependent = true, SetAccess = private) data % signal values end methods function obj = Model(fs, f, a) % constructor if nargin < 3, a = 1.2; end if nargin < 2, f = 5; end if nargin < 1, fs = 100; end obj.fs = fs; obj.f = f; obj.a = a; % 1 time unit with 'fs' samples obj.t = 0 : 1/obj.fs : 1-(1/obj.fs); obj.noise = 0.2 * obj.a * rand(size(obj.t)); end function y = get.data(obj) % signal data y = obj.a * sin(2*pi * obj.f*obj.t) + . sin(2*pi * 2*obj.f*obj.t) + obj.noise; end end % business logic methods function [mx,freq] = computePowerSpectrum(obj) num = numel(obj.t); nfft = 2^(nextpow2(num)); % frequencies vector (symmetric one-sided) numUniquePts = ceil((nfft+1)/2); freq = (0:numUniquePts-1)*obj.fs/nfft; % compute FFT fftx = fft(obj.data, nfft); % calculate magnitude mx = abs(fftx(1:numUniquePts)).^2 / num; if rem(nfft, 2) mx(2:end) = mx(2:end)*2; else mx(2:end -1) = mx(2:end -1)*2; end end end end 

View_TimeDomain.m

function handles = View_TimeDomain(m) %VIEW a GUI representation of the signal model % build the GUI handles = initGUI(); onChangedF(handles, m); % populate with initial values % observe on model changes and update view accordingly % (tie listener to model object lifecycle) addlistener(m, 'f', 'PostSet', . @(o,e) onChangedF(handles,e.AffectedObject)); end function handles = initGUI() % initialize GUI controls hFig = figure('Menubar','none'); hAx = axes('Parent',hFig, 'XLim',[0 1], 'YLim',[-2.5 2.5]); hSlid = uicontrol('Parent',hFig, 'Style','slider', . 'Min',1, 'Max',10, 'Value',5, 'Position',[20 20 200 20]); hLine = line('XData',NaN, 'YData',NaN, 'Parent',hAx, . 'Color','r', 'LineWidth',2); % define a color property specific to the view hMenu = uicontextmenu; hMenuItem = zeros(3,1); hMenuItem(1) = uimenu(hMenu, 'Label','r', 'Checked','on'); hMenuItem(2) = uimenu(hMenu, 'Label','g'); hMenuItem(3) = uimenu(hMenu, 'Label','b'); set(hLine, 'uicontextmenu',hMenu); % customize xlabel(hAx, 'Time (sec)') ylabel(hAx, 'Amplitude') title(hAx, 'Signal in time-domain') % return a structure of GUI handles handles = struct('fig',hFig, 'ax',hAx, 'line',hLine, . 'slider',hSlid, 'menu',hMenuItem); end function onChangedF(handles,model) % respond to model changes by updating view if ~ishghandle(handles.fig), return, end set(handles.line, 'XData',model.t, 'YData',model.data) set(handles.slider, 'Value',model.f); end 

View_FrequencyDomain.m

function handles = View_FrequencyDomain(m) handles = initGUI(); onChangedF(handles, m); hl = event.proplistener(m, findprop(m,'f'), 'PostSet', . @(o,e) onChangedF(handles,e.AffectedObject)); setappdata(handles.fig, 'proplistener',hl); end function handles = initGUI() % load FIG file (its really a MAT-file) hFig = hgload('ViewGUIDE.fig'); %S = load('ViewGUIDE.fig', '-mat'); % extract handles to GUI components hAx = findobj(hFig, 'tag','axes1'); hSlid = findobj(hFig, 'tag','slider1'); hTxt = findobj(hFig, 'tag','fLabel'); hMenu = findobj(hFig, 'tag','cmenu1'); hMenuItem = findobj(hFig, 'type','uimenu'); % initialize line and hook up context menu hLine = line('XData',NaN, 'YData',NaN, 'Parent',hAx, . 'Color','r', 'LineWidth',2); set(hLine, 'uicontextmenu',hMenu); % customize xlabel(hAx, 'Frequency (Hz)') ylabel(hAx, 'Power') title(hAx, 'Power spectrum in frequency-domain') % return a structure of GUI handles handles = struct('fig',hFig, 'ax',hAx, 'line',hLine, . 'slider',hSlid, 'menu',hMenuItem, 'txt',hTxt); end function onChangedF(handles,model) [mx,freq] = model.computePowerSpectrum(); set(handles.line, 'XData',freq, 'YData',mx) set(handles.slider, 'Value',model.f) set(handles.txt, 'String',sprintf('%.1f Hz',model.f)) end 

Controller.m

function [m,v1,v2] = Controller %CONTROLLER main program % controller knows about model and view m = Model(100); % model is independent v1 = View_TimeDomain(m); % view has a reference of model % we can have multiple simultaneous views of the same data v2 = View_FrequencyDomain(m); % hook up and respond to views events set(v1.slider, 'Callback',) set(v2.slider, 'Callback',) set(v1.menu, 'Callback',) set(v2.menu, 'Callback',) % simulate some change pause(3) m.f = 10; end function onSlide(o,~,model) % update model (which in turn trigger event that updates view) model.f = get(o,'Value'); end function onChangeColor(o,~,handles) % update view clr = get(o,'Label'); set(handles.line, 'Color',clr) set(handles.menu, 'Checked','off') set(o, 'Checked','on') end 

MVC GUI1MVC GUI2

In the controller above, I instantiate two separate but synchronized views, both representing and responding to changes in the same underlying model. One view shows the time-domain of the signal, and another shows the frequency-domain representation using FFT.