正如
@SamRoberts所解释的,
模型-视图-控制器(MVC)模式非常适合设计GUI的架构。我同意目前并没有很多MATLAB示例来展示这样的设计...
下面是一个我写的完整而简单的示例,用于演示基于MVC的MATLAB GUI。
模型表示某个信号的1D函数y(t) = sin(..t..)
。它是一个句柄类对象,这样我们可以在不创建不必要的副本的情况下传递数据。它公开可观察属性,允许其他组件监听更改通知。
视图将模型呈现为线性图形对象。视图还包含一个滑块来控制信号属性之一,并侦听模型更改通知。我还包括了一个交互属性,它是特定于视图(而不是模型)的,可以使用右键上下文菜单控制线条颜色。
控制器负责初始化所有内容并响应来自视图的事件,并正确地更新模型。
请注意,视图和控制器是编写为常规函数的,但是如果您更喜欢完全面向对象的代码,则可以编写类。
与通常设计GUI的方式相比,这需要一些额外的工作,但这种架构的优点之一是将数据与表示层分离。特别是在处理复杂的GUI时,这使得代码更加清晰易读,其中代码维护变得更加困难。
这种设计非常灵活,因为它允许您构建相同数据的多个视图。更重要的是,您可以拥有多个同时的视图,只需在控制器中实例化更多的视图实例,然后看看一个视图中的更改如何传播到其他视图!如果您的模型可以以不同的方式进行可视化呈现,则这尤其有趣。
此外,如果您愿意,您可以使用GUIDE编辑器构建界面,而不是通过编程方式添加控件。在这样的设计中,我们将仅使用GUIDE使用拖放来构建GUI组件,但不会编写任何回调函数。因此,我们只对生成的.fig
文件感兴趣,并忽略附带的.m
文件。我们将在视图函数/类中设置回调。这基本上就是我在View_FrequencyDomain
视图组件中所做的,它加载使用GUIDE构建的现有FIG文件。
![GUIDE generated FIG-file](https://istack.dev59.com/lNIHd.webp)
Model.m
classdef Model < handle
properties (SetObservable = true)
f
a
end
properties (SetAccess = private)
fs
t
noise
end
properties (Dependent = true, SetAccess = private)
data
end
methods
function obj = Model(fs, f, a)
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;
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)
y = obj.a * sin(2*pi * obj.f*obj.t) + ...
sin(2*pi * 2*obj.f*obj.t) + obj.noise;
end
end
methods
function [mx,freq] = computePowerSpectrum(obj)
num = numel(obj.t);
nfft = 2^(nextpow2(num));
numUniquePts = ceil((nfft+1)/2);
freq = (0:numUniquePts-1)*obj.fs/nfft;
fftx = fft(obj.data, nfft);
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)
handles = initGUI();
onChangedF(handles, m);
addlistener(m, 'f', 'PostSet', ...
@(o,e) onChangedF(handles,e.AffectedObject));
end
function handles = initGUI()
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);
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);
xlabel(hAx, 'Time (sec)')
ylabel(hAx, 'Amplitude')
title(hAx, 'Signal in time-domain')
handles = struct('fig',hFig, 'ax',hAx, 'line',hLine, ...
'slider',hSlid, 'menu',hMenuItem);
end
function onChangedF(handles,model)
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()
hFig = hgload('ViewGUIDE.fig');
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');
hLine = line('XData',NaN, 'YData',NaN, 'Parent',hAx, ...
'Color','r', 'LineWidth',2);
set(hLine, 'uicontextmenu',hMenu);
xlabel(hAx, 'Frequency (Hz)')
ylabel(hAx, 'Power')
title(hAx, 'Power spectrum in frequency-domain')
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
m = Model(100);
v1 = View_TimeDomain(m);
v2 = View_FrequencyDomain(m);
set(v1.slider, 'Callback',{@onSlide,m})
set(v2.slider, 'Callback',{@onSlide,m})
set(v1.menu, 'Callback',{@onChangeColor,v1})
set(v2.menu, 'Callback',{@onChangeColor,v2})
pause(3)
m.f = 10;
end
function onSlide(o,~,model)
model.f = get(o,'Value');
end
function onChangeColor(o,~,handles)
clr = get(o,'Label');
set(handles.line, 'Color',clr)
set(handles.menu, 'Checked','off')
set(o, 'Checked','on')
end
![MVC GUI2](https://istack.dev59.com/GECpk.webp)
在上面的控制器中,我实例化了两个独立但同步的视图,它们都表示并响应同一基础模型中的变化。一个视图显示信号的时域,另一个使用FFT显示频域表示。