ITPub博客

首页 > 应用开发 > IT综合 > PowerBuilder被忽略的的技术

PowerBuilder被忽略的的技术

原创 IT综合 作者:esen 时间:2007-11-25 09:43:54 0 删除 编辑

1.1 把Object看作类
Completed: 100 %
类(Class),仅仅在概念的层次上,是不能直接使用的,类只有在具体化(实例化)后才能使用,实例化的类我们称之为对象(Object);
在PB的帮助中常常出现Object这个词,例如介绍菜单时使用的名称是Menu Object 而不是 Menu Class。(我认为严格讲,在帮助中这样叫有不妥之处;Sybase可能会有它自己的说法,这些我们暂且不管),为了能合理解释Powerbuilder的面向对象的继承、多态、封装等特性,现在我们作如下假设:
我们暂且把Powerbuilder中的Object统一称作Class;
本次约定在接下来的几次讨论中都有效,如果我能记得我会每次都强调一下;
1.2 窗口的控件创建和释放
Completed: 100 %
控件是我们在开发中最常用的了,但是放在窗口上的控件何时被创建,何时有被销毁?
创建过程:
在windows中,控件被创建时必须指明用来承载控件的窗口对象,所以可以肯定的是窗口的创建过程肯定在所有准备使用它来承载的控件之前完成创建;
细心的话你会注意到控件的Constructor事件会在窗口的Open之前执行,事情就是这样子的,那是因为窗口Open事件并不是窗口的Constructor事件,窗口在完成自己的创建后,再把Control[]中的对象一一创建,在这时控件的Constructor事件会被触发,在所有的控件成功创建完毕后窗口才会触发Open事件。
用流程图表示:

释放过程:
释放过程正好跟创建顺序相反;
在窗口收到WM_CLOSE消息前会先收到WM_CLOSEQUERY消息,来给开发人员一个阻止WM_CLOSE发生的时机;在Powerbuilder中WM_CLOSE <= > Event Close() ,WM_CLOSEQUERY <= >Event CloseQuery()。
所以在窗口关闭时首先触发自己的CloseQuery事件,在CloseQuery同意后触发Close事件,之后才会把Control[]中的控件一一释放,这时各个控件的destructor事件会被触发。等所有的控件被释放后,再触发窗口的Destructor。完成释放过程。
用流程图表示:


注意:控件创建的先后顺序要看控件怎样存储在Control[]中,Powerbuilder无论是创建过程还是释放过程,都是从下表1开始循环创建的,可以查看源码知道各个控件的下标。一般规律是后放入的控件县创建。

1.3 谨慎使用Post
Completed: 100 %
谈到Post,就会想起来是用来向控件消息队列发送消息,通过Post发送的消息处理时间是不能确定的,这由操作系统是如何调度的,以及当时计算机的运行情况来决定。在PowerScript中Post的语法很灵活,表现形式多种多样。
Post以关键字的形式出现:
发送消息给以Powerbuilder事件形式出现的消息处理函数:this.post event open()
发送消息给以Powerbuilder函数形式出现的消息处理函数:this.Post classname( )(这种方法有别于 this.classname())
以关键字形式出现一般用在有参数的消息处理函数中;
并且使用Post是不能得到函数或事件的返回值的;

Post以函数形式出现
this.postevent( "XXXX ",{Word},{Long})
XXXXX 是函数名或事件名

Windows是消息驱动的操作系统,所以Windows下的Powerbuilder程序也不例外,Powerbuilder中使用Post()和Send()来完成消息的发送。熟悉SDK编成的可以直接使用Post和Send来发送消息,Send发出的消息会立即被处理,所以这个我们不讨论,但是Post是我们的导论范围.
通常情况下Post发送的消息在被对应的消息处理函数处理的时刻要比直接调用消息处理函数晚一些。这就会存在潜在危险,如果发出消息对应的消息处理函数所在的对象已经被释放,这时将可能会出现运行错误或者消息不被处理的危险;所以在使用了Post之后应该注意目标对象是否存在对象提前被释放的情况。特别需要注意的是在窗口的Close中使用Post,因为发出Post时没有办法知道消息何时会被处理。所以在Close中尽量不要使用Post。
这里顺便提到一点:
在控件的代码中,尤其是按钮的Clicked事件中,写了如下代码将会出现运行错误:
CLose(Parent)
this.text = " "
如果出现下面代码则不会出现运行错误:
CLose(Parent)
MessageBox( " ", " ")
如果出现下面代码则不会出现运行错误:
Post CLose(Parent)
MessageBox( " ", " ")
这些就是Post应用的一些临届情况。

