Model View Controller (MVC) is a compound pattern whose goal is to separate responsibilities into modular pieces that can be interchanged relatively easily. If you didn't need a user interface, all you'd have to worry about is the model. Why should you have to burden the model with user interface code? Obviously you shouldn't, thus the model view separation. Introduction of a controller loosens the coupling between the model and the view and, if done well, allows the behavior of the system to be modified by replacing the controller with a different one. After the jump, I'll show you how to apply the pattern to a traditional Matlab GUI.
![]() |
| Model View Controller |
I'm following the MVC example laid out in the excellent book Head First Design Patterns. My starting point is the quick start GUIDE 'GUI with Uicontrols'. If you choose this GUIDE template and view the m file, you can see how it started and compare it to the MVC version I am presenting. Of course there are a few idiosyncrasies that the Matlab scripting language imposes. The main one is that the user interface is not a class; rather, it is a handle graphics object. The good news is that it's not hard to work around.
We start with a top-level main function that instantiates the model class as well as the controller class and passes the model into the controller.
function main() mymodel = model(); mycontroller = controller(mymodel);
classdef model < handle
properties (SetObservable)
density
volume
units
mass
end
methods
function obj = model()
obj.reset();
end
function reset(obj)
obj.density = 0;
obj.volume = 0;
obj.units = 'english';
obj.mass = 0;
end
function setDensity(obj,density)
obj.density = density;
end
function setVolume(obj,volume)
obj.volume = volume;
end
function setUnits(obj,units)
obj.units = units;
end
function calculate(obj)
obj.mass = obj.density * obj.volume;
end
end
end
How do we deal with this handle graphics user interface? Simple, we wrap it in a class. The view class is passed a reference to the controller object when it is created. It also instantiates the GUIDE graphics object, which is called measures here. Notice that we also pass the controller into the GUI. Then we register listeners to the model properties and specify a method to handle the triggered events. This method has access to the visual elements of the GUI and modifies them appropriately in response to events. The addlistener call has been modified here with an anonymous function definition that adds the self object, obj. The event handler needs obj to get the GUI handles structure.
classdef view < handle
properties
gui
model
controller
end
methods
function obj = view(controller)
obj.controller = controller;
obj.model = controller.model;
obj.gui = measures('controller',obj.controller);
addlistener(obj.model,'density','PostSet', ...
@(src,evnt)view.handlePropEvents(obj,src,evnt));
addlistener(obj.model,'volume','PostSet', ...
@(src,evnt)view.handlePropEvents(obj,src,evnt));
addlistener(obj.model,'units','PostSet', ...
@(src,evnt)view.handlePropEvents(obj,src,evnt));
addlistener(obj.model,'mass','PostSet', ...
@(src,evnt)view.handlePropEvents(obj,src,evnt));
end
end
methods (Static)
function handlePropEvents(obj,src,evnt)
evntobj = evnt.AffectedObject;
handles = guidata(obj.gui);
switch src.Name
case 'density'
set(handles.density, 'String', evntobj.density);
case 'volume'
set(handles.volume, 'String', evntobj.volume);
case 'units'
switch evntobj.units
case 'english'
set(handles.text4, 'String', 'lb/cu.in');
set(handles.text5, 'String', 'cu.in');
set(handles.text6, 'String', 'lb');
case 'si'
set(handles.text4, 'String', 'kg/cu.m');
set(handles.text5, 'String', 'cu.m');
set(handles.text6, 'String', 'kg');
otherwise
error('unknown units')
end
case 'mass'
set(handles.mass,'String',evntobj.mass);
end
end
end
end
The controller knows about the model and the view; but it's only function in this demo is call model methods as requested by the GUI. In more realistic applications, it may interact with the view and the model in more complicated ways.
classdef controller < handle
properties
model
view
end
methods
function obj = controller(model)
obj.model = model;
obj.view = view(obj);
end
function setDensity(obj,density)
obj.model.setDensity(density)
end
function setVolume(obj,volume)
obj.model.setVolume(volume)
end
function setUnits(obj,units)
obj.model.setUnits(units)
end
function calculate(obj)
obj.model.calculate()
end
function reset(obj)
obj.model.reset()
end
end
end
Finally, you can compare the m file created by the template GUIDE to the one in the listing below. You would see that the model was originally held in the handles structure of the GUI. The handles structure is one of the ways to store user data within a GUI. Now there is a separate model class that stands on its own. You may also notice that function listed below doesn't have the responsibility of modifying the graphics elements directly. Rather, it bumps that responsibility to the wrapping view class, which can respond to events from the model. Nor does it modify the model, but passes that responsibility to the controller.
function varargout = measures(varargin)
% MEASURES M-file for measures.fig
% MEASURES, by itself, creates a new MEASURES or raises the existing
% singleton*.
%
% H = MEASURES returns the handle to a new MEASURES or the handle to
% the existing singleton*.
%
% MEASURES('CALLBACK',hObject,eventData,handles,...) calls the local
% function named CALLBACK in MEASURES.M with the given input arguments.
%
% MEASURES('Property','Value',...) creates a new MEASURES or raises
% the existing singleton*. Starting from the left, property value pairs are
% applied to the GUI before measures_OpeningFcn gets called. An
% unrecognized property name or invalid value makes property application
% stop. All inputs are passed to measures_OpeningFcn via varargin.
%
% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
% instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES
% Edit the above text to modify the response to help measures
% Last Modified by GUIDE v2.5 10-Feb-2013 20:14:47
% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @measures_OpeningFcn, ...
'gui_OutputFcn', @measures_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin && ischar(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end
if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT
% --- Executes just before measures is made visible.
function measures_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% varargin command line arguments to measures (see VARARGIN)
% Choose default command line output for measures
handles.output = hObject;
% get handle to the controller
for i = 1:2:length(varargin)
switch varargin{i}
case 'controller'
handles.controller = varargin{i+1};
otherwise
error('unknown input')
end
end
handles.metricdata.density = 0;
handles.metricdata.volume = 0;
set(handles.density, 'String', 0);
set(handles.volume, 'String', 0);
set(handles.mass, 'String', 0);
set(handles.unitgroup, 'SelectedObject', handles.english);
set(handles.text4, 'String', 'lb/cu.in');
set(handles.text5, 'String', 'cu.in');
set(handles.text6, 'String', 'lb');
% Update handles structure
guidata(hObject, handles);
% UIWAIT makes measures wait for user response (see UIRESUME)
% uiwait(handles.figure1);
% --- Outputs from this function are returned to the command line.
function varargout = measures_OutputFcn(hObject, eventdata, handles)
% varargout cell array for returning output args (see VARARGOUT);
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% Get default command line output from handles structure
varargout{1} = handles.output;
% --- Executes during object creation, after setting all properties.
function density_CreateFcn(hObject, eventdata, handles)
% hObject handle to density (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles empty - handles not created until after all CreateFcns called
% Hint: popupmenu controls usually have a white background on Windows.
% See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
function density_Callback(hObject, eventdata, handles)
% hObject handle to density (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% Hints: get(hObject,'String') returns contents of density as text
% str2double(get(hObject,'String')) returns contents of density as a double
density = str2double(get(hObject, 'String'));
if isnan(density)
density = 0;
errordlg('Input must be a number','Error');
end
handles.controller.setDensity(density)
% --- Executes during object creation, after setting all properties.
function volume_CreateFcn(hObject, eventdata, handles)
% hObject handle to volume (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles empty - handles not created until after all CreateFcns called
% Hint: popupmenu controls usually have a white background on Windows.
% See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
function volume_Callback(hObject, eventdata, handles)
% hObject handle to volume (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% Hints: get(hObject,'String') returns contents of volume as text
% str2double(get(hObject,'String')) returns contents of volume as a double
volume = str2double(get(hObject, 'String'));
if isnan(volume)
volume = 0;
errordlg('Input must be a number','Error');
end
handles.controller.setVolume(volume)
% --- Executes on button press in calculate.
function calculate_Callback(hObject, eventdata, handles)
% hObject handle to calculate (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
handles.controller.calculate()
% --- Executes on button press in reset.
function reset_Callback(hObject, eventdata, handles)
% hObject handle to reset (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
handles.controller.reset()
% --- Executes when selected object changed in unitgroup.
function unitgroup_SelectionChangeFcn(hObject, eventdata, handles)
% hObject handle to the selected object in unitgroup
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
if (hObject == handles.english)
units = 'english';
else
units = 'si';
end
handles.controller.setUnits(units)
So, the model notifies the view when its properties change. the view updates the GUI as appropriate. The GUI sends requests to the controller in response to user interaction. The controller instructs the model to update. You would not want to use such a heavy solution for this simple model; but it doesn't take much more complexity for the benefits of MVC to shine through.
The complete source code for this demo is available at Matlab Central File Exchange.
Have fun with OO and MVC in Matlab!


Looks great,but what if I create my GUI(and is that means 'view' in MVC?) without GUIDE, what should I do and things I should notice?
ReplyDeleteHi Jason,
ReplyDeleteSorry to leave you hanging for so long.
As far as I can tell, there should be no difference. If you forgo using GUIDE, then you have access to the entire figure, rather than a partial, so I wonder if that would open up any new possibilities to you. I can't think of anything; but would love to learn from your experience.
Best,
Chris
A really super example. Thanks.
ReplyDeleteI will be sharing this with the guys at work.
The model reset() wasn't re-setting the radio buttons, so I added the following to the view handlePropEvents() call: (lines ~43,49) to force this (I believe this still follows MVC contraints)
ReplyDeletecase 'english'
...
set(handles.unitgroup, 'SelectedObject', handles.english);
case 'si'
...
set(handles.unitgroup, 'SelectedObject', handles.si);
Wow, Awesome example. Thank you so much. This article has greatly accelerated my understanding of MVC. Don't know if you still track this blog but I have a two follow-up questions.
ReplyDelete1) In the code above, the addlistener method calls a function in the View class. Would it be better (more akin to MVC) to call a function in the Controller class? I am under the impression that the View is "dumb" and the Controller "manages" the views; therefore it seems that we would prefer to keep the View (and Model) clean of any code that links the two.
2) If the GUI has an edit text uicontrol that "controls" the Model state (and subsequently the View), should the uicontrol's callback point to a Controller function? Or should the Controller have a listener on the View? The former seems to make more sense to me but is there a preferred way per MVC?
Thanks All,
Adam
Hi Adam,
DeleteThanks for the kind words. I'm glad you found it useful.
To me it makes sense that the view should 'listen' to the model, while the controller 'talks' to the model; but there are exceptions to every rule. So, I would answer your second question in the affirmative, i.e. the view should talk to the controller, which in turn should modify a setting in the model. There may be other ways to construct the code, but this was the best one I could come up with given the constraints (and allowances) of Matlab OO design.
Cheers,
Chris
Does the view interact with the model besides property event changes? E.g. View gets property directly from model, or should this still go through the controller?
ReplyDelete