Dan*_*hoa 26 matlab user-interface matlab-deployment matlab-guide
我正在开发一个相当复杂的GUI程序,可以使用MATLAB Compiler进行部署.(有很好的理由使用MATLAB来构建这个GUI,这不是这个问题的重点.我意识到GUI构建不适合这种语言.)
有很多方法可以在GUI中的函数之间共享数据,甚至可以在应用程序中的GUI之间传递数据:
setappdata/getappdata/_____appdata - 将任意数据与句柄相关联guidata - 通常与GUIDE一起使用; "存储[s]或检索[s] GUI数据"到句柄结构set/get操作应用于UserData句柄对象的属性我的代码的结构不是最漂亮的.现在我将引擎与前端隔离开来(好!)但GUI代码很像意大利面条.这是一个"活动"的骨架,借用Android说话:
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
Run Code Online (Sandbox Code Playgroud)
我之前编写的代码没有嵌套,所以我最终h来回传递(因为需要重新绘制,更新等东西)并setappdata(fig)存储实际数据.无论如何,我一直在一个文件中保留一个"活动",我相信这将成为未来的维护噩梦.回调与应用程序数据和图形句柄对象进行交互,我认为这是必要的,但这阻止了代码库的两个"一半"的完全隔离.
所以我在这里寻找一些组织/ GUI设计帮助.即:
set/get是处理对象的属性).set/getappdata?我不是一个交易软件工程师,我只知道危险,所以我确信这些对于经验丰富的GUI开发人员(任何语言)来说都是相当基本的问题.我几乎觉得MATLAB中缺少GUI设计标准(确实存在吗?)严重干扰了我完成这个项目的能力.这是一个比我曾经做过的任何规模都要大得多的MATLAB项目,而且我以前从未考虑过具有多个数字窗口等的复杂UI.
Amr*_*mro 26
正如@SamRoberts所解释的那样,模型 - 视图 - 控制器(MVC)模式非常适合作为设计GUI的架构.我同意那里没有很多MATLAB示例来展示这样的设计......
下面是我在MATLAB中演示基于MVC的GUI的一个完整而简单的例子.
该模型代表某些信号的一维函数y(t) = sin(..t..).它是一个句柄类对象,这样我们就可以传递数据而不会创建不必要的副本.它公开了可观察的属性,允许其他组件侦听更改通知.
的视图给出了模型作为线条图形对象.该视图还包含一个滑块,用于控制其中一个信号属性,并侦听模型更改通知.我还包括一个特定于视图(不是模型)的交互式属性,其中可以使用右键单击上下文菜单控制线条颜色.
所述控制器负责初始化一切,从视图对事件作出响应并正确地相应地更新模型.
请注意,视图和控制器是作为常规函数编写的,但如果您更喜欢完全面向对象的代码,则可以编写类.
与通常的GUI设计方法相比,这是一项额外的工作,但这种架构的一个优点是数据与表示层的分离.这使得代码更清晰,更易读,尤其是在使用复杂的GUI时,代码维护变得更加困难.
此设计非常灵活,因为它允许您构建相同数据的多个视图.您可以拥有多个同步视图,只需在控制器中实例化更多视图实例,并查看一个视图中的更改如何传播到另一个视图中!如果您的模型可以通过不同方式直观呈现,这一点尤其有趣.
此外,如果您愿意,可以使用GUIDE编辑器来构建接口,而不是以编程方式添加控件.在这样的设计中,我们只使用GUIDE来使用拖放来构建GUI组件,但是我们不会编写任何回调函数.所以我们只对.fig生成的文件感兴趣,只是忽略附带的.m文件.我们将在view function/class中设置回调.这基本上就是我在View_FrequencyDomainview组件中所做的,它加载了使用GUIDE构建的现有FIG文件.

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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

