事件结构体
组件中触发某事件时,XMANAGER就会调用为该组件指定的事件处理器。XMANAGER调用事件处理器时只传入一个参数,就是事件结构体。事件结构体是一个有名结构体,其中含有组件名和其他此事件的相关信息。例如,考虑按钮组件的事件结构体: event={widget_button,id:0L,top:0L,handler:0L,select:0L}
此事件结构体样本是一个有四个域的有名结构体。前三个域id,top和handler是所有组件事件结构体都必须要有的域,每个域都保存中特殊的组件标识名: event.id 触发事件的组件的标识。若按下某个按钮,event.id就是该按钮的组件标识。 event.top 组件层次构架中顶层基底的组件标识。对由同一层次构架生成的组件而言此值相同。
event.handler 指定给该事件处理器的组件的标识。
组件结构体中的另一个域中包含着触发事件的组件的具体信息。一个按钮组件的事件结构体总有一个select域,它指示着按钮的开关状况(1或0)。其他事件结构体可能会包含更多的信息。
调用XMANAGER
现在,PLOTINTERACTIVE还不能触发事件,创建能使用户交互式地触发事件的最简单的方法是使用XMANAGER。XMANAGER将在IDL中注册该组件并开始一个等待事件发生的循环。将下述的黑体部分代码加入到组件创建例程PLOTINTERACTIVE中: plotinteractive
; Draw a plot.
Plot, data , linestyle=0
; Call xmanager to start the event handling process. Xmanager,?plotinteractive?,tlb
end
XMANAGER需要两个参数,一个是含有需要注册的应用程序名的字符串,一个是顶层基底的组件标识名。
组件程序数据
可将任何用于组件程序的数据存储在某个特定位置,这样就可通过事件处理器访问它们了。组件数据具有保留性质,这意味着若某事件处理器改动了某数据,则其他事件处理器访问的将是修改后的数据。
在面向过程的编程过程中,数据被作为参数从一个例程传递到另一个,共用模块中的数据成员都被设置为全局变量。组件程序是事件驱动的,程序的大部分时间花费在循环中等待来自用户界面的事件。“事件循环”发生在XMANAGER中,它还将调用事件处理器,XMANAGER过程不允许用户将数据从创建例程直接传入事件处理器。
共用模块和组件
解决数据传递及其保留性质的方法之一是将所有组件数据放在一个共用模块中,但在组件程序中使用共用模块可能造成意外的不良影响。数据存在于IDL共用模块中时,它们对每个
6
包含该共用模块的应用程序都是全局性的,这就意味着在同一个IDL运行期中将某程序运行两次时,该程序会使用相同的内存空间并会相互覆盖。因此最好避免在IDL组件程序中使用共用模块。
组件用户值
一种比较好的解决数据传递问题的方法是用组件来传递数据。每个组件都有一个可用于保存一条用户自定义的信息的附加存储区域,称为用户值。为组件创建函数设置UVALUE关键字或为WIDGET_CONTROL过程设置SET_UVALUE关键字都可设置用户值。在组件用户值中存放数据后,用组件名即可获取该数据。顶层基底组件常被用来存储数据,因为它的组件名会通过事件结构体的top域传入每个事件处理器中。
状态变量
匿名结构体提供了一种可将组件程序中所有数据存储在单个变量中的好方法。这个结构体通常被称为状态结构体或信息结构体,因为其中存储了组件程序当前状态的所有信息,当然也包括事件处理器中所需的来自创建模块的数据。 plotinteractive
; Get tge window index of the draw widget and set it to be the ; current window.
Widget_control, draw, get_value=win_id West, win_id
; Create a structure of data for the application. State={data:data, win_id:win_id, linestyle:0}
; Draw a plot.
Plot, data, linestyle=0
State的初始内容包括一个用于保留绘图数据的data域,一个保存绘制组件窗口索引号的win_id域和一个保存当前线型的域。
下一步用一个指针指向该状态结构体并将指针名放入顶层基底的用户值中。 Plotinteractive
State = {data:data, win_id:win_id, linestyle:0}
; create a pointer to the state structure and put that pointer ;into the user value of the top-level base.
pstate = ptr_new(state, /no_copy)
Widget_control, tlb, set_uvalue=pstate
; Draw a plot.
plot, data, linestyle=0
; call xmanager to start the event handling procedure. xmanger, 'plotinteractive', tlb
7
; clean up the pointer reference before exiting the program. ptr_free, pstate end
顶层基底的用户值中现在含有一个指向数据结构体的指针。这是一个状态变量。注意到用户值只是包含有指针名,指针数据保存在堆内存中。还应注意到XMANAGER返回到此分块化的组件程序中时,就会清除该指针。最后一步防止了PLOTINTERACTIVE退出时造成的堆变量泄漏。
事件处理器
事件处理器只接收一个参数,就是事件结构体。事件结构体的任务是响应所发生的事件,必要时会使用事件结构体中的数据。现在需要为PLOTINTERACTIVE编写的事件处理器是线型下拉列表——PLOTINTERACTIVE_LS。在创建下拉列表组件时使用EVNET_PRO关键字为事件处理器定义名称。
事件处理器的编写规则
编写事件处理器应遵循如下规则:
1. 事件处理器应该是过程。只有当需用事件管理器将事件结构体传给另一个事件管理器时
它才是一个函数,这主要应用于复合组件。
2. 事件处理器只从XMANAGER处接收一个参数——事件结构体。
3. 若将事件处理器与定义例程放在同一个文件中,事件处理器应位于定义例程之前。在写
有多个过程或函数的文件中定义例程总是位于最后。
4. 事件处理器存储在单独文件中时,文件名应与事件处理器名相同,扩展名为.pro。 下面给出了PLOTINTERACTIVE_LS事件处理器的代码。我们可将此代码与PLOTINTERACTIVE写在同一文件中或放在单独文件中。 plotinteractive_ls
pro plotinteractive_ls, event
; Get the pointer to the state structure from the user value ; of the top_level base.
Widget_control, event.top, get_uvalue=pstate
; store the new linestyle in the state structure. (*pstate) . linestyle=event . index
; Replot the data in the draw widget.
plot, (*pstate) . data, linestyle=(*pstate) . linestyle end
首先,用WIDGET_CONTROL的GET_UVALUE关键字从顶层基底用户值中获取状态结构体的指针。此时PLOTINTERACTIVE_LS就可访问存于组件创建例程中的数据了。下一步,给出event.index后会在当前下拉列表中更新状态结构体的linestyle域。最后绘制数据,这也是通过状态结构体传入的,同时还传入了新的线型。
编译PLOTINTERACTIVE_LS并运行PLOTINTERACTIVE,测试一下线型下拉列表。
可扩展的组件程序
8
组件编程的目的之一就是创建能够很容易地进行扩展或修改的程序,我们常遇到的情形是刚发布了某个组件程序,用户就提出了更多的功能要求。PLOTINTERACTIVE就是一个可扩展的程序。要添加此类程序,执行下述步骤: 1. 在组件创建例程中的恰当位置添加组件。
2. 添加组件时一定要包含EVENT_PRO关键字,告诉IDL调用该组件的事件处理器名。 3. 在创建例程中添加所需变量。
4. 编写事件处理器响应来自该组件的事件,记得创建时使用EVENT_PRO关键字。 5. 为应用程序中的“Do”例程做添加所需内容。
使用“do”例程
使用“do”例程可简化组件编程中对应用程序的扩展工作。“do”例程是一个过程或函数,它将执行该组件应用程序核心的图形操作。PLOTINTERACTIVE的核心图形操作是调用PLOT过程绘制一幅线型图。PLOTINTERACTIVE中的“do”例程被命名为PLOTINTERACTIVE_DOPLOT,下面给出了此事件处理器的代码: Plotinteractive_doplot
pro plotinteractive_doplot, pstate
; Ensure that the data are being plotted in the draw window. west, (*pstate) . win_id
;plot the data.
plot, (*pstate) . data, linestyle=(*pstate) . linestyle End
指向组件数据的指针一般要传入“do”例程中,然后由“do”例程执行该应用程序的核心图形操作。本例中,执行了PLOT过程,其LINESTYLE关键字按状态结构体的linestyle域设置。若要为此例程添加其他绘制选项,这些绘制选项的值将被添加到状态结构体中,并会更新“do”例程使之接收这些关键字。我们应在事件处理器的最后调用“do”例程以修改图形的属性。
既然已经写出了“do”例程,修改PLOTINTERACTIVE_LS来调用它。 plotinteractive_is
Pro plotinteractive_ls, event
; Get the pointer to the state structure from the user value ; of the top-level base.
Widget_control, event . top, get_uvalue=
; store the new linestyle in the state structure. (*pstate).linestyle=event .index
;call the “Do” routine.
Plotinteractive_doplot,pstate end
9
组件创建例程中的PLOT指令也被替换为对“do”例程PLOTINTERACTIVE_DOPLOT的调用。
plotinteractive
; create a pointer to the state structure and put that pointer ; into the user value of the top-level base. pstate = ptr_new(state , /no_copy) widget control , tlb, setuvalue=pstate
; Draw a plot by calling the “do” routine. plotinteractive_doplot , pstate
; Call xmanager to start the event handling procedure. xmanager , 'plotinteractive' , tlb
注意到由于PLOTINTERACTIVE_DOPLOT需要传入状态指针作为参数,所以此指令必须放在组件创建例程定义了状态指针之后。
更多组件
有了“do”例程就拥有了所需的所有可扩展的组件。PLOTINTERACTIVE的下一操作是添加可使用户与图形进行交互的组件。我们先添加另一个下拉列表使用户可选择绘图符号: plotinteractive
; create a droplist to hold line style choices. linestyles=['Solid','Dotted','Dashed','Dash Dot',$ 'Dash Dot Dot','Long Dash']
linestylesyledrop = widget_droplist(controlbase, value=linestyles,$ title='Linestyle', event_pro='plotinteractive_ls' ) ;Define a hexagon for symbol 8.
usersym , [1.0, 0.5, -0.5, -1.0, -0.5, 0.5, 1.0],$ [0.0, 1.0, 1.0, 0.0, -1.0, -1.0, 0.0], /fill
; Make a droplist for the plot symbols .
symbols = ['None', ' plus' , 'Asterisk', 'period', 'Diamond'.$ ?Triangle?, 'Square', 'X' 'Hexagon']
symboldrop = widget_droplist(controlbase,value=symbols,$ title='symbol', event_pro='plotiteractive_sym') Realize the top-level base. widget_control, tlb, /realize
绘图符号下拉列表与线型下拉列表类似。事件发生时它会调用名为PLOTINTERACTIVE_SYM的事件处理器。下一步是在状态结构体中添加一个变量来保存当前符号。
plotinteractive
;Create a structure of data for the application.
state = {data:data, win_id:win_id,linestyle:0, psym:0}
10