1.4 避免类重名
Completed: 100 %
这里主要讨论两种情况的重名:
1、不同的PBL重名类
Powerbuilder允许在同一个Target中出现多个Powerbuiler Library (PBL文件),在不同的PBL中间出现相同的类别名称,就算是出现同一种类型的类,Powerbuiler不会作出判断,这样就存在了一个潜在的危险,PBL(或PBD)在Target中出现的先后顺序将会影响在运行时引用的对象;并且产生编译警告。所以这种一定要避免。
2、同一个PBL中出现重名
如果是相同类型的重名出现在同一个PBL中,Powerbuiler会作出提示,询问是否要覆盖。但如果出现同名的类不是同一种类型,Powerbuilder不会作出提示,这样就会带来许多不必要的麻烦。例如:在同一个PBL中出现了ww窗口,同时也出现了ww菜单,在ww窗口引用ww菜单时编译器会提示ww不是一个菜单类型。
综上所述第一种重名情况比较致命,不易被发现。第二种情况也应该坚决避免。好在Sybase在引导开发时,都提倡加前缀,这样就可以避免第二种问题。所以要强调的就是手误。

1.5 隐藏的全局类和局部类
Completed: 100 %
隐藏的全局类
打开Powerbuilder相关资料,很容易看到“Powerbuilder一个面向对象的开发工具”之类的话。绝大多数人写过的第一个程序就是在从window建立一个w_main,然后在Application中调用Open(w_main),这样一个程序就产生了
如果有面向对象编成的思想,那么这个时候应该有个疑问,我建立的w_main是个什么东西啊,类?对象?
众多书籍上会说建立一个window对象,那么这时Open(w_main)没有任何疑问,因为w_main是一个全局对象阿,作为参数传给Open当然没有任何疑问。可以你还可能会看到过一下代码:
w_main w_a,w_b
open(w_a)
open(w_b)
上面的代码不但不会产生编译错误,并且能够执行成功。执行后你会看到两个窗口。
你可能会以为对象也能作类别使用,难道Powerbuilder超越了面向对象?
这时应该怀疑的是w_main也是个类,并且有一个与w_main同名的全局w_main对象。只有这样能解释上面提到的两种代码。带着疑问打开w_main的源码:
global type ww from window
global ww ww
看到这里,应该可以证实我们之前的猜测是正确的,Powerbuilder确实创建了window的子类w_main,并且同时又声明与类(Class)同名的全局变量(全局对象 Object)w_main。除了structure(不是类)之外都所有出现在PBL中的类都具有这样的特性。
隐藏的局部类
更深入的编程尤其是写通用代码时,会需要使用ClassName()来判断对象的类别,然后根据类别做出判断。接下来我们仍然以一个具体的例子来展开讨论。
现在有一个窗口w_main,在窗口上方防置了一个按钮cb_1,通常我们写代码都是象下面一样的写法:
cb_1.Text = "XXXX "
cb_1.Enabled = False
String ls_clsNm
ls_clsNm = cb_1.ClassName()
这时就会发现ls_clsNm是“cb_1”,怎么不是CommandButton?难道ClassName()不是取的类别名?查一下帮助,可以证实ClassName()的功能,就是取类名。所以我们又要有猜测,是不是又有局部的类被 cb_1,由于cb_1可以直接用,所以也应该有一个与cb_1同名的局部对象存在。为了证实我们的想法,让我们打开源码分析:
cb_1 cb_1
type cb_1 from commandbutton within w_main
需要注意:如果控件是动态创建的,则ClassName()得到的将是CommandButton。
拨云见日,知道了这些隐藏的类和对象,至少可以肯定Poswebuilder面向对象的特性,我们在以后的编码中巧妙的使用这些类或对象。

1.6 全局Message对象
Completed: 100 %
首先可以肯定的是 Message是一个Message类型的全局变量(讲到这里我们也许可以理解PowerBuilder帮助中出现的Object是可以理解的,因为很多类都是以全局对象出现的)。这里我们不讨论Message类,而是在讨论Message对象。Message对象在程序启动时自动被创建。首先我们看一下Message类的成员变量:
----------------------------------
Handle Long //消息目标对象句柄
Number UnsignedInt//消息
WordParm Long //参数
LongParm Long //参数
ClassDefinition owerObject //暂不讨论
DoubleParm Double //用来传递数字
StringParm String //用来传第字符 ,字符在传递过程中是复制的
PowerObjectParm owerObject //用来传暂且递人意的Powerbuilder对象
Processed BooleanA boolean value set in the script for the user-defined event or the Other event. Values are:TRUE - The script processed the event; do not call the default window process (DefWindowProc) after the event has been processed.FALSE - (Default) Call DefWindowProc after the event has been processed.
ReturnValueLong牋When Message.Processed is true, specifies the value you want returned to Windows. This property is ignored when Message.Processed is false.
----------------------------------
从上面的成员变量可以看出,蓝色标出的最上面的四个成员变量和Send函数中的参数是一致的,帮助上也是这么声明的.如果Message向我们原来设想的那样,是用来在不同对象之间传递,那么这些成员就显得有些多余(相信有很多人在传递参数过程中会误用Message.Number,Message.WordParm,Message.LongParm,用这些来接收数字,结果都失败了),所以他一定有其他用途,那么它是如何和windows消息联系到一起的呢?这个暂且放一下,我们继续看下面的成员变量,绿色标出的是我们最为熟悉的参数了,我就不做详细解释,最后面的两个我对他的功能有些质疑,待会儿我会验证我的说法。
Message对象的第一种功能:
经过Debug你会发现,Powerbuilder程序受到任何一个消息以后Message内容都有变化,仔细观察就会发现变化的只是前四个参数,这样我们就可以确定这个全局对象就像一个梭子一样跟着每个消息在程序中传来传去,Powerbuilder在接收到消息后会先初始化Message对象,然后再把消息继续传递。这种特性对于自定义消息也同样有效。
我上面提到,最后两个成员变量的功能我有质疑,原因是我在实际测试中发现,当我把Processed设置为False时,ReturnValue仍然会起作用。还有,事件中的返回值回覆盖ReturnValue中的值,即便是使Processed为True。这个有兴趣可以做一个测试看看。
注意接收Message的返回值需要使用函数Send,而不能使用Post。
在这种情况下,DoubleParm、StringParm、PowerObjectParm是不会被修改的,这个是肯定的,要不然我们就会因消息过多而不能使用燤essage传递参数了。
Message对象的第二种功能:
这种功能也是Powerbuilder开发人员常用的功能。
例如OpenWithParm(w_a, "ACB ")
在w_a的窗口中可以使用Message.StringParm来得到ABC,
可是我们在实际的工作中,尤其在经过多次封装后的窗口中会发现得到的Message不是我们需要的,可能是一个空的(不是非法),也许有了其他的内容,到底何时把全局Message对象重置又何时把Message对象填充?
问题已经引出,这就是我们要讨论的内容:
我们可以肯定:
OpenWithParm
OpenSheetWithParm
OpenUserObjectWithParm
但是会不会清空我们可以作一些测试来验证:
Open
OpenSheet
OpenUserObject
经过验证发现他们都可以把Message对象清空的;
目前我只发现这些。
所以可以有结论,当调用以上六个函数中任意一个时,就会把Message重置。
理清楚这些,就很容易把Message被偷天换日的情况彻底杜绝。
需要注意:第二种功能中只会影响到DoubleParm、StringParm、PowerObjectParm三个参数。
综合来讲Message分为了两个部分,一部分用在系统传递消息时,一部分用在开发程序中传递参数。

1.7 关于菜单编程
Completed: 75 %
通用菜单
我们不需要解释什么叫菜单,所以就直接不如正题,在实际的开发过程中菜单是必不可缺的,有时为了方便会把所有弹出式菜单都坐在一个类中,需要时用那个弹那个。有时也会作一些通用菜单作为公用组件使用。
下面列出我们常见的菜单编程方法来引出这个问题:
• 调用指定窗口对象的函数:
w_main.relogon()
• 直接调用调用全局函数:
gf_relogon()
• 使用Parentwindow动态调用:
Parentwindow.Dynamic relogon()
这几种做法的灵活性从上而下,可是这些做法都不足以保证灵活性,并且业务处理代码相对分散,并且如果发菜单放到没有定义相对应的函数的窗口中会出现运行错误。这种也是通用菜单不应该出现的错误;
受系统菜单的启发