在上面的控制器中,我实例化了两个独立但同步的视图,它们都表示和响应同一底层模型中的更改.一个视图显示信号的时域,另一个视图显示使用FFT的频域表示.
cha*_*pjc 10
该UserData属性是MATLAB对象的有用但遗留的属性.在"应用程序数据"套件的方法(即setappdata,getappdata,rmappdata,isappdata,等等)提供到相对更笨拙的理想替代品get/set(hFig,'UserData',dataStruct)的方法,IMO.实际上,为了管理GUI数据,GUIDE使用了guidata函数,它只是setappdata/ getappdatafunctions 的包装器.
AppData方法的一些优点'UserData'在于我想到的属性:
更多自然界面,适用于多种异构属性.
UserData仅限于一个变量,需要您设计另一层数据oranization(即结构).假设您要存储字符串str = 'foo'和数字数组v=[1 2].有了UserData,你就需要采取结构方案,如s = struct('str','foo','v',[1 2]);和set/get,每当你想这两个属性(例如整个事情s.str = 'bar'; set(h,'UserData',s);).随着setappdata,这个过程是比较直接(和有效)setappdata(h,'str','bar');.
受保护的底层存储空间接口.
虽然'UserData'它只是一个常规的句柄图形属性,但是包含应用程序数据的属性是不可见的,尽管它可以通过名称访问('ApplicationData',但不要这样做!).您必须使用setappdata更改任何现有的AppData属性,这可以防止您'UserData'在尝试更新单个字段时意外地破坏整个内容.此外,在设置或获取AppData属性之前,您可以验证命名属性是否存在isappdata,这有助于异常处理(例如,在设置输入值之前运行进程回调)以及管理GUI的状态或其任务管理(例如,通过存在某些属性来推断过程的状态并适当地更新GUI).
'UserData'和'ApplicationData'属性之间的一个重要区别'UserData'是默认情况下[](一个空数组),而'ApplicationData'本身就是一个结构.这种差异,与事实一起setappdata,并getappdata没有M-文件执行(它们是内置的),建议设置一个命名属性与setappdata它不要求重写数据结构的全部内容.(想象一下MEX函数执行结构字段的就地修改 - 一个操作MATLAB能够通过将结构维护为'ApplicationData'句柄图形属性的基础数据表示来实现.)
该guidata函数是AppData函数的包装器,但它仅限于单个变量,如'UserData'.这意味着您必须覆盖包含所有数据字段的整个数据结构才能更新单个字段.一个明确的优点是您可以从回调中访问数据而无需实际的数字句柄,但就我而言,如果您对以下语句感到满意,这不是一个很大的优势:
hFig = ancestor(hObj,'Figure')
Run Code Online (Sandbox Code Playgroud)
此外,正如MathWorks所述,存在效率问题:
在"句柄"结构中保存大量数据有时会导致相当大的减速,特别是如果GUIDATA经常在GUI的各个子功能中调用.因此,建议仅使用"handle"结构将句柄存储到图形对象.对于其他类型的数据,应使用SETAPPDATA和GETAPPDATA将其存储为应用程序数据.
此语句支持我的断言,即'ApplicationData'在使用setappdata修改单个命名属性时不会重写整个.(另一方面,guidata将handles结构变成一个'ApplicationData'被调用的字段'UsedByGUIData_m',因此很清楚为什么guidata在更改一个属性时需要重写所有GUI数据).
嵌套函数只需要很少的工作量(不需要辅助结构或函数),但它们显然将数据范围限制在GUI,使得其他GUI或函数无法在不将值返回到基础工作区或公共调用的情况下访问该数据功能.显然,这会阻止您将子功能拆分为单独的文件,'UserData'只要您传递图形句柄,就可以轻松地将其用于AppData.
总之,如果您选择使用句柄属性来存储和传递数据,则可以使用它们guidata来管理图形句柄(不是大数据)和 setappdata /或getappdata实际程序数据.他们不会互相覆盖,因为guidata让一个特殊'UsedByGUIData_m'的领域ApplicationData为handles结构(除非您使用该属性自己的错误!).只是重申一下,不要直接访问ApplicationData.
但是,如果您对OOP感到满意,通过类实现GUI功能可能更清晰,句柄和其他数据存储在成员变量中而不是处理属性,并且可以存在于类或包下的单独文件中的方法中的回调文件夹.在MATLAB Central File Exchange上有一个很好的例子.此提交演示了如何使用类简化传递数据,因为不再需要不断获取和更新guidata(成员变量始终是最新的).然而,还有一个额外的任务是在退出时管理清理,通过设置数字来完成提交closerequestfcn,然后调用delete类的功能.提交很好地与GUIDE示例相似.
这些是我看到的亮点,但MathWorks讨论了更多细节和不同的想法.又见这个官方的回答来UserData对guidata对setappdata/getappdata.
我不同意MATLAB不适合实现(甚至复杂的)GUI - 它非常好.
但是,真实的是:
由于这些原因,大多数人只接触到非常简单或非常糟糕的MATLAB GUI,他们最终认为MATLAB不适合制作GUI.
根据我的经验,在MATLAB中实现复杂GUI的最佳方式与在另一种语言中实现的方式相同 - 遵循一个常用的模式,如MVC(模型 - 视图 - 控制器).
但是,这是一个面向对象的模式,所以首先你必须熟悉MATLAB中的面向对象编程,特别是使用事件.使用你的应用程序中的面向对象的组织应该意味着你提到的所有讨厌的技术(setappdata,guidata,UserData,嵌套函数的范围,以及来回传递多个数据副本)是没有必要的,因为所有相关的东西都可以作为类属性.
我所知道的MathWorks已经发布的最好的例子是MATLAB Digest的这篇文章.即使这个例子也很简单,但是它让你知道如何开始,如果你看一下MVC模式,它应该变得清晰,如何扩展它.
另外,我通常大量使用包文件夹来组织MATLAB中的大型代码库,以确保没有名称冲突.
最后一个提示 - 使用MATLAB Central 的GUI Layout Toolbox.它使GUI开发的许多方面变得更加容易,尤其是实现自动调整大小行为,并为您提供了几个额外的UI元素.
希望有所帮助!
编辑:在MATLAB R2016a中,MathWorks引入了AppDesigner,这是一个新的GUI构建框架,旨在逐步取代GUIDE.
AppDesigner在几个方面代表了MATLAB中以前的GUI构建方法的一个重大突破(最深层的,生成的底层图形窗口基于HTML画布和JavaScript,而不是Java).这是在R2014b中引入Handle Graphics 2引发的另一条道路,无疑将在未来版本中进一步发展.
但AppDesigner对问题的一个影响是它产生了比GUIDE更好的代码 - 它非常干净,面向对象,并且适合构成MVC模式的基础.
| 归档时间: |
|
| 查看次数: |
15771 次 |
| 最近记录: |