Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>As <a href="https://stackoverflow.com/a/20078237/97160">@SamRoberts</a> explained, the <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller" rel="nofollow noreferrer">Model–view–controller</a> (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...</p> <p>Below is a complete yet simple example I wrote to demonstrate an MVC-based GUI in MATLAB.</p> <ul> <li><p>The <strong>model</strong> represents a 1D function of some signal <code>y(t) = sin(..t..)</code>. It is a handle-class object, that way we can pass the data around without creating unnecessary copies. It exposes observable properties, which allows other components to listen for change notifications.</p></li> <li><p>The <strong>view</strong> presents the model as a line graphics object. The view also contains a slider to control one of the signal properties, and listens to model change notifications. I also included an interactive property which is specific to the view (not the model), where the line color can be controlled using the right-click context menu.</p></li> <li><p>The <strong>controller</strong> is responsible of initializing everything and responding to events from the view and correctly updating the model accordingly.</p></li> </ul> <p>Note that the view and controller are written as regular functions, but you could write classes if you prefer fully object-oriented code.</p> <p>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.</p> <p>This design is very flexible as it allows you to build multiple views of the same data. Even more you can have multiple <em>simultaneous</em> 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.</p> <p>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 <code>.fig</code> file produced, and just ignore the accompanying <code>.m</code> file. We would setup the callbacks in the view function/class. This is basically what I did in the <code>View_FrequencyDomain</code> view component, which loads the existing FIG-file built using GUIDE.</p> <p><img src="https://i.stack.imgur.com/lNIHd.png" alt="GUIDE generated FIG-file"></p> <hr> <h3>Model.m</h3> <pre><code>classdef Model &lt; 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 &lt; 3, a = 1.2; end if nargin &lt; 2, f = 5; end if nargin &lt; 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 </code></pre> <h3>View_TimeDomain.m</h3> <pre><code>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 </code></pre> <h3>View_FrequencyDomain.m</h3> <pre><code>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 </code></pre> <h3>Controller.m</h3> <pre><code>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',{@onSlide,m}) set(v2.slider, 'Callback',{@onSlide,m}) set(v1.menu, 'Callback',{@onChangeColor,v1}) set(v2.menu, 'Callback',{@onChangeColor,v2}) % 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 </code></pre> <p><img src="https://i.stack.imgur.com/5PxYT.png" alt="MVC GUI1"> <img src="https://i.stack.imgur.com/GECpk.png" alt="MVC GUI2"></p> <p>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.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload