分享

PB窗口使用技巧之窗口之间的协作

 日尼禾称 2016-12-08

分类: PowerBuilder - PB基础入门

在打开和关闭窗口时进行数据传递
   在应用程序中,有时一个任务要分解成几个部分,由多个窗口来协同完成,这就需要这些窗口之间能够很好地配合。例如,在检索大批量数据时,可以提供一个小窗口让用户随时停止数据检索,并能在该小窗口中显示检索的进度。如果在一个窗口中显示关于职工的简短描述信息,用户双击某职工数据时可以显示该职工的详细情况,等等,这些例子都涉及窗口之间的协作。本节介绍有关的函数和技巧。
窗口之间的协作有以下几种可能情况:
l  互相补充性的协作:从窗口提供比主窗口更加详细的信息,是对主窗口信息的补充。
l  控制性的协作:从窗口控制主窗口的进度,通常在主窗口完成需要花费较长时间的操作时提供具有控制功能的从窗口。
 l  地位平等的协作:一个任务分割成几部分,不同部分在不同的窗口中完成。在执行过程中有可能需要参考各个任务的中间结果,所以它们之间应该能够交互。
上述这些情况在实际应用软件开发过程中都可能遇到,下面分别以典型例子来介绍。
一、在打开和关闭窗口时进行数据传递
窗口打开和关闭时进行数据的传递是一种基本的并且非常重要的窗口协作手段。可以使用两个函数和一个对象来完成这些工作。函数OpenWithParm和CloseWithReturn分别用来带参数打开窗口和带参数关闭窗口,这两个函数传递的参数使用对象Message的成员变量保存。使用它们可以构建多种形式的窗口协作。
      函数OpenWithParm具有Open函数的功能,并且还能传递参数给要打开的窗口。该函数的语法如下:
OpenWithParm ( windowvar, parameter {, parent } )
其中windowvar是要打开的窗口名称,可以是Window画板中定义的窗口,也可以是脚本中定义的窗口变量;parameter是要传递的参数,只能是String,Numeric或者PowerObject类型,该参数根据类型保存在Message的成员变量中传递给要打开的窗口;parent是一个已经打开的窗口名称,该窗口要成为windowvar窗口的父窗口。函数执行成功返回1,否则返回-1。如果有参数为NULL则返回NULL。下面是以传递参数方式打开窗口的一些例子。
      下面脚本打开窗口w_employee,并将“James NewTon”传递给该窗口:
        OpenWithParm(w_employee, 'James NewTon')
下面脚本在数据窗口的DoubleClicked事件中,把用户点击的数据行中的职工身份证号传递给w_detail窗口:
        String ls_no
        If row < 1 Then Return                 //用户点击的不是数据行
        ls_no = GetItemString(row, 'no') //获取身份证号
        If Not IsNull(Ls_no) And Len(ls_no) > 0 Then
        OpenWithParm(w_detail,ls_no)
End If
上面的脚本首先判断用户点击的是否有效数据行,使用DoubleClicked事件中的参数row是否大于0来判断,如果大于0则用户点击了有效的数据行。然后读取用户点击数据行中职工的身份证号,并将身份证号传递给w_detail窗口。在窗口w_detail中可以检索身份证号等于该参数的职工详细信息,这需要读取传递过来的参数。
      对象Message是一个结构类型的全局变量,并有很多的成员变量。在传递参数时有三个成员变量用来读取传递的数据,它们是:
l  Message.DoubleParm:用来传递Numeric类型的数据。
l  Message.PowerObjectParm:用来传递PowerObject对象类型的数据,像数据窗口、按键、列表框和用户自定义的结构等都可以使用该变量进行传递。
l  Message.StringParm:用来传递String类型的数据。
当使用函数OpenWithParm打开窗口后,应该在进行其他操作之前首先读取传递过来的参数,以免其他操作修改Message中的成员变量。例如,上述的双击数据窗口打开窗口w_detail后,在该窗口的w_detail中编写如下脚本:
        String ls_no
        ls_no = Mesasge.StringParm                //读取传过来的参数
        dw_1.SetTransObject(SQLCA)
        dw_1.Retrieve(ls_no)      //以ls_no为参数检索数据
在前面介绍设置MDI窗口中的工具条时,曾提到在调用通用工具条设置模块时,在菜单项的Clicked事件中使用函数OpenWithParm(parentwindow)把当前MDI窗口作为参数传递。在通用工具条设置模块的打开事件中编写如下脚本:
        iw_window = Message.PowerObjectParm
        …     //其他处理
其中,iw_window为Window类型的实例变量,因为在该窗口的很多地方用到iw_window,所以应该定义成实例变量。
函数CloseWithReturn的作用是关闭指定的窗口,并且返回参数。该函数的语法格式是:
      CloseWithReturn ( windowname, Returnvalue )
