陈建++张胜良++李鑫
摘 要:MATLAB具有高效科学的计算功能、友好的用户界面及功能丰富的应用工具箱,在众多科学领域得到了广泛的应用。MATLAB GUI具有学习起点低、易学易懂、开发周期短的优点,用户不必深入掌握面向对象的编程语言也能设计出精美的人机界面。深入探讨了MATLAB GUIDE程序设计中参数在内部函数间及窗口间传递的不同方式,结合实例阐述了各方式原理,讨论了其优缺点、使用范围和使用方法,以方便MATLAB GUIDE程序设计者检索查看并对其熟练掌握。
关键词:MATLAB GUIDE;内部函数;窗口;参数传递
DOIDOI:10.11907/rjdk.151537
中图分类号:TP301
文献标识码:A 文章编号文章编号:16727800(2015)009001205
基金项目基金项目:国家自然科学基金项目(51175473);浙江省教育厅项目(Y201122752)
作者简介作者简介:陈建(1975-),男,山东泰安人,博士,浙江工业大学机械学院副教授、硕士生导师,研究方向为绿色设计及制造、可拓设计;张胜良(1987-),男,江苏徐州人,浙江工业大学机械学院硕士研究生,研究方向为绿色设计及制造;李鑫(1991-),女,湖南株洲人,浙江工业大学机械学院硕士研究生,研究方向为绿色设计及制造。
0 引言
GUI(Graphical User Interface, 图形用户界面)是指由窗口、菜单、对话框等各种图形对象组成的用户界面,提供一个友好的人机图形交互平台,用户通过一定的方法(如鼠标或键盘)选择、激活这些图形对象,使计算机产生某种动作或变化,例如实现符号运算、数值计算、图形处理等,具有友好性、操作方便的优点[1]。创建MATLAB GUI通常有两种方式[2]:①使用M文件直接利用句柄图形指令生成控件及其功能,移植性好,但是因为纯代码编写,工作量较大,一般需要设计者有丰富的经验[3];②使用GUIDE(Graphical User Interface Development Environment, 图形用户界面开发环境),它提供了一系列工具用于建立GUI对象,这些工具极大简化了设计和创建GUI的过程,可快速完成GUI界面布局,自动对M文件编程,实现控件的功能扩展[410]。显然第二种方法操作方便、效率高,更适合写大型程序。本文讨论的参数在内部函数及窗口间的传递方式仅限于后者。
参数在内部函数及界面间的传递方式是MATLAB GUIDE程序设计的重点,也是难点,如果熟练掌握,会达到事半功倍的效果。笔者利用MATLAB GUIDE程序设计了“基于模糊AHPTOPSIS的绿色设计油锯评价系统”软件,如图1、图2所示,这里需要设置并处理大量的参数在内部函数及窗口间的传递。
图1 基于模糊AHPTOPSIS的绿色设计油锯评价系统
为了叙述的方便性和统一性,在本文中,Variable代表参数,Value代表参数值,Nameoffile代表文件名。本文只对涉及到的各类命令函数作简单介绍,具体使用方法请参阅参考文献[11]。另外,验证实例编程时,需要在COMMAND WINDOW输入以下代码,清除所有窗口,以防出现未知错误。
h=findall(0,'type','figure');
delete(h);
1 参数在内部函数间传递方式
MATLAB GUIDE程序设计中,参数在内部函数间传递方式有多种,主要分为:handles/guidata、userdata、getappdata/setappdata、save/load、evalin/assignin、global函数等,为了形象具体地说明参数在内部函数间传递的程序设计,特用一个例子来说明。如图 3所示,其实现功能如下:在弹出菜单中选择一项,然后点击显示,右边文本就会显示选择的内容,这就涉及到了弹出菜单的数据如何传递到触控按钮的问题,其中,设置窗口tag:figure,设置弹出菜单tag:popupmenu/string:软件/导刊,设置触控按钮tag:pushbutton/enable:off,设置静态文本tag:text,其余采用默认设置。另外,GUIDE自动生成的M文件中,各函数除了下文添加了代码的函数外均采用默认代码。
图2 基于模糊AHPTOPSIS的绿色设计油锯评价系统参数
图3 参数在内部函数间传递方式实例
1.1 handles/guidata
GUIDE中某一窗口的所有控件使用同一个handles结构体,handles结构体中保存了图形窗口中所有对象的句柄,可以使用handles获取或设置某个对象的属性。由于handles是个结构体,可以为它增加新的结构域,并可在该窗口对应的M文件各函数中共享调用,这就实现了参数在内部函数间的传递。
在回调函数中把参数存入handles的方式为:
handles=guidata(handles.figure);
…
handles.variable=value;
…
guidata(handles.figure,handles);
其中,handles=guidata(handles.figure)为该函数取之前的GUI数据,并保存到handles中,guidata(handles.figure,handles)用来更新handles结构,生成新的GUI数据。guidata任何时刻只能管理一个GUI数据,也就是说,任何GUI任何时刻只能有一个handles结构体,并且不能使用guidata存储除handles结构外的任何其它数据,否则,它会覆盖handles结构体,导致GUI不能运行[11]。这里有一点需要注意,经常有人引用opening函数中的guidata(hObject,handles)代替guidata(handles.figure,handles),在opening函数中,hObject是该窗口的句柄,而在回调函数中,hObject指的是该控件的句柄。当hObject不是窗口对象句柄时,MATLAB会将handles保存到hObject的父类窗口句柄中。为了保证句柄的一致性,避免未知的错误,推荐使用guidata(handles.figure,handles)。
另,handles不宜添加过多的用户数据,因为在GUIDE生成的M文件中,handles是每个回调函数的输入参数,这样会加大每个回调函数不必要的内存开销。
为了实现示例效果,可在弹出菜单和触控按钮的callback函数中添加如下代码:
function popupmenu_Callback(hObject, eventdata, handles)
handles=guidata(handles.figure);%取得之前的GUI数据,并保存到handles中去
set(handles.pushbutton,'enable','on');%设置触控按钮可使用
v = get(handles.popupmenu,'value'); % 确定选择弹出菜单的第几项
str=get(handles.popupmenu,'string');%获得弹出菜单的所有内容
switch v
case 1
handles.forshow=str{1};%选择第一项,把字符串“软件”添加到handles结构体中
case 2
handles.forshow=str{2};%选择第二项,把字符串“导刊”添加到handles结构体中
end
guidata(handles.figure,handles);%更新handles结构,参数forshow被保存进去
function pushbutton_Callback(hObject, eventdata, handles)
set(handles.text,'string',handles.forshow);%使文本显示选择的内容
1.2 userdata
userdata方式用于存储用户定义的数据,可以为任何数据类型,直接通过对象的userdata属性进行各个callback之间的数据存取操作。虽然使用这种方法简单、安全性高[12],但是每个对象仅能存取一个变量值,因此当同一对象存储多次变量时,先前的变量就会被覆盖掉,只保留最后一次存储的变量,因此UserData适用于存储简单、单一且读取不频繁的数据。使用方法:
首先将数据存储到一个特定的对象中,建议选定该窗口:
set(handles.figure,'userdata',variable)
此时,variable数据就存在该窗口属性内:
variable= get(handles.figure,'UserData')
取回变量,可在任意callback中获取该数据值。
1.3 getappdata/setappdata
Application数据保存在GUI对象的一个未公开属性Application Data内,该属性的值为一个结构体。一个GUI中,可同时存在多个Application数据,通常选择窗口对象作为Application数据的保存对象。与UserData的方式相比,明显克服了UserData的缺点,使一个对象能存取多个变量值,适用于变量多且读取不频繁的数据。使用方法:
setappdata(handles.figure,variable,value)
添加新的结构域variable到对象handles.figure的Application数据中,value可以为任何类型数据。
value=getappdata(handles.figure,variable)
获取对象handles.figure的Application数据中variable字段的值:
isappdata(handles.figure,variable)
判断对象handles.figure的Application数据中是否存在字段variable,存在返回1,否则返回0。
1.4 save/load
save函数将workspace中变量用二进制格式保存在当前工作目录的MAT文件下,可以使用load函数重新载入数据,其思想是将某参数的值先存到磁盘,等待用的时候再调用。当然,这种方式涉及到磁盘读写,速度会有影响,但却方便对所有的全局参数进行管理,且适用于大量数据的传递[12]。使用方法:
save nameoffile.mat variable1 variable2…variableN
把工作空间多个参数保存到名为nameoffile的MAT文件中,如果此前该MAT文件已存在,则写成save nameoffile.mat variable append。
return= load('nameoffile.mat')
将nameoffile.mat加载到工作空间,返回值return为一结构体,包括该MAT文件中的所有数据。
1.5 evalin/assignin
MATLAB通常的基本工作空间是base空间。MATLAB在程序运行过程中,将为每个函数分配其函数工作空间(从基本空间中分割出的一块, 以函数名作为其工作空间名),各个工作空间之间的变量不能直接引用,在函数退出后,该函数空间也就立即被注销。因此,对于函数文件,运行结果除输出变量返回到基本工作空间或其父工作空间(调用该函数的程序工作空间)之外,其它中间变量都不在基本工作空间或其父工作空间保留。为了得到某中间变量,可使用evalin/assignin函数,其优点是方法简单,但在维护工作时不能清除[12]。使用方法:
assignin('base','variable',value)
这样工作空间中就生成一个名字为variable的变量,保存有value数据。
evalin('base','variable')
得到工作空间变量variable的数据。
为了实现示例效果,可在弹出菜单和触控按钮的callback函数中添加如下代码:
function popupmenu_Callback(hObject, eventdata, handles)
set(handles.pushbutton,'enable','on');%设置触控按钮可使用
v = get(handles.popupmenu,'value');% 确定选择弹出菜单的第几项
str=get(handles.popupmenu,'string');%获得弹出菜单的所有内容
switch v
case 1
forshow=str{1};%选择第一项,把字符串“软件”添加到handles结构体中
case 2
forshow=str{2};%选择第二项,把字符串“导刊”添加到handles结构体中
end
assignin('base','zsl',forshow);%工作空间中生成一个名字为zsl的变量,保存有forshow数据
function pushbutton_Callback(hObject, eventdata, handles)
forshow=evalin('base','zsl');%得到工作空间变量zsl的数据,返回给forshow。
set(handles.text,'string',forshow);%使文本显示选择的内容
1.6 global函数
global函数方式是最简单的方式,但也是最容易出错的方式,其优点很明显:代码可读性好,代码运行效率高。因为通过函数传参数的方式,系统会浪费过多的时间在复制数据上,其缺点也很明显:如果你在一个地方修改了x值,那么所有x值就都变了,会出现未知错误。另外,全局变量破坏了程序的封装性。所以,global函数方式是最后考虑的方式,只适合在单窗口且全局变量较少或数据存取比较频繁时使用,多窗口且全局变量较多时应避免使用。使用方法:
在定义全局变量时,加一句 global variable;在调用全局变量前,加一句 global variable。
2 参数在窗口间传递方式
两个窗口间的结构分为顺序结构和主子结构,这是相对于某个参数是先定义在调用还是调用时再定义而言的。当窗口A运行时产生的参数在后来运行的窗口B中得到调用,则窗口A、B为顺序结构;当窗口A运行时,需运行窗口B得到其预定义数据或产生某参数以调用,则窗口A、B为主子结构,A为主窗口,B为子窗口。
2.1 顺序结构
MATLAB GUIDE程序设计中,参数在窗口间顺序传递方式有多种,主要分为:findall/guidata、save/load、global函数、setappdata/getappdata等,它们各有自己的优缺点,下文将进行具体阐述。为了形象具体地说明参数在顺序结构窗口间传递的程序设计,特用一个例子来说明,如图 4、图5所示,其实现功能如下:在窗口A的弹出菜单中选择一项后,窗口A消失,进入窗口B,点击显示,文本就会显示在窗口A选择的内容,这就涉及到窗口A中弹出菜单的数据如何传递到窗口B触控按钮的问题。其中,在窗口A中,设置窗口tag:figure1,设置弹出菜单tag:popupmenu1/string:软件/导刊,在窗口B中,设置窗口tag:figure2,设置触控按钮tag:pushbutton2,设置静态文本tag:text2,其余采用默认设置。另外,GUIDE自动生成的M文件中,各函数中除了下文添加了代码的函数外均采用默认代码。
图4 参数在顺序结构窗口 图5 参数在顺序结构窗口 间传递方式示例 间传递方式示例
2.1.1 findall/guidata
在A窗口,按照参数在内部函数间handles/guidata的传递方式,把传递变量保存到handles中。
在B窗口,进行如下步骤:
HandlesofAObject=findall(0,property1,value1,…)
在根对象内查找满足要求的窗口并返回所查找到的窗口句柄,要使查找的窗口具有唯一性,可利用该窗口的多个属性值或利用唯一属性值:
HandlesofAObject=guidata(HandlesofAObject)
获得窗口A的handles数据。
2.1.2 save/load
使用方法与参数在内部函数间传递方式相同,故不赘述。
2.1.3 global
使用方法与参数在内部函数间传递方式相同,故不赘述。
2.1.4 setappdata/getappdata
使用方法与参数在内部函数间传递方式大体相同,通常选择根对象作为Application数据的保存对象,使用方法:
setappdata(0,variable,value)
添加新的字段variable到根对象的Application数据中,value可以为任何类型数据。
value=getappdata(0,variable)
获取根对象的Application数据中variable字段的值。
isappdata(0,variable)
判断根对象的Application数据中是否存在字段variable,存在返回1,否则返回0。
2.2 主子结构
MATLAB GUIDE程序设计中,参数在窗口间主子传递的方式主要有利用GUIDE本身的varargin/varargout传递和利用waitfor函数传递。为了形象说明参数在主子结构窗口间传递的程序设计,特用一个例子来说明,如图 6、图7所示,其实现功能如下:在主窗口A中点击显示按钮,弹出子窗口B,在子窗口B中的弹出菜单中选择某一项后,窗口B消失,在窗口A中显示选择的内容,这就涉及到子窗口B弹出菜单的数据如何传递到主窗口A触控按钮的问题,其中,在窗口A中,设置窗口tag:figure1,设置触控按钮tag:pushbutton1,设置静态文本tag:text1;在窗口B中,设置窗口tag:figure2,设置弹出菜单tag:popupmenu2/string:软件/导刊,其余采用默认设置,另外,GUIDE自动生成的M文件各函数中除了下文添加了代码的函数外均采用默认代码。
图6 参数在主子结构窗口 图7 参数在主子结构窗口
间传递方式示例一 间传递方式示例二
2.2.1 利用GUIDE本身的varargin/varargou传递参数
GUIDE执行函数顺序为:Opening函数→Output函数→Callback函数,其中Opening函数和Output函数只会执行一次,执行完Opening函数,GUI窗口就被创建了,执行完Output函数就已经输出varargout了。若要GUI根据用户的操作来输出varargout,可以使用uiwait和uiresume,此时GUIDE执行函数顺序为:Opening函数→Callback函数→Output函数[11]。在窗口的OpenFcn中,如果不加uiwait,程序会直接运行到下面,执行OutputFcn,也就是说程序一运行,返回值就确定了,再在其它部分对handles.output作更改也没有效果了。加上uiwait,只有执行了uiresume后,才会继续执行到OutputFcn,在此之前用户有充分的时间设置返回值。使用方法:
在子窗口M文件中,假设子窗口的名称为subGUI,设想的参数输入输出为:[out1,out2,…,outN]=subGUI(in1,in2,…,inN)。
(1)在OpeningFcn函数中,读入参数,并用guidata保存,即:
handles.in1=varargin{1};
handles.in2=varargin{2};
…
handles.inN=varargin{N};
guidata(hObject,handles);
(2)在OpeningFcn函数的结尾加上uiwait(handles.figure2)。
(3)在窗口中控制程序结束的回调函数末尾加上uiresume(handles.figure2)。
(4)在子窗口的OutputFcn中设置要传递出去的参数,即
varargout{1}=handles.out1;
varargout{2}=handles.out2;
…
varargout{N}=handles.outN;
(5)末尾添加delete(handles.figure2)结束程序。
通过以上设置后,在主窗口就可以通过[out1,out2,…,outN]=subGUI(in1,in2,…,inN)的形式调用该子程序。在主窗口中调用子窗口时,主窗口不需要特别设置,同调用普通的函数一样。在打开子窗口的同时,主程序还可以响应其它控件,不需要担心子窗口的返回值被传错了地方。
在子窗口(subGUI.fig)的M文件中,添加如下代码:
function subGUI_OpeningFcn(hObject, eventdata, handles, varargin)
handles.output = hObject;
guidata(hObject, handles);
uiwait(handles.figure2);%暂停执行OutputFcn函数执行回调函数,直到resume命令出现
function varargout = subGUI_OutputFcn(hObject, eventdata, handles)
varargout{1} = handles.forshow;%输出参数为handles结构体中的forshow结构域,即在弹出菜单选中的内容
delete(handles.figure2);%删除子窗口
function popupmenu2_Callback(hObject, eventdata, handles)
handles=guidata(handles.figure2);%取得之前的GUI数据,并保存到handles中去
v = get(handles.popupmenu2,'value'); % 确定选择弹出菜单的第几项
str=get(handles.popupmenu2,'string');%获得弹出菜单的所有内容
switch v
case 1
handles.forshow=str{1};%选择第一项,把字符串“软件”添加到handles结构体中
case 2
handles.forshow=str{2};%选择第二项,把字符串“导刊”添加到handles结构体中
end
set(handles.figure2,'visible','off');%设置子窗口不可见
guidata(handles.figure2,handles);%更新handles结构,参数forshow被保存进去uiresume(handles.figure2);%恢复执行OutputFcn函数
在主窗口(mainGUI.fig)的触控按钮的callback函数添加如下代码:
function pushbutton1_Callback(hObject, eventdata, handles)
forshow=subGUI;%函数subGUI的返回值赋给变量forshow
set(handles.text1,'string',forshow);%使文本显示选择的内容
2.2.2 利用waitfor函数
waitfor函数用来阻止程序执行,等待事件或条件的发生直到条件满足,程序继续执行。可以很好地利用waitfor这一特性来进行参数在主子窗口间传递。以前例说明:首先在子窗口B利用前文所述的handles/guidata,把传递的参数保存到handles中,然后在主窗口中点击触控按钮,弹出子窗口B,此时利用waitfor函数来阻止主窗口A运行,并对子窗口B进行操作,直到操作完毕(也就是满足了waitfor的条件),主窗口A继续运行,再次通过guidata函数完成主子窗口间的数据传递。
在子窗口(subGUI.fig)的弹出菜单callback函数添加如下代码:
function popupmenu2_Callback(hObject, eventdata, handles)
handles=guidata(handles.figure2);%取得之前的GUI数据,并保存到handles中去
v = get(handles.popupmenu2,'value'); % 确定选择弹出菜单的第几项
str=get(handles.popupmenu2,'string');%获得弹出菜单的所有内容
switch v
case 1
handles.forshow=str{1};%选择第一项,把字符串“软件”添加到handles结构体中
case 2
handles.forshow=str{2};%选择第二项,把字符串“导刊”添加到handles结构体中
end
guidata(handles.figure2,handles);%更新handles结构,参数forshow被保存进去
set(handles.figure2,'visible','off');%设置子窗口不可见
在主窗口(mainGUI.fig)的触控按钮的callback函数添加如下代码:
function pushbutton1_Callback(hObject, eventdata, handles)
handlesofsubGUI =subGUI;%生成子窗口,返回其句柄
waitfor(subGUI,'visible');%程序暂停,直到在子窗口弹出菜单中选择一项
handlesofsubGUI =guidata(handlesofsubGUI);%取得子窗口GUI的handles
forshow= handlesofsubGUI.forshow;%返回handlesofsubGUI结构体中结构域forshow
set(handles.text1,'string',forshow);%使文本显示选择的内容
3 结语
本文讨论了MATLAB GUIDE程序设计中参数在内部函数间及窗口间传递的不同方式,并结合实例阐述了各方式原理。受篇幅限制,部分示例略。不同的传递方式有各自的优缺点和适用范围,在应用时需结合具体情况选择。
参考文献参考文献:
[1] 于育民, 梁瑛. MATLABGUI在图形可视化中的应用[J]. 安阳工学院学报, 2010, 9(4):5354,96.
[2] 纪元法, 孙希延, 施浒立. MATLAB的两种图形用户界面参数输入方法[J]. 电脑知识与技术, 2008,2(13):728731.
[3] 陈德伟. MATLAB图形用户界面的制作[J]. 常州工学院学报, 2005,18(4):811.
[4] 徐俊立, 王强, 金珩. MATLAB环境下的GUI编程[J]. 内蒙古民族大学学报:自然科学版, 2006,21(6):640641.
[5] 陈安宇, 陈伟, 石彬, 等. MATLAB图形用户界面的应用研究[J]. 机电工程技术, 2008,37(10):2627,46.
[6] 梁辉. MATLAB制作图形界面的应用[J]. 佳木斯大学学报:自然科学版, 2003,21(4):402406.
[7] 陈立明, 董辉 ,李加海. 基于MATLAB 6.5的图形用户界面应用技术研究[J]. 电脑开发与应用, 2005,18(4):4749.
[8] 陈子为. 基于MATLAB GUI扫雷游戏的设计与实现[J]. 现代电子技术, 2008,31(24):8588.
[9] 王玉林, 葛蕾, 李艳斌. 新型界面开发工具:MATLAB GUI[J]. 无线电通信技术, 2008,34(6):5052.
[10] 张秋红, 李玉忍. 用MATLAB制作图形用户界面[J]. 电脑开发与应用, 2003,16(3):1011,14.
[11] 罗华飞. MATLAB GUI设计学习手记[M]. 北京: 北京航空航天大学出版社, 2009.
[12] 南洋, 周静, 孟开元, 等. 基于MATLAB GUI的界面设计[J]. 石油仪器, 2008,22(6):7678.
责任编辑(责任编辑:杜能钢)