为了解决代码分散,容易出现运行错误等问题,我们采用Windows的消息路有机制来完成菜单的功能。
具体做法:
1、可以定义好一些全局常量,如:
CONSTANT UINT WM_USER = 1024
CONSTANT UINT WM_M1 = WM_USER + 1
CONSTANT UINT WM_M2 = WM_USER + 2
CONSTANT UINT WM_M3 = WM_USER + 3
为了跟windows消息区别,一定要在WM_USER上增加。
在需要处里该消息的窗口上写好处理代码就行了:
Choose Case Message.number
Case WM_M1
MEssageBox( " ", 'wm_m1 ')
Return 1
Case WM_M2
MEssageBox( " ", 'wm_m2 ')
Return 1
Case WM_M3
MessageBox( " ", 'wm_m3 ')
End Choose
这样只要会发送这样消息的过来,就会自动处理。最大限度的让菜单代码灵活。
弹出式菜单
在PB帮助中这样写道
The coordinates you specify for PopMenu are relative to the active window. In an MDI
application, the coordinates are relative to the frame window, which is the active window.
To display a menu at the cursor position, call PointerX and PointerY for the active window
(the frame window in an MDI application) to get the coordinates of the cursor. (See the
examples.)
右键菜单弹出时系统管理的,所以需要屏幕坐标才能准确;
所以怎样能够快速准确的找到父窗口对象就成了解决问题的关键。

未解决问题
菜单动态禁用的代码可能会比较分散,不便于管理;如果Powerbuilder能够支持 ON_UPDATE_COMMAND_UI这样的功能就好处理了。

1.8 SQLCA对象
Completed: 100 %
SQLCA是Powerbuilde应用程序中 ,Transobject类别的一个全局对象,负责与数据库通讯,一般的用法我相信各位都在熟悉不过,在这里主要讨论一下存储过程的用法;
自己定义用户对象Transobject 的子类My_Trans;然后再定义外部函数的地方打开右键菜单。使用如下菜单项可以在这里定义存储过程和函数;把需要的存储过程都在这里做声名;

然后把全局对象SQLCA的类改称My_Trans,这样就可以像使用SQLCA的一般方法一样来使用存储过程;

1.9 双向第归,事半功倍
Completed: 100 %
想法是在该面试人员的PB答卷中被激发的,答卷最后一题是这样的:
运用第归算法写一个函数实现1到100的累加。
在这份答卷中是这样写的
Long uf_add(long al_Start, long al_End)
{
if al_Start > al_End then Return 0
if al_Start = al_End then Return al_End //或者 al_End
Return al_Start + uf_add(al_Start + 1, al_End)
}
初一看以为是的错误的算法,当即我给了0分。
给分后心理已知不踏实,便仔细看了一下,才发现这是个正确的算法,算法很简练,只是在写法上有别于我下面给出的算法:
Long add1 (Long aparm)
{
if aparm = 1 Then Return 1
Return aparm + add1(aParm - 1)
}
单从形式上看这两种写法,后者可能更简单些;
随后我把这两种算法都上级做了测试,结果都是5050,第归次数都是100次。
在我准备删除这些测试函数时,试卷上的函数突然给了我启发:al_Start 能加 , 那么al_End为和不能减,两端都向中间靠拢,这样第归次数就可以减少一倍,再看看试卷上函数的第归结束条件,不正好满足这样的要求吗?于是便有了下面的经典(我自封的)第归算法:
Long add2 (Long a1, Long a2);
{
if a1 > a2 then return 0
if a1 = a2 then return a1
return a1 + add2(a1+ 1, a2 - 1) + a2
}
经测试结果为5050,第归次数为51;
虽然这种算法在我们的编码中可能很少用到,但是这种“双向第归”的方法却可以让效率按倍数提升;

1.10 Tracing
Completed: 100 %
使用如图所示的Profiling,然后在程序运行的时候可以把每个函数的运行时间和次序打印到文件中。
(转帖)

[@more@]

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/36101/viewspace-984558/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2008-03-17

  • 博文量
    26
  • 访问量
    18670