其中,windowname是要关闭的窗口的名称,一般是脚本所在的窗口的名称;Returnvalue是要返回的数值,和上述函数OpenWithParm的完全相同。函数正确执行返回1,否则返回-1,当有参数为NULL时返回NULL。
需要注意的是:只要是response类型的窗口,使用该函数就能有效地传递参数;该窗口不一定非得是用OpenWithParm打开的。在打开该response的窗口中可以读取传递过来的参数。例如,下面的例子中,窗口w_data中有按钮“检索学生数据”、“检索教师数据”和一个数据窗口dw_1;当用户点击按钮“检索学生数据”或者“检索教师数据”时,弹出response类型的窗口w_parm;用户在该窗口中输入要检索的学生或教师的姓名,点击该窗口w_parm上的“确定”按钮,然后返回到w_data窗口中,以用户输入的参数来检索数据。
在窗口w_data的按钮“检索学生数据”的Clicked事件中编写如下脚本:
        String ls_name
        OpenWithParm(w_parm, 'student')  //打开窗口w_parm
        Ls_name = Message.StringParm      //此处在关闭w_parm后继续执行
        If Len(ls_name) > 0 Then Dw_1.Retrieve(ls_name)
在按钮“检索教师数据”的Clicked事件中编写如下脚本:
      String ls_name                    
        OpenWithParm(w_parm, 'teacher')   //打开窗口w_parm
        Ls_name = Message.StringParm                //此处在关闭w_parm后继续执行
        If Len(ls_name) > 0 Then Dw_1.Retrieve(ls_name)
在窗口w_parm的Open事件中编写脚本:
        If message.StringParm = 'student' Then
                 st_1.text = '请输入要查找的学生姓名:'
        Else
                 st_1.text = '请输入要查找的教师姓名:'
        End If
在w_parm窗口的“确定”按钮的Clicked事件中编写如下脚本:
        If Len(Trim(sle_1.text)) <= 0 Then
                  Beep(2)
                 MessageBox('提示', '请在编辑框中输入内容!',STopSign!)
        Else
                 CloseWithReturn(Parent,sle_1.Text)
        End If
在w_parm窗口的“取消“按钮的Clicked事件中编写如下脚本:
     CloseWithReturn(Parent, ' ')
总之,使用CloseWithReturn函数时一定要注意,只有被关闭的窗口是response类型才能有效地获取返回参数。
另外需要说明的一点是,某些情况下需要直接修改Message成员变量的取值才能正确返回值。比如,在一个数据窗口中,经常需要在窗口的CloseQuery事件中判断数据是否已经修改;如果修改,则直接保存,这时返回是否保存过数据的相应标记。在CloseQuery事件中使用函数CloseWithReturn不能正确返回值,只好直接修改Message成员变量的取值了。


不同窗口之间的变量或函数调用
使用窗口名加界定符再加变量的形式可以应用其他窗口中的变量或者函数。例如,要想在窗口w_parm中引用窗口w_data中的实例变量is_title,可以使用w_data.is_title来引用。引用变量既可以使用其他的取值,也可以给它赋值,只要变量的定义允许这些操作(具体参见前面有关章节的介绍)。
实际上,这在程序设计中并不提倡。模块之间互相联结的程度称为耦合,程序设计提倡创建低耦合的模块,这样模块之间的接口才会简单,这样的程序才易于维护。不同窗口之间直接引用变量是最高程度的耦合(称为内容耦合),是应该尽量避免的一种耦合。
在不同窗口之间访问窗口函数也使用相同的形式,同样也是不提倡的。
虽然不提倡,但某些情况下编程还是很方便的。比如,在数据窗口进行数据检索时,提供给用户一个小窗口显示检索进度,并允许用户随时取消检索。假设当窗口w_data中的数据窗口dw_1进行检索时弹出小窗口w_cancel,其编程过程是:
(1)在窗口w_data中定义实例变量:
boolean ib_cancel_open
(2)在窗口w_data的Open事件中:
dw_1.SetTransObject(SQLCA)
(3)在窗口w_data的数据窗口控件dw_1的RetrieveStart事件中:
ib_cancet_open = true            //表示窗口w_cancel已经打开
Open(w_cancel)
(4)在窗口w_data的数据窗口控件dw_1的RetrieveRow事件中:
This.ScrollNextRow( )         //滚动到新检索的数据行上  
If IsValid(w_cancel) Then             //如果w_cancel没有关闭
                 //在w_cancel窗口中的st_count静态文本控件上显示已经检索到的记录数
        w_cancel.st_count.Text = String(This.RowCount( ))
