2.3.1 通知、回调、触发器与链接
要完全理解通知、回调、触发器和链接的功能,首先必须理解界面编辑器操作、GUI引擎和API接口的工作原理,以及三者是如何与应用程序、用户进行交互的。开发者使用界面编辑器为系统最终用户设计界面窗口,并定义界面参数,比如设置执行时的通知、回调、触发器、弹出菜单和链接功能,事件发生时,GUI引擎就能理解这些事件,并做出相应的响应。
GUI引擎接收用户和应用程序传递的数据,比如用户在图形界面中点击鼠标、按下按键等,GUI引擎理解后,根据用户设置做出响应。比如发送事件通知给应用程序,应用程序再根据程序代码执行相应的事件处理以响应通知消息;应用程序也可以主动使用API命令发送界面更新和信息(比如修改仪表数值)给GUI引擎,GUI引擎理解后,再将更新和信息推送到界面显示给用户。
在开发阶段,开发者可以使用触发器功能指定用户与对象的交互产生的响应操作,比如用户按下指定的按钮,即显示指定的窗口,开发者可以在该按钮上设置一个触发器,当按钮在触发器内被按下,GUI引擎就打开指定的窗口。Tilcon中的触发器只与GUI引擎和用户进行交互,而不与应用程序进行交互。
回调是应用程序中与响应特定事件时被调用的函数,比如特定按钮被按下时可以触发一次回调。
链接功能用于链接两个以上对象,这些对象必须共享和显示相同的值。应用程序发送数据更新到某个对象时,链接到该对象的其他对象也会同步更新。链接是从应用程序收集数据,而通知、触发器和回调都是用于响应用户动作。
API是Tilcon图形界面与应用程序之间的交互接口,API库中包含了大量GUI引擎能够理解和执行的功能函数和命令,比如与通知和回调有关的API命令是TRT_GetInput()。当通知或回调发生时,GUI引擎将一个事件描述结构体推送到队列中,然后告知应用程序,并返回到自己的主循环;应用程序执行API命令TRT_GetInput()并等待从通知队列中接收下一个事件。若该事件为回调,则从TRT_GetInput()函数调用返回之前回调就会被执行,且回调执行返回值为1。
1.通知 在许多窗口和对象的属性设置对话框中,都有消息通知复选框,如图2-2所示。
图2-2 多种对象的通知消息类型
在通知复选框被选中的情况下,当预定义的事件发生时,GUI引擎将通知实时应用程序。应用程序接收到事件通知后,通常需要检查事件通知结构体中的Code和ID字段,以确定调用哪个回调函数。例如,在一个打开的窗口中,有一个仪表需要更新数据,通过发送通知消息给实时应用系统告知窗口已被打开,GUI引擎将创建更新仪表数据的必要条件。
2.回调 回调是一种用于绑定事件和对象到应用程序的交互机制,也是一种减少通知的ID分析的方式,建立回调过程和在用户应用程序中处理用户事件的解析工作都很少。每个对象在使用前都会在GUI引擎上注册一个或多个回调函数,当匹配的用户输入事件发生时,相应的回调函数即被调用。回调机制看上去是GUI引擎在应用程序中调用函数以响应绑定在对象上的用户事件。由于回调函数是在应用进程中定义,而GUI引擎在另一个不同的进程中,所以必须在应用进程中调用TRT_GetInput()为GUI引擎完成回调。每个进程都有自己的内存印象图,无法在没有帮助的情况下访问另一个进程中的内存或回调函数。
当TRT_GetInput()接口被调用时,GUI引擎将指示API库当前通知存在一个回调操作,API库将在返回应用前执行该回调函数。对于一个通知来说,该消息由描述事件的结构体组成,而对于回调来说,该消息还包括回调函数地址。应用程序接收到事件通知消息后,需要检查通知结构体中的Code和ID,以决定调用哪个回调函数。由于在回调创建过程中,回调通知已经关联事件到回调函数,因此这个步骤可以通过使用回调来去除。回调函数的触发条件在界面编辑器中通过对象的回调函数对话框进行设置,这些触发条件都注册在TWH表中,并被保存在TWD文件中。TWH表中注册了函数名(应用中的回调函数)和与之匹配的客户端数据(应用中的变量名、结构体或长整型数),回调数组名为结构体名后追加callback字符串。TWH文件必须编译到应用代码中,应用必须使用TRT_WindowCallbacks()接口调用通过结构体将这些信息传递给GUI引擎。
(1)回调设置过程 在界面编辑器中,可以设置回调的对象有可编辑文本、标签文本、多行文本、按钮、单选按钮、复选框、数字框、列表框、组合框(下拉列表框)、树、滑动块、目录框、原生图、颜色列表框、颜色组合框、LCD文本、Map对象等。为对象建立回调,需要执行如下操作:
1)在窗口属性设置对话框中,输入窗口的结构名;
2)在回调函数设置对话框,输入函数名(必需)和客户端数据(可选)。
为了更好地理解回调设置过程,示例如下:
打开界面编辑器,在画布窗口点击右键,弹出窗口属性设置对话框,在结构体名称设置项输入ST_name,如图2-3所示;
图2-3 设置窗口结构名
点击“Close”按钮,关闭对话框。选择“File > Save As”菜单或点击快捷工具栏中的“Save”按钮,保存当前窗口为callbackTest.twd文件。
在当前窗口中,添加两个按钮,并为他们设置表2-1所示回调函数。
表2-1 按钮回调函数列表
按照表2-1所示信息,分别在按钮b1和b2的回调函数设置对话框中,输入如图2-4所示内容。
图2-4 按钮回调函数设置
保存callbackTest.twd文件,并在同级目录下生成callbackTest.twh文件。用文本编辑器打开callbackTest.twh文件,我们可以看到如下内容:
extern void callb1(TRT_ReceiveData*, void*); extern void callb2(TRT_ReceiveData*, void*); extern void callb3(TRT_ReceiveData*, void*); struct { void (*callback)(TRT_ReceiveData*,void*); void *client_data; } ST_name_callback[8] = { callb1,(void *)&cbdata1,NULL,NULL,NULL,NULL,NULL,NULL, // type: button, ID:b1 callb2,(void *)&cbdata2,callb3,(void *)15,NULL,NULL,NULL,NULL, // type: button, ID:b2 };
callbackTest.twh文件只能在应用代码中被包含一次,客户端数据变量必须在该文件被包含之前在应用代码中声明。同时,回调函数也必须在应用代码中调用TRT_WindowCallbacks()向GUI引擎注册(示例如下)。
main() { long cbdata1=5; char cbdata2[5]="Hi!"; struct { long v1; void *ptr; }cbdata2; #include “callbackTest.twh” /*...*/ TRT_WindowLoad (TRT_pid, “callbackTest”); TRT_WindowCallbacks (TRT_pid, “callbackTest”, ST_name_callbacks, sizeof(ST_name_callback)); /*...*/ } /*Sample callback*/ void callb1 (void * trt_data, void *client_data) { TRT_ReceiveData *rec_data = trt_data; long *lvalue = client_data; printf ("code %d id %s\n", rec_data->code, rec_data->ID); printf ("client_data %d\n",*lvalue); }
当按钮b1被点击时,GUI引擎将发送消息通知应用程序调用callb1函数。TRT_ReceiveData结构体是回调函数的第一个参数,客户端数据变量的地址是回调函数的第二个参数。以上示例代码将打印如下输出信息:
code b id b1 client_data 5
(2)回调通用设置 所有界面对象在建立回调时,都需要执行如下所示的一些通用设置:
1〉从下拉框中选择一个回调条件(可以为每个回调条件设置一个回调);
2〉点击“Callback Attributes”设置区内的“Enable”或“Disable”选择框,作为回调的初始状态(该标识也可以通过Tilcon API调用进行修改);
3〉若需要先前设置的删除回调,点击“Delete Callback”按钮;
4〉输入回调函数名称(应用代码中的函数名);
5〉输入客户端数据名称(应用代码中的变量、结构体或数值);
6〉点击“OK”按钮关闭对话框,并保存当前回调设置;或点击“Cancel”按钮,关闭对话框,并放弃当前回调设置。
(3)回调条件 不同类型界面对象有不同的回调条件,各类对象的回调条件如下:
1〉按钮或复选框:Change to In/On、Change to Out/Off、Gain Focus、Lose Focus;
2〉可编辑文本:On Text if Modified、On Key Stroke、Gain Focus、Lose Focus;
3〉标签文本:On Key、Gain Focus、Lose Focus;
4〉单选按钮:Single Click、Gain Focus、Lose Focus;
5〉组合框、列表框、树、颜色组合框、列表框:Single Click、Double Click、Gain Focus、Lose Focus;
6〉按钮式菜单:Single Click;
7〉图表:Gain Focus、Lose Focus;
8〉目录框:Single Click、Double Click;
9〉图片、LCD对象:On Click、Gain Focus、Lose Focus;
10〉滑动块、数字框:On Release、On Change、Gain Focus、Lose Focus;
11〉弹出式菜单:Checked、Unchecked。
3.触发器 触发器是内嵌到窗口或对象中的必备API命令,当对象进入预定义状态时,执行该API命令以触发一个或多个动作(比如打开一个对话框)。触发器适用于对象和窗口两种不同的实体。
(1)对象触发器 对象触发器允许针对特定对象触发一个指定的动作,共有八种可供选择的触发器动作:删除、显示、隐藏、灰显、亮显、特效、修改、赋值。删除触发器用于执行满足事件触发条件时的删除操作,比如设置按钮In/On状态上的触发器在按钮被点击时,删除一张图片。显示触发器用于执行满足显示事件触发条件时的对象显示操作,比如设置按钮In/On状态上的触发器在按钮被点击时,显示一张图片。隐藏触发器用于执行隐藏事件触发时的对象隐藏操作,比如设置按钮In/On状态上的触发器在按钮被点击时,隐藏一张图片。灰显触发器用于执行灰显事件触发时的对象灰显操作,比如设置按钮In/On状态上的触发器在按钮被点击时,灰显一张图片。亮显触发器用于执行亮显事件触发时的对象亮显操作,比如设置按钮In/On状态上的触发器在按钮被点击时,亮显一张图片。特效触发器用于执行特效事件触发时的对象特效操作,比如设置按钮In/On状态上的触发器在按钮被点击时,特效显示另一个按钮对象,改变显示位置、大小和淡入淡出。修改触发器用于执行修改事件触发时的对象属性修改,比如设置In/On状态按钮On状态的修改触发器为当按钮被点击时,修改绘图对象(比如圆)的颜色属性。赋值触发器用于执行事件触发时的对象属性赋值,比如设置In/On状态按钮On状态的赋值触发器为当按钮被点击时,修改按钮的文本属性值。
触发器由界面编辑器创建,状态对象、可编辑文本和所有GUI/HMI对象(按钮、菜单项等)都可以触发事件。状态对象的每个状态都可以独立地触发事件,若状态对象被更新(值改变但状态不改变),则触发器不会被执行。菜单的每个选项、列表框中的每个选项或简略按钮都可单独触发事件。
每个按钮的进入、退出状态都有自己的触发器,对象的最终状态决定执行哪个触发器,按钮可以在进入和退出状态之间切换,或永久性地保持一个状态,或由拨动开关控制状态,在创建触发器之前明确是否允许按钮进行状态切换是至关重要的一点。若创建了两个及以上的触发器,其中一个触发器动作是删除包含该触发器的窗口,则该触发器必须是触发器队列中的最后一个。在按钮变更状态时,触发器也可以执行。
进行按钮触发器配置时,将弹出两个配置对话框,一个是触发器配置对话框、另一个是对象配置对话框,如图2-5所示。
图2-5 按钮触发器配置对话框
在对象配置对话框中,显示了“Text”“GUI”“Window”“HMI”四个单选按钮选项标签,单选按钮被选中后,将在“Object Type”列表框中显示对应的可选对象类型,在“Trigger Type”列表框中显示对象的可选触发器动作。Text、GUI、HMI对象的可选触发器动作有删除、显示、隐藏、灰显、亮显、特效、修改和赋值,Window对象的可选触发器动作有删除、显示、隐藏和加载。“Start Time(ms)”配置项允许开发者以毫秒的精度控制触发器动作的开始执行时间。“Trigger Object ID Of”配置项列举了触发器绑定的对象ID。“ID's List”配置项显示了当前界面窗口中的可选对象类型。
在触发器配置对话框中,用户输入触发状态配置用于创建用户动作型触发器,比如鼠标点击、按键操作;值修改触发状态配置用于使能/禁用状态对象,状态对象的回调条件和值设置后,当回调条件满足时,动作即被触发,比如弹出消息窗口、按钮颜色从绿色变为红色;触发器创建列表列举了当前对象建立的所有触发器动作,一个触发器可以触发一个或多个动作;编辑按钮用于修改触发器配置;后置添加按钮用于在选定的触发器之后添加另一个触发器;前置添加按钮用于在选定的触发器之前添加另一个触发器;删除按钮用于删除选定的触发器;每秒更新次数设置框用于设置每秒触发器动作执行的频数;特效显示速率设置框用于设置触发器特效执行速率,值越大特效运行的越快,反之亦然。
(2)窗口触发器 窗口触发器允许针对特定窗口触发一个动作,对于那些希望动态创建快速演示程序或无须用户输入应用系统的非编程人员来说,这是一个非常有用的特性。
当窗口被选中时,点击快捷工具栏中的创建动画图标,弹出模拟对话框,如图2-6所示。
图2-6 模拟对话框
在“Add Trigger”列表框中有四种窗口触发器动作可供选择:Delete、Display、Hide、Load。其中,Delete触发器用于删除选定的窗口;Display触发器用于显示选定的窗口;Hide触发器用于隐藏选定的窗口;Load触发器用于加载一个窗口文件到内存中,Load触发器必须在Display触发器之前创建。
4.链接 链接功能用于匹配两个或多个对象以共享同一的值,比如链接消息文本到仪表上,这样就可以读取仪表数值到消息文本中,同一信道内的任意多个对象都可以链接在一起。链接在窗口创建时指定,在界面实时显示期间无须调用API函数即可实现对象更新或执行动作。例如,对象A、B、C、D在同一信道上,则它们可以彼此相互链接、共享、甚至显示相同的值,在同一链接组内的任何对象的值被修改,该链接组内的其他对象的值也会同步修改,但该链接对其他信道上的对象不可见。被链接到同一组的对象拥有相同的链接组ID(区分字母大小写),但用同一链接组ID对多个对象赋值并不足以把它们链接在一起。为了确保链接的正确创建,必须对链接中的每个对象进行单独的显式链接设置。
大多数存储数值的对象都可以把数值链接到其他对象的数值上,表2-2是可链接对象与存储值类型列表。
表2-2 可链接对象及其存储值类型
[注意]任意到图表Y坐标的链接都是单向链接,例如当图表接收到新数据时,链接到图表Y坐标的仪表可以自动更新,但如果数据发送给仪表,图表则不会更新,这样链接中的每个对象都可以执行各自的数据计算。
我们以两个对象A和B之间建立链接(对象A链接到对象B)为例,对象A的链接配置对话框如图2-7所示,配置过程需要执行的主要步骤如下:
图2-7 对象A的链接配置对话框
(1)若要链接创建后立即生效,则点击“Value Change Link State”配置区域内的“Enable”按钮;若在其他条件满足时才形成有效链接,则点击“Value Change Link State”配置区内的“Disable”按钮,初始化链接为禁止状态。
(2)若对象A的输入由其他被链接的对象B来更新,则点击“User Input Link State”配置区域内的“Enable”按钮;若对象A的输入用于更新其他对象且不被其他被链接的对象B更新,则点击“Disable”按钮。
(3)在“Link To Group”配置区域内的“Link Group ID”输入框中,输入链接组ID(该ID也是链接到该组内的对象B及其他对象的链接组ID)。务必确保不要使用对象ID作为链接组ID,因为复用ID将导致系统错误,同时记住ID是区分字母大小写的。
(4)点击“OK”按钮,完成并保存对象A的链接配置。
对象B的链接配置对话框如图2-8所示,配置过程需要执行的主要步骤如下:
图2-8 对象B的链接配置对话框
(1)若对象A在当前窗口中(与对象B同在一个窗口内),则对象A的链接组ID将出现在“Link Group ID List”列表中,只需选中该链接组ID即可将对象B绑定到该链接上;
(2)若对象A不在当前窗口中(与对象B不同在一个窗口内),则在“Link Group ID”输入框中输入在对象A中设置的链接组ID(记住ID区分字母大小写);
(3)选择链接的初始状态是否为使能状态,以及当前对象的输入是否用于更新其他对象;
(4)点击“OK”按钮,完成并保存对象B的链接配置。
这里,以数字框(ID为number1)链接到消息文本(ID为text1)为例,其主要操作步骤如下:
(1)在消息文本对象text1的属性设置对话框“Action”配置页面中,点击“Link Object To”按钮,弹出对象链接配置对话框;
(2)在“Link Group ID”输入框中输入链接组ID(ID为TXT2NO);
(3)设置“Value Change Link State”和“User Input Link State”配置项为“Enable”状态,然后点击“OK”按钮,完成并保存text1的链接配置;
(4)在数字框对象number1的属性设置对话框“Action”配置页面中,点击“Link Object To”按钮,弹出对象链接配置对话框;
(5)若“Link Group ID List”列表中存在ID为TXT2NO的链接,则选择该链接组,自动将TXT2NO填写到“Link Group ID”输入框中,若不存在ID为TXT2NO的链接,则直接将TXT2NO填写到“Link Group ID”输入框中;
(6)设置“Value Change Link State”和“User Input Link State”配置项为“Enable”状态,然后点击“OK”按钮,完成并保存number1的链接配置。
如此,数字框中的任何数据更新都会同步实时更新消息文本,反之亦然。当使用消息文本链接到另一个消息文本对象时,则对象之间的数据更新需要使用管道(TRT_ATT_LINK)传输,除非数据遮罩是一个数值,比如TRT_ATT_TEXT|TRT_ATT_LINK。