End If
If ib_cancel_open = False Then Return 1                 //停止检索
(5)在窗口w_data的数据窗口控件dw_1的RetrieveEnd事件中:
If ib_cancel_open Then      
        ib_cancel_open = False
End If
Close(w_cancel)
(6)在窗口w_cancel的按钮“取消”的Clicked事件中:
Parent.ParentWindow( ).TriggerEvent('cancel_requested')
(7)在窗口w_data的自定义事件cancel_requested中:
ib_cancel_open = false
在上面这个程序中,当窗口w_data中的数据窗口开始检索时,小窗口w_cancel显示并不断刷新检索到的记录数,在w_cancel中用户随时可以点击“取消”按钮终止检索。该程序中,在w_data的数据窗口dw_1的retrieverow事件中直接修改窗口w_cancel中的st_count控件的内容,所以这两个窗口属于内容耦合。

信号灯概念的使用
在多进程的应用程序中,进程之间的协调关系特别严格。可以使用信号灯来进行不同模块间的同步或互斥,而信号灯一般用全局变量来实现。程序中的信号灯模拟现实世界中进行交通管理的交通灯,并提供进程之间同步或互斥访问资源。下面是一个典型信号灯同步程序的逻辑结构:
首先规定对信号量s的两种操作,P操作和V操作。下面是P(s)操作的步骤:
a.s=s-1。
b.若s>=0,则调用P(s)的进程继续执行。
c.若s<0,则调用P(s)的进程被阻塞,并把它插入到等待信号量s的阻塞队列中。
V操作和P操作的过程相反,V(s)的步骤如下:
a.s=s+1。
b.若s>0,则调用V(s)的进程继续执行。
c.若s<=0,从等待信号量s的阻塞队列中唤醒一个进程,然后调用V(s)的进程继续执行。
1.用信号量实现互斥
      首先定义s为两个进程互斥的公共信号量(一般在PowerBuilder中定义成全局整型变量),初值为1,表明某互斥性质的操作还没有开始。只要把需要互斥执行的操作放在P(s)和V(s)操作之间即可实现两个互斥操作的异步执行。例如,对进程p1和进程p2做如下安排即可实现互斥:
进程p1
进程p2
P(s)
P(s)
S1程序段
S2程序段
V(s)
V(s)
由于信号量s的初值为1,故第一个进程p1执行P操作后信号量为0,p1进程可以正常执行;如果这时进程p2开始执行,执行P操作后信号量为-1,所以进程p2只能处于等待状态,等到进程p1执行完程序段S1,执行V操作,且信号量变为0时,可以唤醒进程p2开始执行。所以两个互斥的程序段S1和S2不会被同时执行,两个进程p1和p2可以很好地协作。
      例如,一个客户端的两个进程要互斥地检索数据库,首先定义全局变量:
        Int gi_message = 1
在第一个窗口的相关事件中编写下面的脚本:
        gi_message = gi_message – 1
        Do While gi_message < 0
                  Yield()
        Loop
        dw_1.SetTransObject(SQLCA)
        dw_1.Retrieve()
        gi_message ++
在第二个窗口的相关事件中编写完全相同的脚本,这样这两个窗口就可以互斥地进行数据检索。
2.用信号量实现同步
信号量s的初值为0,两个进程之间的同步模型如下:
进程p1
进程p2
L1:P(s)
L2:V(s)
      设进程p1先到达L1点,当它执行P(s)时,使s=-1,于是进程p1进入阻塞状态并进入信号量s的阻塞队列;然后进程p2到达L2点,当它执行V(s)时,将s值变为0,于是唤醒进程p1,使其继续执行。由此可见,当进程p1到达L1点时,除非进程p2已过了L2点,否则进程p1就要暂时处于等待状态。这就是说,p1在L1点必须与进程p2同步。
      例如,可以改进CloseWithReturn,使非response类型的窗口也能正常传递参数。关键在于使用信号量让窗口1在打开窗口2之后进入等待状态,直到窗口2关闭并返回参数后才继续执行。首先定义两个全局类型的整型变量:
        Int gi_message=0      //信号量
        string gs_Return  //用于参数传递
在窗口1的适当事件中(比如一个按键的Clicked事件等)编写如下脚本:
String ls_Return
Open(w_test)                   //打开另一个窗口
gi_message = gi_message – 1             //信号量执行P操作
Do While gi_message < 0            
                 Yield()
Loop
ls_Return = gs_Return
MessageBox('提示',ls_Return)
在窗口w_test的“关闭”按钮的Clicked事件中编写如下脚本:
gs_Return = 'test'
Close(Parent)
在窗口w_test的Close事件中编写如下脚本:
        gi_message ++              //执行V操作

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多