1. 如何使PB窗口总在最上层
通过SetWindowPos函数吧窗口的显示层次修改为HWND_TOPMOST,就可以使指定窗口永远不会被其他窗口覆盖,该函数声明为:
Function Long SetWindowPos(Long hwnd, Long ord, Long x, Long y, Long dx, Long dy, Long uflag) Library “user32.dll”
参数1为要顶层显示的窗口句柄,参数2指定显示的层次,参数7为附加选项,其余参数指定窗口位置和大小,均可忽略。在窗口的Open或Activate事件中加入如下函数调用:
SetWindowPos(Handle(This),-1,0,0,0,0,3)
参数2取-1表示在最顶层显示窗口,取1表示在最底层显示;最后一个参数若取1,表示窗口大小保持不变,取2表示保持位置不变,因此,取3(=1+2)表示大小和位置均保持不变,取0表示将窗口的大小和位置改变为指定值。
2. 在PB中如何获得光盘盘符
通过GetDriveType函数可以获取驱动器(如:软驱、硬盘、光驱、网络映射驱动器等)的信息,该函数声明为:
Function Unit GetDriveTypeA(String drive) Library “kernel32.dll”
参数为一个盘符(如“C:”),返回值:1表示未知,2表示软驱,3表示本地硬盘,4表示网络驱动器,5表示光驱。因此如下代码可以获得光盘的盘符:
For I=Asc(‘D’) to Asc(‘Z’)
//列举所有可能的CDROM的驱动器
If GetDriveTypeA(Char(i)+”:”) = 5 Then
//若找到CDROM
Messagebox(“CDROM”,Char(i)+”:”)
//显示光盘盘符
Exit //退出循环
End if
Next
3. 在PB中如何获取目录信息
(1) 获取当前目录。通过GetCurrentDirectory函数可以获取当前目录,该函数
声明为:
Function Ulong GetCurrentDirectory(Ulong buflen,ref String dir)
Library “kernel32.dll”
参数2为接受当前目录的字符缓冲区,前面必须加ref表示地址引用;参数1用来指定字符缓冲区的长度。调用过程为:
String curdir
Curdir=Space(256)
//为字符缓冲区开辟内存空间
GetCurrentDirectory(256,curdir)
MessageBox(“当前路径”,curdir)
(2) 获取Windows及系统目录。要用到GetWindowsDirectory和GetSystemDirec
tory两个函数,须作如下声明:
Function Uint GetWindowsDirectoryA(ref String dir,Uint buflen)
Library kernel32.dll”
Function Uint GetSystemDirectoryA(ref String dir,Uint buflen)
Library "kernel32.dll”
4. 在PB中如何注销当前用户、关闭计算机、重启计算机
通过ExitWindowsEx函数可实现这三个功能,首先作如下声明:
Function Long ExitWindowsEx(Long uflag, Long nouse) Library "user32.dll”参数2保留不用,可取0;参数1取0可以注销当前用户,取1可以关闭计算机,取2可以重启计算机,其值再加4表示强制结束“未响应”的进程。
5. 控制由Run运行的程序(简称Run程序)
在PB程序设计中,可以用Run()来运行一些程序。但Run程序无法与PB主程序协调工作,若用户多次调用,就会启动Run程序的多个实例,主程序退出时,Run程序依然运行。可以用如下函数使它们协调工作:
Function Ulong FindWindowA(Ulong classname, String windowname)
Library "user32.dll”
Function Long SetParent(Long childwin, Long parentwin) Library "user32.dll”
(1) 使Run程序只运行一个实例
handle = FindWindowsA(nul,wtitle)
//查找Run程序是否已经运行,wtitle为Run程序的窗口标题
If handle > 0 Then Return
//若已经在运行就返回
Run(“c:\luhan.chm”)
//否则运行Run程序
(2) PB主程序退出时,Run程序也关闭
Handle = FindWindowA(nul,wtitle)
SetParent(handle,Handle(w_main))
//使Run程序窗口成为PB主程序的子窗口
6. 映射网络驱动器
若要在程序中把远程主机的资源映射到本地驱动器,可以用如下函数:
Function Long WNetAddConnectionA(String path, String pwd, String drv)
Library “mpr.dll”
如下代码可以把远程主机Alexander上的共享文件夹My Documents映射到本地的J盘:
WnetAddConnectionA(“\\ Alexander\ My Documents”,””,”J:”) //参数2为访问口令它的作用相当于在DOS提示符下执行:Net Use J: \\ Alexander\ My Documents
7. 显示或隐藏Windows的任务栏
要显示或隐藏任务栏,首先要得到它的窗口句柄。任务栏是一个特殊的窗口,它的窗口类为:Shell_TrayWnd,没有标题,故只能用FindWindowEx函数来取得它的句柄:
Function Long FindWindowEx(Long ph, Long ch, ref String cn, ref
String wn) Library “user32.dll”
Function Long ShowWindow(Long hWnd, Long nCmdShow) Library “user32.dll”
用ShowWindow来显示或隐藏窗口,其第二个参数为0表示隐藏,为5表示显示:
handle = FindWindowEx(0,0,” Shell_TrayWnd”,wn) //wn为空串
ShowWindow(handle,0) //隐藏任务栏
8. 如何将长文件名转换为短文件名
通过GetShortPathName函数可以把上文件名转换为8.3格式,其声明为:
Function Long GetShortPathNameA(String lf, ref String sf, Long
buflen)
Library “kernel32.dll”
参数1为长文件名,参数2为保存短文件名的缓冲区,参数3为缓冲区长度。例如:
GetShortPathNameA(“C:\My Document\Powerbuilder编程实践.Doc”,sf,256)
\
//sf = Spcace(256)
9. 如何在PB中实现延时
延时函数很有用,PB虽然没有提供,但可以通过Wind32的Sleep函数来扩展:
Function Long Sleep(Long ms) Library “kernel32.dll”
调用:Sleep(1000) //延时1秒
10. 如何在PB中播放音乐
PB没有提供任何多媒体函数,要播放音乐只能通过Win32 API的PlaySound来实现 :
Function Long PlaySound(String Filename, Int Mod, Int Flags) Library “ winmm.dll”
参数1为wav文件名,参数2必须取0,参数3取1表示后台播放,取8表示循环播放,因此取9(=1+8)表示在后台循环播放。
[align=right][color=#000066][此贴子已经被作者于2005-4-14 8:12:41编辑过][/color][/align]
[[i] 本帖最后由 kevin1982 于 2006-5-13 20:46 编辑 [/i]]
2005-4-13 19:21
kevin1982
窗口的常用函数
Open和Close两个函数是窗口中常用的两个函数,用来打开、关闭窗口。本节详细介绍这两个函数的用法。
3.6.1 Open函数
该函数用来打开其他的窗口,触发窗口的Open事件。该函数的语法格式比较多,最常用的格式如下:
Open ( Windowvar)
其中,Windowvar是一个Window类型的参数,是要打开的窗口名称。函数正确执行则返回1,否则返回-1,如果有参数为Null, 则返回Null。例如,下面语句打开主操作窗口w_main
Open(w_main)
如果对同一个窗口连续两次调用Open函数,该窗口并不被打开两次,只是在第二次调用Open函数时再次触发该窗口Activate事件。要避免这种情况,可以将上面的脚本做如下修改:
If Not IsValid(w_main) Then //如果窗口没有打开
Open(w_main) //则打开该窗口
Else //如果窗口已经打开
W_main.BringToTop = True //将该窗口显示在最顶层
End If
如果希望打开相同的窗口多次,则只能使用窗口实例,对实例可以打开多个。这些技术在窗口的使用技巧中将详细介绍。
在引用窗口的属性之前,应该首先确保窗口是打开的,可以使用上面的语句首先进行判断,否则将发生运行时错误,这样的错误在开发环境中编译完全可以通过。该函数的其他格式在本书后面有更多的介绍,也可以参见PowerBuilder的联机帮助文档。
3.6.2 Close函数
该函数的作用是关闭窗口,释放窗口及其上面的控件所占用的内存空间,窗口的CloseQuery事件和Close事件触发。窗口是否能够关闭还要取决于这两个事件的返回值。函数的语法如下:
Close ( Windowname )
其中,Windowname是要关闭的窗口名称,是一个Window类型的变量。函数执行成功返回1,否则返回-1,如果参数为Null,则返回Null。关闭已经关闭的窗口不会发生任何执行错误。关闭了的窗口就不能再应用其属性、控件属性等。
关于在关闭窗口时返回参数的方法,在窗口的使用技巧中有详细的介绍。
3.6.3 MessageBox函数
该函数可以打开一个小信息窗口,不仅可以多种方式给用户显示提示信息,还可以将用户的选择信息返回。经常在执行完一定操作后提示简短的结果信息或者在需要用户确认时询问用户并返回用户的选择。小信息窗口有标题、提示信息、图标、按钮等4个元素,可以通过不同的参数来决定显示哪些或者显示哪种样式。函数的语法格式稍微有些复杂,这里首先掌握最常用的一种方式:
MessageBox(title,text)
这种语法格式只能显示提示信息,并有一个确认按钮,不能让用户进行选择。通常在需要为用户提供信息时使用这种格式。其中,title为信息窗口标题,text为提示信息。例如语句MessageBox(“错误提示”,”数据保存错误!”),执行后显示如图3-9所示的信息窗口。这里的图标为默认图标。
该函数的完整语法是:
MessageBox ( title, text {, icon {, button {, default } } } )
可选参数icon、button分别用来表示使用哪种图标、提供哪些按钮,因为可用的图标和按钮组合都比较多,这两个参数的可用取值也比较复杂。表3-9是icon参数的可用值和对应的图标样式。
表3-9 icon参数取值
图 标
参 数
图 标
参 数
StopSign!
Information!
Exclamation!
Question!
无图标
None!
参数button的可用取值和返回值的意义如表3-10所示。
表3-10 button参数意义
参 数 取 值
显 示 样 式
返回值意义
OK!
显示Ok按钮,该取值为默认值
总返回1
OKCancel!
显示Ok和Cancel按钮
1-Ok,2-Cancel
YesNo!
显示Yes和No按钮
1-Yes,2-No
YesNoCancel!
显示Yes、No和Cancel三个按钮
1-Yes,2-No,3-Cancel
RetryCancel!
显示Retry和Cancel按钮
1-Retry,2-Cancel
AbortRetryIgnore!
显示Abort、Retry和Ignore三个按钮
1-Abort,2-Retry,3-Ignore
参数default是用来确定信息提示窗口上的默认按钮,该参数为一个数字类型,取值和表3-11中的含义相同。
在汉字的操作系统上,上面的英文字母和汉字之间的对应关系如表3-11所示。
表3-11 提示信息中英对照
英 文
汉 字
英 文
汉 字
Ok
确定
Cancel
取消
Yes
是
No
否
Retry
重试
Abort
放弃
Ignore
忽略
下面介绍一个例子。比如,用户关闭窗口而没有保存数据窗口中的数据时,可以使用如下脚本显示一个提示信息窗口,并根据用户的选择执行相关的处理。脚本如下:
int li_flag
li_flag = MessageBox("提示","是否保存数据?",Question!,YesNoCancel!,1)
Choose Case li_flag
Case 1 //用户选择Yes,保存数据
...... //处理语句
Case 2 //用户选择No,不保存
......//处理语句
Case 3 //用户选择了Cancel,不进行任何操作
......//处理语句
End Choose
上面讨论了窗口画板的用法、窗口的分类、窗口中控件的操作,在学习时应该多实践。更多的窗口使用技巧在后面将有详细介绍。
2005-4-13 19:22
kevin1982
[img]http://202.107.127.122/bbscs5/showimg?picdir=imgup/10/31/20050328/img_262134_1111978587401.gif[/img]
数据表操作(1)
数据表是数据库中一个非常重要的对象,是其他对象的基础。没有数据表,关键字、主键、索引等也就无从谈起。在数据库画板中可以显示数据库中的所有数据表(即使不是用PowerBuilder创建的表),创建数据表,修改表的定义等。
4.4.1 创建数据表
可以使用Database画板中的功能,在当前联接的数据库中创建数据表。在Database画板中有三种创建数据表的方式:完全新建、使用已存在的表创建新表、使用SQL语句。
新建一个数据表的操作步骤如下:
a. 单击PainterBar中图标为的Create New Table小功能图标,也可在Column视窗中单击鼠标右键弹出菜单中的New Table或在Object视窗中选中Tables后单击鼠标右键弹出菜单中的New Table。使用窗口菜单Object中的Insert->Table命令也可以在Columns视窗中打开定义表字段的界面,如图4-8所示。
b.
在Column视窗中为字段指定相关的信息,使用Tab按钮在字段不同的栏中切换。
c. 重复进行上面第二步,直到定义完所有的字段。
d. 该步骤是可选的。可以查看定义表的相应SQL语句。在Column视窗中单击鼠标右键,选择弹出菜单中的Pending Syntax,或者选择窗口菜单Object中的Pending Syntax,都可以显示对应的SQL语句。选择窗口菜单Object中的Pending Syntax使其前面的对钩取消,返回到字段定义。
e. 可以使用PainterBar中的Save按钮,也可用鼠标右键弹出菜单或者窗口菜单File中的Save或者Save as,都可以显示一个对话框,要求为新创建的数据表命名,指定拥有者(默认为DBA)和数据库名称。输入名称和拥有者后单击“确定”命令按钮,将提交对应的SQL语句在当前活动联接的数据库中创建该数据表并在Layout视窗中图形化显示刚创建的数据表。如果在保存了数据表之后、关闭之前又修改了数据表,进入到SQL语句时,SQL语句也发生了相应的变化,再次保存时将提交一个Drop Table SQL语句来删除前面保存的数据表,执行新的Create Table SQL语句来重新创建数据表。
f. 为字段指定扩展属性。这在后面将详细介绍。
使用已经存在的数据表创建另外一个数据表。前提是要创建的数据表和已经存在的数据表比较相似。首先打开已经存在的数据表,另存为新创建的表名,然后再修改表的定义,最后保存即可。如果在其他数据库中已经有表和要创建的表格式相同,可以使用Pipeline来创建,直接将数据表定义转入到当前的数据库中。详细的操作方法参见后面Pipeline的介绍。
使用SQL语句创建数据表是最快捷的方式。但是,PB在创建数据表的同时,要在系统数据表中添加关于数据表的信息,表和字段的扩展属性都保存在系统表中。所以,直接使用SQL语句创建系统表将使系统表中的信息不完整。使用窗口菜单Design下的Synch Extended Attributes命令可以纠正这些不完整。实际上,系统表的不完整没有太多的副作用,尤其在不使用系统表编写程序时。
直接在ISQL视窗中输入相应的SQL语句,然后运行就可以在当前联接的数据库中创建指定的表。具体语法在后面介绍SQL语句时将详细介绍,下面是一个在Adaptive Server Anywhere创建一个简单表的SQL语句:
Create table student
(no char(4),
sex char(5));
上面语句创建一个表名为student的数据表,该表只包含一个类型为char、长度为4、名称为no的字段和一个类型为char、长度为5、名称为sex的字段。
4.4.2 定义字段
定义数据表必须定义字段,不存在没有字段的数据表。字段的定义都是在Columns视窗中进行。在Columns视窗中,需要为字段指定相关的属性,不同的DBMS需要指定的信息有所不同。表4-3是字段各个属性的含义,更多的信息请参见DBMS的相关详细资料。
表4-3 各属性含义
属 性
意 义
Column name
指定字段的名称,必须为字段定义该属性
Data Type
字段保存的数据的类型,必须为该字段定义该属性
Width
字段保存数据的宽度,很多类型都有默认宽度
Dec
Numeric类型的字段需要指明该属性,用来指定显示的数字位数
Null
通过选择Yes或者No为字段指定是否可以有空值。如果选择Yes,则该字段可以为Null;如果选择了No,则录入数据时必须为该字段提供一个非空值
Default
在用户向数据窗口中插入空行数据时,自动将该值设置到该字段上。可以在下拉框中为字段选择默认值,也可以手动输入默认值。默认取值的详细资料可以参见DBMS中的相关文档
2005-4-13 19:23
kevin1982
[img]http://202.107.127.122/bbscs5/showimg?picdir=imgup/10/31/20050328/img_262135_1111978643932.gif[/img]
4.4 数据表操作(2)
4.4.3 指定表和字段的扩展属性
定义完表和字段后,可以在Details视窗中为表或字段定义更多的信息。可以在字段、表的扩展属性中使用汉字来为它们指定更多的信息。后面介绍PowerBuilder仓库应用时所举的例子中体现了这些汉字注释信息是非常重要的。
表的扩展属性包括在数据窗口中显示该数据表时,数据窗口中Header、Label、Data等的字体,表的注释(Comment)等。字段的扩展属性包括Header和Label的文字、显示样式、校验规则、编辑风格以及字段的注释信息。下面分别介绍这些扩展属性的设置。
1.表的扩展属性
设置表的扩展属性,可以在Database画板中按照如下步骤操作:
a. 在Object视窗中选用要定义扩展属性的数据表,使用鼠标右键弹出菜单中的Properties,也可以单击PainterBar上的Properties小功能图标,还可以用鼠标将该表拖动到Object Detail视窗中,这三种操作方法都可以在Details视窗中显示选中数据表的属性定义界面,如图4-9所示。
b. 在相关的属性页中设置相关的属性。在General属性页中可以设置表的注释信息,可以使用汉字。Data Font属性页用来定义从数据库中检索出来的数据的显示属性,在Database画板中操作数据或在数据窗口运行检索数据时。Heading Font属性页用来设置grid、tabular和n-up显示样式的数据窗口中Header的显示样式。Label Font属性页用来设置freeform显示样式的数据窗口中Label的显示样式。
c. 设置完扩展属性后,在Object Details视窗的空白处单击鼠标右键,选择弹出菜单中的Save Changes来保存属性。
2.字段的扩展属性
设置字段的扩展属性,在Database画板中按照如下步骤操作:
a. 在Object视窗或者Layout视窗中选中要定义扩展属性的字段,使用鼠标右键弹出菜单中的Properties或者单击PainterBar上的Properties小功能图标,或者直接将Object视窗中
要定义扩展属性的字段拖到Details视窗中。这些操作的作用都相同,都可以在Details视窗中打开字段扩展属性定义界面,如图4-10所示。
b. 在相应的属性页中设置相关的属性。General属性页用来设置字段的注释信息。Headers属性页用来设置freeform,显示样式的数据窗口中的Label,tabular,grid和n-up显示样式的数据窗口中的Header。Display属性页用来设置字段的显示样式,包括字段中数据显示的宽度、高度、是否显示为图片、是否添加一定的标记(比如,是否添加¥表示当前数据为人民币)。Validation属性页用来设置校验规则,在数据窗口中录入的数据必须能够通过该校验规则才能被接收。比如,可以规定用户在Salary(工资)字段上输入的数据应该在某个范围内,如果不在该范围内,则不被接收。Edit Style属性页用来设置数据窗口中编辑该字段时的编辑风格,比如可以使用Radio button让用户编辑该字段的数据。
c. 修改完字段的扩展属性,在Details视窗的空白处单击鼠标右键,使用弹出菜单中的Save Changes来保存属性。
另外,char类型的字段可以有更多的扩展属性,如指定大小写、是否显示为图片等都是在字段的扩展属性中设置。将字段显示为图片有时非常有用,比如,在产品介绍数据表中,字段product保存的是产品名称,同时在特定的目录中有和产品名称相同的图片文件,可以将该字段指定显示图片,数据窗口显示内容图文并茂,效果比只有文字的要好得多。
3.关于扩展属性
不管是字段还是数据表的扩展属性,都依赖于PowerBuilder,和具体的DBMS没有关系。因为PowerBuilder单独开辟了一些系统数据表,用来保存这些扩展信息,并且PB在设置或者修改扩展属性时自动维护这些系统表。关于系统表,在后面将有详细的介绍。
系统已经提供了一些默认的编辑风格、显示样式等扩展属性,可以在Details视窗中的相关下拉列表框中查看所有的这些设置。
4.4.4 替换表
数据表创建以后,有可能重新修改数据表的部分定义,然后替换。如何替换以及可以修改数据表的哪些部分取决于数据库管理系统。不管什么DBMS,一般都可以添加或者修改扩展属性,删除或者创建索引;一般都不能在数据表中间插入字段,不能替换已经存在的索引,不能增加非空的字段。还有部分DBMS允许添加可空的字段,允许增加或者减少已有字段的数据长度。数据库画板中可以进行这些操作,该画板是DBMS敏感的,也就是说,画板可以自动判断当前联接的DBMS允许进行哪些操作、不允许哪些操作。更详细的资料参见DBMS。
替换一个已有数据表的操作如下:
a. 在Objects视窗中,选中要修改的数据表,单击鼠标右键,选择弹出菜单中的Alter Table。这时,在Columns视窗中显示选中数据窗口的定义。
b. 在Columns视窗中修改表的定义。
c. 修改完毕保存。在Columns空白处单击鼠标右键,选择弹出菜单中的Save Table即可。
在修改字段定义时,Columns视窗中不同位置单击鼠标右键显示不同的弹出菜单,很多操作都可以在这些菜单中完成,如追加新字段、剪切字段、粘贴字段、删除字段等等。
当要修改的字段属性或者新添加的字段不允许时,只能先删除该表,然后再重新创建了。在删除之前首先备份表的语法,然后修改该语法为正确格式,再执行来创建表,这样的操作比较快速。备份语法的操作在4.4.7节有详细介绍。
2005-4-13 19:25
kevin1982
在脚本中嵌入SQL语句(1)
在PowerBuilder中对数据的操作大部分都是通过数据窗口来完成的。数据窗口控件提取数据的功能很强大,还有很多的相关函数。有时很多复杂工作只要编写非常少量的脚本就可以完成;有时有些工作数据窗口处理起来也不是很灵活;还有些工作用数据窗口处理就有些大材小用。这时,嵌入SQL语句可以很好地弥补数据窗口的这些不足,对数据的操作更加灵活。
SQL语句是标准化查询语言(Standard Query Language)的简称,是数据库中数据查询的既定工业标准,没有哪种关系型数据库不支持SQL语言的。学习好SQL语言不仅在PowerBuilder的运用中有很重要的作用,对于其他数据库软件的学习也有非常大的帮助。SQL语言有两种执行方式:自含式和内嵌式。自含式就是在数据库平台上以命令行的方式交互执行,每键入一条SQL命令都可以立即返回结果或者以命令文件的方式在数据库平台上批量处理;内嵌式就是嵌入到其他的语言中执行,比如,嵌入到Powers cript语言中,返回结果可以交给Powers cript处理,也可以在嵌入的SQL语句中处理Powers cript中的变量。
在学习SQL语言时,可以在数据库平台上随时试验每条SQL语句或者在Powers cript脚本中书写SQL语句,前面一种方式更方便。在嵌入到Powers cript的SQL语言中,联接数据库、断开和数据库的联接操作是必须的,检索、修改、插入、删除数据是基本的操作,事务的概念应该加以理解,较高级的应用是动态SQL,留待后面章节介绍。
5.10.1 事务
在SQL语句中有一个非常重要的概念:事务。C/S结构中数据的分布式处理和多用户对数据库操作的随机性,给数据的处理工作带来了复杂性,随时有可能发生一致性方面的冲突。比如,多个用户同时修改一个数据表时,如何保证每个用户的数据都能正确提交,如何保证用户正在做修改时的中间结果不被其他查询用户看到,如何保证两个关系紧密的操作能够同时正确提交等等。使用事务就可以解决这些问题。事务是这样一种机制,它确保多个SQL语句被当做单个工作单元来处理。这样做的好处是:
l 一致性:同时进行的查询和更新彼此不会发生冲突,其他用户不会看到发生了变化但尚未提交的数据(修改和提交当做一个事务)。
l 可恢复性:一旦系统故障,数据库会自动地完全恢复未完成的事务。
在PowerBuilder中,事务管理是通过事务对象和相关的SQL语句来完成的。默认的事务对象SQLCA是一个全局变量,可以随时使用。使用事务对象不仅可以解决上面所述的冲突,而且可以和多个数据库系统建立联接。在PowerBuilder中,和数据库的联接、修改数据、查询数据等等所有和数据库打交道的操作都是通过事务对象来完成的。正确地管理事务可以保证数据的完整性,当所做的工作全部完成得到确认之前,没有任何数据物理地写进数据库。
在PowerBuilder中,对事务的操作是这样进行的:先定义开始一个事务,然后对数据进行修改操作,这时如果提交(COMMIT),这些修改就永久地保存下来;如果回退(ROLLBACK),数据库管理系统将放弃所有修改,回到开始事务时的状态。这些操作都有相应的SQL语句,下面逐一介绍。
5.10.2 在PB中使用SQL语句的格式
在Powers cript中,随时可以使用SQL语句。一条SQL语句可以一行书写,也可以分多行书写,以分号“;”作为结束标记。除了静态游标定义语句不能放在循环语句中,其他的SQL语句可以放在Powers cript任意位置。在SQL语句中可以使用Powers cript中定义的变量(在变量前面加冒号“:”),也可以使用数据库系统的一些内置函数。每次Powers cript只能处理一条SQL返回结果,当SQL语句返回多行结果时,应该使用游标。可以将返回多个结果的情况理解成集合,简单变量只能保存单个数据。PowerBuilder不区分SQL语句的大小写,经常用大写来标识SQL关键字。下面是一段有SQL语句的例子:
Int li_salary
String ls_name
Ls_name = "张三"
SELECT salary INTO :li_salary FROM salarys
WHERE name=:ls_name;
MessageBox(ls_name,String(li_salary)
上面的例子选择名字为“张三”的职工的工资,将结果保存在变量li_salary中。如果在数据库中有多个名字为“张三”的职工,那么li_salary中保存的为最后一个取出的“张三”的工资数据并且事务对象产生错误信息,要正确处理每个“张三”的数据就应该使用游标了。关于游标在后面相关章节做详细介绍,现在只要清楚一点:返回多条记录不能使用简单的SQL语句。
5.10.3 联接和断开数据库
所有和数据库的操作,包括内嵌SQL、数据窗口、datastore、pipeline等等,都必须首先和数据库建立联接才能正常进行。联接数据库是使用相应的事务对象进行的。一个事务对象一次只能联接一个数据库,定义多个事务对象可以同时和多个数据库建立联接。在联接数据库之前,首先应该正确设置事务对象的参数。事务对象包括15个参数,5个用来返回操作的状态信息,其他10个用于设置和数据库联接时的参数。不是所有的这10个参数都必须设置,需要设置哪些联接参数和具体的数据库系统有关,简单的方法是参阅db profile属性对话框中的preview属性页中的内容。联接数据库的操作一般是在应用对象的open事件中,典型脚本如下:
// This s cript will read all the database values from PB.INI
// and store them in SQLCA.
SQLCA.DBMS =ProfileString("PB.INI","Database","DBMS", " ")
SQLCA.Database =ProfileString("PB.INI","Database","DataBase", " ")
SQLCA.LogID =ProfileString("PB.INI","Database","LogID", " ")
SQLCA.LogPass =ProfileString("PB.INI","Database","LogPassword", " ")
SQLCA.ServerName =ProfileString("PB.INI","Database","ServerName", " ")
SQLCA.UserID =ProfileString("PB.INI","Database","UserID", " ")
SQLCA.DBPass =ProfileString("PB.INI","Database","DatabasePassword", " ")
SQLCA.Lock =ProfileString("PB.INI","Database","Lock", " ")
SQLCA.DbParm =ProfileString("PB.INI","Database","DbParm", " ")
CONNECT; //和数据库建立联接
If SQLCA.SQLCODE <> 0 Then //没有正确建立联接
//正确联接情况下的处理语句
Else //正确建立
//错误信息处理语句
messageBox("错误","不能和数据库正确建立联接!~r~n错误代码:" &
+ String(SQLCA.SQLDbCode) + "~r~n错误信息:"&
+SQLCA.SQLErrText,StopSign!,
End If
其中,connect语句是事务处理语句,用来和数据库建立联接。可以通过事务对象成员SQLCODE的取值来判断是否正确联接,进而决定继续做相关的处理工作。
2005-4-13 19:28
kevin1982
5.10 在脚本中嵌入SQL语句(2)
5.10.4 选取单行数据
在嵌入到Powers cript的SQL语句中,SQL语句的返回结果必须赋给相应类型的变量,然后才能使用,这点和标准SQL的选择语句不同。格式是:
select 字段名集合 into :变量集合
from 表名
where 条件表达式
{using 事务对象名};
其中,字段名一定要在表中存在,并且没有blob类型字段(关于blob类型字段在后面章节介绍)。变量集合中的变量个数要和前面字段集合中字段的数目相等,对应的类型应该兼容,每个变量都是以冒号(“:”)开头。条件表达式由常数、Powers cript中的变量、字段、数据库内置函数等构成。使用事务对象是可选项,当省略时,PowerBuilder使用默认的全局事务对象SQLCA,否则使用指定的自定义事务对象。例如,下面是一个嵌入到Powers cript中的SQL语句例子:
String ls_name
Ls_name = sle_1.text //用户可以在单行编辑器sle_1中输入要查询的职工姓名
SELECT salary,name INTO :li_salary,:ls_name FROM salarys
WHERE name = :ls_name
USING SQLCA;
上面的语句从表salarys中选择名称为“张三”的工资、名称,并将结果保存在变量li_salary和ls_name中。但是,上面的语句可能遇到很多的问题。例如,表名或者字段名不存在(这种情况通常是在脚本正确提交后又删除了数据表或者修改了数据表而造成的),不存在符合条件的数据或者存在多条符合条件的数据。最后一种情况可以使用游标来处理,前面的两种情况都必须做相应的处理,至少不应该做有一条数据时同样的处理。现在的问题是,如何知道发生了这些情况?
5.10.5 判断事务对象的返回信息
在进行程序设计时,应该考虑到各种特殊情况下程序的处理。在输入数据正确时,能正确运行,输入数据错误时,运行就出现异常的程序就不理想。同样,在SQL语句执行时也会有各种情况,可以通过事务对象的返回信息来做相应的处理。在事务对象中有5个参数用来返回SQL语句的执行状态,如表5-13所示。
表5-13 SQLCA参数含义
参 数
类 型
意 义
SQLCode
Long
是否成功标志,有三个可能的取值:0—成功,100—无数据,-1—错误(关于错误的详细信息用参数SQLDBCode或SQLErrText来获取)
SQLDBCode
Long
数据库的错误代码
SQLErrText
String
数据库的错误信息
SQLNRows
Long
涉及到的行数(不同的DBMS有所不同)
SQLReturnData
String
DBMS指定的信息
其中,最常使用的是SQLCode,经常使用该参数判断操作是否正确,然后再决定如何做进一步的处理工作。下面是典型的常见代码:
Int li_salary //用来保存工资
String ls_name //用来保存职工姓名
Ls_name = "张三" //职工姓名赋初值
SELECT salary INTO :li_salary FROM salarys
WHERE name = :ls_name;
If SQLca.SQLcode = 0 Then //如果成功获取了数据
MessageBox(ls_name,String(li_salary) //显示该数据
Elseif SQLca.SQLcode = 100 Then //没有找到数据
MessageBox("提示", "没有找到职工张三的数据!",Information!)
Else //SQL语句执行错误
MessageBox("错误代码" + String(SQLca.SQLdbcode),SQLca.SQLerrtext)
End If
另外,在联接数据库的代码中使用了CONNECT语句之后,应该判断是否正确联接了数据库,正确联接,则打开主窗口(或口令校验窗口),否则出现错误提示窗口,然后关闭应用。代码如下:
…….. //其他设置事务对象参数的语句
SQLCA.Lock = ProfileString("PB.INI","Database","Lock", " ")
SQLCA.DbParm = ProfileString("PB.INI","Database","DbParm", " ")
CONNECT;
If SQLca.SQLcode <> 0 Then
Beep(2)
MessageBox("联接错误!","错误代码" + String(SQLca.SQLdbcode) + &
",错误信息:" + SQLca.SQLerrtxt + "~r~n按回车键退出系统…….. ")
Halt Close
Else
Open(w_main) //打开主操作窗口
End If
5.10.6 插入、删除和修改数据
插入、删除和修改数据是SQL语句中最基本的数据库操作语句。这里单独对其进行介绍。
1.插入数据
在数据库中插入一条数据使用INSERT语句,格式如下:
INSERT INTO 表名 (字段列表) VALUES (值列表) {USING 事务对象};
不同的字段使用逗号(“,”)分隔,并且不包含blob类型的字段;值列表中不同的值之间用逗号分隔,和字段列表中字段的类型对应兼容(最好类型相同),并且字符型和日期型取值用引号引起来。省略事务对象时使用默认的全局事务对象SQLCA。下面是一个向表中增加记录的SQL语句:
insert into dept (deptno,deptname,workers) values (:ls_deptno, "销售部",10);
该例子向表dept中插入一条记录,给该表中的字段deptno赋值为变量ls_deptno的取值,给字段deptname赋值为“销售部”,给字段workers赋值为10。可能该表中还包含其他的字段,但是一般情况下,这三个字段中最起码应该有主键,除非使用了Identify类型的字段,因为主键肯定是不允许为空的。如果该表就包含这三个字段,上面的SQL语句可以简写成:
insert into dept values (:ls_deptno, "销售部",10);
2.删除数据
SQL语句删除数据是以行为单位删除的,不能删除某行中某字段的数据。删除数据的SQL语句格式是:
DELETE FROM 表名 WHERE 条件表达式 {USING 事务对象};
删除游标中和当前数据对应的记录可以使用下面格式的语句:
DELETE FROM 表名 WHERE CURRENT OF 游标名称;
例如,下面的例子删除表dept中workers=10的所有记录:
delete from dept where workers=10;
再如,下面的例子是删除和光标中当前数据对应的记录:
delete from dept where current of dept_cur;
3.修改数据
使用修改语句可以修改数据表中的一条或多条记录中的一个或多个字段的取值。语法格式如下:
UPDATE 表名 SET 字段名=:变量名(或常数)[,字段名=:变量名(或常数)] WHERE 条件 {USING 事务对象};
修改游标中和当前数据对应的记录时可以使用下面格式的语句:
UPDATE 表名 SET 字段名=:变量名(或常数)[,字段名=:变量名(或常数)] WHERE CURRENT OF 游标名;
下面是一个修改数据的例子:
update dept set workers=100 where workers=10.
该例子修改表dept中所有workers=10的数据,将字段workers修改为100。
上面讲述的是SQL语句的一些基本的用法,像选取多行数据、动态SQL语句以及SQL语句较为详细的介绍留待后面章节。
2005-4-13 19:31
kevin1982
[img]http://202.107.127.122/bbscs5/showimg?picdir=imgup/10/31/20050328/img_262168_1111979642351.gif[/img]
数据缓冲区
在应用程序运行时,用户对数据窗口进行的操作情况是很复杂的,删除、插入、修改等操作有可能都会发生。记录哪些数据修改过、哪些数据需要删除、哪些数据是新增加的等等,都是很重要的工作,以便数据保存时能够正确修改数据库。这些工作都是由数据窗口缓冲区来完成的。
8.5.1 缓冲区
数据窗口在运行时要创建四个缓冲区,分别是主缓冲区、删除缓冲区、过滤缓冲区和原始缓冲区,这四个缓冲区各司其职、共同配合,来保证数据窗口对数据的正确处理。下面分别加以介绍。
1.主缓冲区
这是最重要的一个缓冲区,保存的是当前显示在数据窗口中的所有数据以及它们的修改状态。在保存数据时使用这些状态生成SQL语句。平常的大多数操作都是针对该缓冲区,函数中的缓冲区参数缺省也是该缓冲区。
2.删除缓冲区
该缓冲区保存所有删除了的数据。保存数据时,该缓冲区中的数据用来产生delete语句。在数据没有提交到数据库之前,可以从该缓冲区中将数据恢复出来。数据提交后,该缓冲区中的数据清空。
3.过滤缓冲区
该缓冲区中保存的是没有通过过滤规则的所有数据,这些数据在存储时同主缓存区的数据一起生成相应的insert或update语句。
4.原始缓冲区(original)
用来保存从数据库中检索到的初始值,在保存数据时用来生成where语句。
上面这四个缓冲区中,original缓冲区在脚本中很少使用,其他三个经常涉及到,很多数据窗口函数都需要指定对这三个缓冲区中的哪个进行操作,大多数情况下都使用缺省的缓冲区Primary!。这三个缓冲区,除了保存相应的数据外,都自动维护这些数据的修改标志,它们之间的协作也是自动维护的。例如,当使用过滤函数时,没有通过过滤规则的数据自动从Primary缓存区移送到Filter缓存区;执行删除操作时,数据自动从Primary缓存区移送到Delete缓存区。所有这些数据的移送无需脚本的干预,只管使用相应的函数即可。
8.5.2 应用实例
这四个缓冲区由系统自动维护,一些特殊情况下需要人工干预,特别是在需要数据恢复时。经常使用的方法是,专门建立一个数据窗口用来显示缓冲区的数据,提供一种鼠标拖动的方式来恢复数据。经常使用函数RowMove,它可以将数据从一个缓冲区移动到另外缓冲区中。该函数的语法是:
dwcontrol.RowsMove ( startrow, endrow, movebuffer, targetdw, beforerow, targetbuffer )
其中,dwcontrol是进行移动操作的源数据窗口;startrow和endrow是要移动数据的范围(包括这两个行号的数据);movebuffer指要从哪个缓存区移出数据,可以是Primary!、Delete!、Filter;targetdw是目标数据窗口控件名称;beforerow表示在目标数据窗口的哪一行之前插入移入的数据,如果该数值比目标数据窗口的行数大,则在最后插入移入的数据;targetbuffer是目标缓存区,取值同movebuffer一样。
下面使用该函数实现要创建的数据恢复。因为拖动技术还没有介绍,所以在数据窗口的单击事件中恢复数据。窗口界面如图8-4所示。窗口中有两个数据窗口,dw_cur和dw_del分别是当前操作的数据窗口和保存删除数据的数据窗口,单击“删除”按钮时删除dw_cur中当前的数据,并放置到dw_del中。该按钮下的脚本是:
If dw_cur.GetRow() > 0 Then
//如果有数据
//让用户确认是否真要删除数据
If MessageBox("删除","是否真要删除姓名为" + &
dw_cur.GetItemString(dw_cur.GetRow(),"name") + "的数据?",&
Question!,YesNo!,2) = 1 Then
//将数据从一个数据窗口移动到另一个数据窗口
dw_cur.RowsMove(dw_cur.GetRow(),dw_cur.GetRow(),Primary!,&
dw_del,dw_del.RowCount() + 1,Primary!)
st_1.text = "当前操作的数据:(现有" + string(dw_cur.RowCount()) + "条)"
st_2.text = "已经删除的数据:(删除了" + String(dw_del.RowCount()) + "条)"
End If
Else
Beep(1)
MessageBox("提示","请选择要删除的数据!")
End If
在“恢复”按钮上编写类似的脚本:
If dw_del.GetRow() > 0 Then //如果有删除了的数据
If MessageBox("恢复","是否真要恢复姓名为" + &
dw_del.GetItemString(dw_del.GetRow(),"name") + "的数据?",&
Question!,YesNo!,2) = 1 Then
dw_del.RowsMove(dw_del.GetRow(),dw_del.GetRow(),&
Primary!,dw_cur,dw_cur.RowCount() + 1,Primary!)
st_1.text = "当前操作的数据:(现有" + string(dw_cur.RowCount()) + "条)"
st_2.text = "已经删除的数据:(删除了" + String(dw_del.RowCount()) + "条)"
End If
Else
Beep(1)
MessageBox("提示","请先选择要恢复的数据!")
End If
该程序使用函数RowsMove在两个数据窗口之间移动数据。因为还没有介绍拖动技术,所有操作都以按钮来完成。如果改为鼠标拖动并设置漂亮的拖动图标,会设计出界面更友好、使用更方便的数据处理窗口。
上面介绍了人工干预缓冲区的一种情况,另外一种情况是直接修改缓冲区数据修改标识。使用函数dwcontrol.GetItemStatus ( row, column, Primary! )可以获取该缓冲区内指定单元的状态,当参数column为0时,表示读取整个列的修改状态。有以下状态。
l NotModified! :指定单元的数据和原始数据相同,没有修改过。
l DataModified!:指定单元的数据和原始数据不同,修改过。
l New!:该数据行是新增加的,但还没有在该行上输入数据。
l NewModified!:该数据行是新增加的,并且已经在该行上录入了数据。
这些修改标识都是由数据窗口自动维护的,一般情况下没有必要编写脚本修改这些标记,但并不是说就不能修改。PowerBuilder提供了函数SetItemStatus,它的语法是:
dwcontrol.SetItemStatus ( row, column, dwbuffer, status )
其中,row参数指定将要修改状态的行;column参数指定将要修改状态的列(可以是整型的列号,也可以是string类型的列名),当列号为0时表示要修改row指定的整行的状态;dwbuffer指定要修改哪个缓冲区(肯定不能是original),status为上面的四个取值中的一个,但不是任意的取值,因为有些状态不能用该函数设置成另外一种状态,必须是能够转换的状态。表8-1列出了能够转换的状态。
表8-1 状态转换
New!
NewModified!
DataModified!
NotModified!
New!
Yes
Yes
No
NewModified!
No
Yes
New
DataModified!
NewModified!
Yes
Yes
NotModified!
Yes
Yes
Yes
表中的Yes表示可以使用SetItemStatus进行该状态设置,No表示不会产生预期的状态,如果标明了某个特定的状态,则说明是新的状态,而不是期望的状态。例如,数据窗口dw_1的第1行第1列的当前状态为DataModified!,使用函数dw_1.SetItemStatus(1,1,New!)后,第1行第1列的状态改变为NewModified!。同样对于该数据窗口dw_1,如果使用函数dw_1.SetItemStatus(1,1,NotModified!),则会将其状态改变为NotModified!。当从一种状态不允许转变到另一种状态时,可以修改成其他一个中间状态,然后再进行一次转换。例如,要从new!改成NotModified,应该首先转换到DataModified!。
关于使用脚本干预缓冲区状态的应用在后面再做介绍。
2005-4-13 19:32
kevin1982
编 辑 控 件
和数据窗口控件打交道都是通过编辑控件进行的。在数据窗口中录入数据、修改数据,实际上是在修改单元上的编辑控件中的内容。当编辑控件移动到另外单元上之前,要对编辑控件中的数据进行校验,如果能通过字段的校验规则,就保存到字段中。编辑控件是可以移动的,需要编辑哪个单元,编辑控件就移动到哪个单元上,接受输入或者修改。
对于熟悉Microsoft Excel的人来说,编辑控件可以看成是Excel网格中的单元格。当用户在一个单元格中输入一个公式,它不会被接受和计算,直到用户移到其他单元格时才进行公式正确性检查,如果错误则不能移动到其他单元格中。数据窗口控件使用了同样的概念。
编辑控件在没有离开当前字段时,用户录入或者修改的数据就不会被保存到字段中,如何确保在数据窗口失去焦点时,最后位置上编辑框中的内容不被遗漏呢?前面介绍了在数据窗口的LoseFocus事件中编写如下脚本:
this.AcceptText()
这个方法在很多情况下能解决该问题。美中不足的是,如果编辑框中的内容不能通过字段的校验规则,有时会显示两次错误信息窗口。造成两个错误信息窗口,往往是在选择其他单元时发生,在数据窗口失去焦点之前(如单击“保存”按钮等)只会显示一个错误信息窗口。用户选择其他单元时,编辑框中的数据开始进行校验,不能通过校验规则,显示校验错误信息,在显示校验错误信息时数据窗口失去焦点,触发数据窗口的LoseFocus事件执行AcceptText函数,又一次显示校验错误窗口。
所以,显示两次校验错误窗口的原因是LoseFocus事件中的AcceptText函数造成的。可以想法让该函数在这种情况下不执行。为此定义一个实例变量:
Boolean ib_accept = True
该变量用来表示是否正确通过了校验,然后将LoseFocus中的脚本改成下面的脚本:
If ib_accept Then
This.AcceptText()
Else
ib_accept = true
End If
在数据窗口的ItemError事件中编写脚本:
ib_accept = False
这样就可以解决显示两个校验错误信息窗口了,并且不会遗漏最后一个编辑框中的内容。
当在某个单元中修改或者录入数据后,编辑框要离开时,要进行4个校验步骤,前面两个步骤由PowerBuilder完成,后面两个由开发者编写。
(1)看数据和编辑前相比是否发生了改变,没有改变则不做任何处理。
(2)检查测试此值是否违反了任何一个在字段上定义的有效性校验规则,违反则拒绝接受此值,并触发ItemError事件。
(3)检查此值是否与编辑前真的不同,没有改变则终止有效性校验。
(4)检查开发人员在ItemChanged事件中编写的脚本。具体的发生情况取决于分配给被称为动作代码的值。
2005-4-13 19:34
kevin1982
[img]http://202.107.127.122/bbscs5/showimg?picdir=imgup/10/31/20050328/img_262183_1111980098261.gif[/img]
查询和排序(1)
查询和排序是数据窗口操作必不可少的。尤其查询,它的灵活性至关重要,一般在一个应用软件开发中要花费很大的时间比例来考虑和编写查询。查询可以使用函数、修改数据窗口对象的模式、使用SQL语句、使用带参数的Retrieve等方法来实现,本节介绍一些使用函数实现的查询方法,这是最常使用的方法。在后面的章节中将介绍如何开发一个通用的查询系统,之后再介绍一下数据的排序问题。
实现查询最简单的方法是过滤,使用函数SetFilter和Filter。这两个函数必须配对使用,首先使用函数SetFilter设置过滤规则,然后用函数Filter进行过滤。过滤规则是boolean类型的表达式,能够使用过滤规则的,将表达式为True的数据行显示在数据窗口中,使其为False的数据行被移送到数据窗口的Filter!缓存区。在设计数据窗口对象时也可以定义过滤规则,使用这两个函数可以根据需要来动态改变过滤规则。在设计时,指定了过滤规则的数据窗口可以使用这两个函数再进行过滤。每次过滤都是对数据窗口的Original!缓存区进行的,而不是在前一次过滤出来的数据基础上再次过滤。函数SetFilter的语法是:
dwcontrol.SetFilter ( format )
其中,dwcontrol是要进行过滤的数据窗口控件名称。format是过滤规则,string类型。过滤规则是由字段、常量、运算符、函数构成的boolean表达式。需要取消过滤规则显示所有的数据时,可以指定过滤规则为空(“”),如果让用户可以随便指定过滤规则,则可以使用Null的过滤表达式,这时PowerBuilder提供一个和数据窗口设计时相同的过滤规则指定窗口,如图8-5所示。更详细的内容请参照前面排序、过滤和分组一节关于过滤的内容。该函数正确执行返回1,否则返回-1。执行完后,数据窗口中显示的数据没有发生变化,只有当执行了Filter函数后才按照刚刚指定的过滤规则显示数据。该函数的语法是:
dwcontrol.Filter ( )
dwcontrol是和SetFilter中同名的数据窗口控件名称。该函数执行正确则返回1,否则返回-1,如果dwcontrol为Null则返回Null。
下面是一个完整的简单过滤脚本:
dw_1.SetFilter("name='"+Trim(sle_1.text)+"'") //用户在sle_1中输入要查找的姓名
dw_1.Filter()
下面的脚本取消使用过滤规则:
dw_1.SetFilter("")
dw_1.Filter()
下面的脚本可以让用户自己指定过滤规则:
String ls_filter
SetNull(ls_filter)
Dw_1.SetFilter(ls_filter)
Dw_1.Filter()
关于过滤的这两个函数用法都比较简单,关键是如何构造界面,让用户能够灵活地指定查询规则。因为用户大多数都是非计算机专业人士,要求他们掌握很多的语法规则和函数的使用方法显然是不合理的,界面应该屏蔽这些问题,让用户有轻松、自由、灵活的感觉。增强查询功能的灵活性仅仅依赖使用取值为Null的过滤规则肯定是行不通的,一方面该窗口提供的大量函数会让绝大多数的用户感觉麻烦;另一方面使用用户根本没有机会了解的字段名称作为查询条件,而字段的命名又有很大的随意性,哪个字段名和用于显示的数据窗口中的汉字列相对应,有时连开发人员都不能马上搞清楚,所以使用这两个函数给应用系统提供查询时要花费一定的精力来构造更好的界面,不要寄希望于PowerBuilder提供的功能强大但不实用的过滤规则指定窗口。
图8-6是一个非常简单的用过滤进行查询的窗口界面,该窗口实现单条件查询。在列表框和图形列表框一节中对该窗口的查询功能已经做过介绍。在单条件快速查询时使用这样的方式可以满足很多用户的需要。虽说不上灵活,但是这个查询界面可以保证用户永远不会产生错误的查询条件。在后面章节将介绍如何使用过滤规则构造通用查询模块,提供类似PowerBuilder过滤规则指定窗口的功能。
函数Find也可以用来进行查询。该函数可以在数据窗口中的指定范围查找符合某些条件的数据。该函数的语法格式是:
dwcontrol.Find ( expression, start, end )
其中,dwcontrol是要进行查找的数据窗口控件名称,expression是表达式,含义和注意事项同SetFilter中的完全相同。start和end都是long类型变量,用行号表示的查找范围,它们之间没有大小约束。函数返回的是在指定范围内找到的第一个符合条件的记录号,如果没有找到或发生了错误则返回0,如果参数有Null的则返回Null。例如,在数据窗口dw_1中查找第一个姓等于用户在sle_1中指定的姓的记录,找到后高亮显示该数据行:
long ll_find //符合指定条件的数据行号
If dw_1.RowCount() < 1 Then return //如果数据窗口中没有数据则直接返回
ll_find = dw_1.Find("left(name,2) = '" + Trim(sle_1.text) + "'",1,dw_1.RowCount())
If ll_find > 0 Then //如果找到了
dw_1.SelectRow(0,False) //取消选中其他行
dw_1.SelectRow(ll_find,True) //选中查找到的行
Else //如果没有找到
Beep(2)
MessageBox("提示","没有找到! ") //显示没有找到的信息
Return
End If
更为实际的问题是,如何查找用户选中的姓氏数据,这样的程序如何实现?对上面的程序进行改进,通过在一个循环中逐渐缩小查找范围,可以选出所有的符合条件的数据,如何选中这些数据有赖于对函数SelectRow的灵活应用,它的语法格式是:
dwcontrol.SelectRow ( row, boolean )
其中,dwcontrol是数据窗口控件的名称;row是行号,该参数为0则表示是对数据窗口中的所有数据行进行操作;boolean表示是否选中,如果是True,表示选中行号row的数据行,如果是False则取消。该函数正确执行返回1,发生错误返回-1,如果参数有Null则返回Null。
2005-4-13 19:40
kevin1982
[img]http://202.107.127.122/bbscs5/showimg?picdir=imgup/10/31/20050328/img_262185_1111980162842.gif[/img]
8.8 查询和排序(2)
使用函数Find来构造查询可以有多种方式。上面的例子是很简单的一个,只是查找出符合条件的第一条数据,这样的查询比较少,一般都是要找出所有符合条件的数据。而函数Find对查找的数据没有任何视觉上的影响,只是返回找到的数据行号。可以构造一些标识,标识出找到的所有数据。经常使用的方法是循环查找,将所有找到的数据都选中;或者显示一个类似文本编辑软件中常用的字符查找窗口,每次单击“查找下一个”之类的按钮找出一个符合条件的记录,并做上一定的视觉上的标记,直到查找完所有的记录。下面介绍一种使用Find进行查询的方法,该例子按姓查找职工数据,标识出所有用户指定姓的职工数据。要查找的姓在单行编辑器sle_1中输入。脚本如下:
long ll_find
If dw_1.RowCount() < 1 Then Return
dw_1.SelectRow(0,False)
ll_find = dw_1.Find("left(name,2) = '" + Trim(sle_1.text) + "'",1,dw_1.RowCount())
If ll_find > 0 Then
Do While ll_find <> 0 and ll_find <> dw_1.RowCount()
dw_1.SelectRow(ll_find,True)
ll_find = dw_1.Find("Left(name,2) = '" + Trim(sle_1.text) + "'",ll_find + 1,dw_1.RowCount())
Loop
Else
Beep(2)
MessageBox("提示","没有找到!")
End If
因为每个汉字是两个字符长度,所以在查找姓时都是截取前面的两个字符。当然也可以截取长度等于sle_1中输入字符的长度,可以修改find语句为:
find("left(name, "+String(Len(Trim(sle_1.text)))+ "=' "+Trim(sle_1.text)+ "'",…….)
数据的排序可以在数据窗口对象设计时就指定排序规则,也可以在脚本中动态指定。使用函数SetSort和Sort可以完成这一任务,它们和SetFilter、Filter一样也必须配对使用。函数SetSort设置排序的规则,不改变数据的显示,只有当执行了函数Sort时,数据才真正进行重新排列。函数SetSort的语法是:
dwcontrol.SetSort ( format )
其中,dwcontrol为要进行排序的数据窗口控件的名称;format为排序规则,是string类型,由字段、函数、ASC或DESC、逻辑联结符、常数构成。可以使用字段名,也可以使用字段号来表示字段,字段号的格式是#X,其中X为正整数。该函数正确执行返回1,否则返回-1。下面是一个以姓名进行升排序的例子:
dw_1.SetSort("name ASC")
dw_1.Sort()
和SetFilter函数类似,当指定format为空(“”)时,可以取消排序,当指定format为Null时,可以由用户指定排序规则,窗口界面如图8-7所示。该窗口和过滤规则指定窗口有同样的确定,不太适合中国的用户,应该对其进行汉化。在后面将详细介绍如何编写一个通用的排序窗口。
使用函数SetSort后,再使用Sort函数才能重新排列数据。该函数的格式是:
dwcontrol.Sort ( )
其中,dwcontrol是和SetSort中同名的数据窗口控件名称。该函数正确执行则返回1,否则返回-1,如果参数dwcontrol为Null,则返回Null。
2005-4-13 19:42
kevin1982
8.7 读取和设置数据
读取数据窗口中的数据,这是数据窗口进行的最简单、最基本的操作。通常的做法是使用函数,其他方法如使用数据窗口对象属性直接引用字段的技术在后面专门介绍。本节介绍一些基本的读取数据的方法。
使用函数GetItemX是常用的做法,其中的X和要读取字段的类型兼容。例如,要读取string类型字段中的数据,可以使用函数GetItemString,要读取Date类型字段中的数据,应该使用函数GetItemDate。所以,在读取数据之前,应该首先清楚要读取的数据的类型。这些函数的格式是:
dwcontrol.GetItemX( row, column {, dwbuffer, originalvalue } )
其中的X可以替换成Date、DateTime、Decimal、Number、String、Time,所以读取数据的函数有6个。参数row表示要读取哪行的数据,是一个long类型数值。column代表列,可以是string型的列名,也可以是整型的列号。dwbuffer是DWBuffer枚举型,取值Primary!、Delete!、Filter!分别代表主缓冲区、删除缓冲区和过滤缓冲区。originalvalue为Boolean型,表示是否读取最近一次检索时检索到的初始值,当指定dwbuffer时必须指定该参数,该参数和dwbuffer都是可选的。函数正确执行则返回对应类型的数据,执行过程中发生错误则返回空值(“”),任何参数为Null则返回Null。
例如,在一个有性别字段的用于显示数据的数据窗口中,当检索完毕后将1都替换成“男”,将0都替换成“女”,这样的显示更好一些。可以在数据窗口的RetrieveEnd事件中编写如下脚本:
long ll_rowno
string ls_sex
If rowcount <= 0 Then return //如果没有数据则直接返回
For ll_rowno = 1 To rowcount //逐条进行处理
ls_sex = Trim(This.GetItemString(ll_rowno,"sex")) //读取字段sex并去掉两端空格
If ls_sex = "1" Then //如果是“男”
dw_1.SetItem(ll_rowno, "sex","男")
Elseif ls_sex = "0" Then //如果是"女"
dw_1.SetItem(ll_rowno, "sex","女")
End If
Next
上述例程仅仅是为了说明GetItemX函数,没有太大的实际用途。因为有很多手段可以实现该汉字界面的构造。
使用GetItemX类型的6个函数可以读取数据窗口的数据,但是在使用这些函数前必须首先知道字段的类型。另外,如果字段的类型和函数要读取的类型不兼容,例如,使用函数GetItemString读取Date类型字段中的内容,这样的错误在脚本编译时不会被检查出来。有没有解决这个问题的方法呢?
PowerBuilder中提供了any类型的变量,可以接受任何类型的数据,可以使用该变量来解决这个问题。为保证所有需要读取数据的数据窗口中都可以读取任意类型的字段,可以编写一个通用的函数。读取数据时不再使用GetItemX函数,而是使用数据窗口对象属性直接引用。下面的自定义函数使用any类型的变量,来保存读取到的字段的数据,然后返回该any类型的数据,获取该返回值时,只要使用相应的类型转换函数即可。例如,要读取字段name中的数据,可以使用如下格式调用:
String ls_name
Ls_name=String(GetItemAny(dw_1,1, "name")
函数GetItemAny如下:
/***************************************************************
*funtion GetItemAny(datawindow ids_ds,long al_row,string as_Column)
*参数: datawindow ids_ds by value
* long al_row by value
* string as_Column by value
*返回值类型:any
*功能:读取指定数据窗口ids_ds中第al_row行中字段as_column中的值
****************************************************************/
Long ll_Col
Any la_A
IF al_Row > ids_ds.RowCount() THEN RETURN ""
ll_Col = Long( ids_ds.Describe( as_Column + ".ID" ) )
IF ll_Col > 0 THEN la_A = ids_ds.object.data.primary.current[ al_Row, ll_Col ]
RETURN la_A
上面的自定义函数涉及到了另外一种引用数据的方法,即直接使用数据窗口对象属性来读取数据。该语句的格式是:
dwcontrol.object.data[buffer[.whichvalue]][row,column]
l dwcontrol是数据窗口控件名称。
l whichvalue既可以是current(缺省值),也可以是original。这样就允许用户访问一个列的初始值或当前值。
l row和column是要访问的单元所在的行号及列号。
l object、data是保留字,buffer是缓存区的名称,有Delete、Primary、Filter。
这种引用方法速度比较快,尤其读取大量数据时。实际上,这种方法有很多种格式,可以对整行数据、整列数据、一块定义区域或所有数据进行访问,非常灵活,这些都在后面介绍。
上面介绍了两种读取数据的方法,一种是使用GetItemX函数,另一种使用数据窗口对象属性直接获取数据。和获取数据相比,设置数据窗口中的数据的函数就比较简单了,仅仅有一个SetItem函数。在上面的例子中,设置“男”、“女”时就是使用的该函数。和读取数据类似,也可以直接使用数据窗口对象属性来设置数据窗口中的数据。
函数SetItem的语法格式是:
dwcontrol.SetItem ( row, column, value )
各个参数的含义如下。
l dwcontrol:数据窗口或datastore控件名称。
l row:要设置的数据所在行。
l column:要设置的数据所在的列。可以用整型列号,也可以用string型列名。
l value:要设置的值,应该和要设置的列的类型一致。
函数正确执行则返回1,这时数据窗口中row行column列显示的数据是刚刚用该函数设定的数据,如果函数执行过程中发生错误则返回-1。注意,该函数执行时仅仅检查函数中指定数据的类型和字段的类型是否一致,不会进行有效性校验,包括在数据窗口中设置的校验规则、在ItemChanged事件中编写的校验规则、在ItemChanged调用的校验规则都不会执行。所以,在设置带有有效性校验规则的字段时,应该考虑一定的技巧。
函数SetText的功能是设置当前编辑框中的内容。注意,当编辑框离开当前单元时要进行有效性校验,如果校验数据正确,则当前字段接受该数据,否则触发ItemError事件。所以,可以使用该函数给带有校验规则的字段设置数据。例如,逐行修改数据窗口dw_1中的字段name,在该数据窗口对象中给字段name设置校验规则为:Len(Trim(GetText()))>=4,校验信息为:“请输入字符不少于4个的数据!”。在窗口中放置两个控件,一个是“替换”,另一个是“SetItem”。该窗口打开时检索数据。这两个按钮下的脚本分别如下。
按钮“替换”下的脚本为:
int li_i
dw_1.SetColumn("name") //使name列成为当前列
For li_i = 1 To dw_1.RowCount()
dw_1.SetRow(li_i) //使第I行成为当前行
dw_1.SetText("屁") //向当前编辑框中写入内容
Next
dw_1.SetColumn("sex") //选中性别列,保证最后一个也要通过校验规则
按钮“SetItem”下的脚本为:
int li_i
For li_i = 1 To dw_1.RowCount()
dw_1.SetItem(li_i, "name","哈")
Next
运行上面的窗口,单击按钮“替换”,每条替换时都会显示校验错误信息“请输入字符不少于4个的数据!”,并且数据也不会被替换。单击按钮“SetItem”则能够正常设置所有的name字段,并不会出现任何错误。
因此,在设置有校验规则的字段时,函数SetText、SetColumn及SetRow配合使用,这样可以确保数据能够不被校验规则忽略。使用函数SetItem可以用来设置没有校验规则的字段,或者在确保数据肯定能通过校验规则时,用来设置有校验规则的字段。
2005-4-13 19:43
kevin1982
8.9 数 据 打 印
数据窗口的打印工作一般是必不可少的,有很多的函数和属性可以用来进行打印,最常使用的是print通用函数。如果对打印工作没有特殊的要求,使用下面的脚本就可以完成打印设置和打印工作。
在按钮“打印设置”的Clicked事件下编写脚本:
PrintSetup()
在按钮“打印”的Clicked事件下编写脚本:
dw_1.Print()
单击按钮“打印设置”可以打开操作系统中的打印机设置窗口,如图8-8所示。在该窗口中再单击Setup按钮可以进行关于选中的打印机更详细的设置。下面详细介绍各种打印的实现。
PowerBuilder中提供了两种常用的打印数据窗口的方法,一种是使用函数print,使用该函数可以由DataWindow自动维护打印作业;另一种是使用函数PrintDatawindow,该函数需要脚本来维护打印作业。使用打印作业应该首先启动打印作业,然后发送数据到打印机,最后关闭打印作业。典型脚本模式如下:
Long ll_job
Ll_job = PrintOpen("数据窗口打印")
//进行打印相关项目的设定
//发送打印内容
PrintClose(ll_job) //关闭打印作业
8.9.1 启动打印作业
PrintOpen函数用来打开一个作业,并返回当前可以使用的打印作业号,该打印作业号可以标识当前的打印工作。该函数的语法是:
PrintOpen ( { jobname } )
如果发生错误,该函数返回-1。打印作业名称是可选的,名字在打印队列中。在打印作业的最后必须关闭打印作业,使PowerBuilder和Windows清除打印作业所占用的所有资源。因此,每个启动作业的语句都有一个关闭作业的语句相对应。
8.9.2 关闭打印作业
有两个函数可以用来关闭打印作业。PrintClose()函数把当前页传送给打印机,并关闭当前打印作业。语法格式为:
PrintClose(printjobnumber)
函数PrintCancel()取消打印作业并删除当前的打印文件。这个函数可以与Print或者PrintDataWindow()函数组合使用。用于PrintDatawindow()的语法是:
DatawindowControl.PrintCancel()
用于Print()的语法是:
PrintCancel(printjobnumber)
PrintClose()函数和PrintCancel()函数是互相排斥的,成功调用过一个以后,不要在没有再次打开打印作业时调用另一个函数。
8.9.3 PrintDatawindow函数
该函数是以单个打印作业的形式打印数据窗口控件中的内容。PowerBuilder使用数据窗口对象中定义的字体和布局进行打印。用这个函数可以在一个打印作业中打印多个数据窗口,但是每个数据窗口控件都从新的一页开始打印;如果要让几个数据窗口打印在同一页中,则需要利用底层的打印函数或将要打印在同一页中的数据窗口,创建成一个composite显示样式的数据窗口。PrintDatawindow函数的语法是:
PrintDatawindow(printjobnumber,datawindow)
其中,printjobnumber是PrintOpen函数返回的打印作业号,datawindow是要打印的数据窗口控件的名称。除了能够和Printopen()、PrintClose()函数共同使用外,其他函数都不能和PrintDatawindow共同使用。下面是一个完整地使用函数PrintDatawindow()进行数据窗口打印的例子,该例子中同时打印三个数据窗口:
Long ll_job
Ll_job = PrintOpen("数据窗口打印")
PrintDatawindow(ll_job,dw_1)
PrintDatawindow(ll_job,dw_2)
PrintDatawindow(ll_job,dw_3)
PrintClose(ll_job) //关闭打印作业
在一个打印作业中同时打印多个数据窗口时,有时需要页码连续计数。如果使用composite类型的数据窗口就没有必要考虑这个问题了,打印单个的数据窗口如何解决这个问题呢?首先在应用中定义一个全局变量,用来保存当前已经打印过的数据窗口的页号:
int gi_pageno
定义一个函数f_pageno(可以是全局函数,也可以是窗口函数)用来返回前面已经打印过的页号:
Return gi_pageno
在数据窗口对象中放置计算字段引用该函数,计算字段如下定义:
page()+String(f_get_pageno())
然后在数据窗口的PrintPage事件中修改该变量,以便记录每个数据窗口打印了多少页。
gi_pageno = gi_pageno + 1
在第一个要打印的数据窗口的PrintStart事件中编写如下脚本,以便在重新开始打印作业时,页号重新开始计数:
gi_pageno=0
在其他要打印的数据窗口的PrintStart事件中编写脚本,使计算字段更新:
dw_1.Modify("Page_c.Expression= 'String( page()+f_get_pageno())'")
上面的过程可能感觉到有些烦琐。实际上脚本很简单,定义一个全局变量和一个函数,在数据窗口对象上放置一个计算字段,在数据窗口的printstart、printpage事件中编写脚本。
8.9.4 Print函数
该函数是一个通用的函数,可以用来打印PowerBuilder中许多可视对象。下面介绍打印数据窗口时的语法,格式如下:
datawindowname.Print ( { canceldialog } )
datawindowname为要打印的数据窗口控件名称,canceldialog是一个boolean型变量,指示在打印时是否显示一个无模式的可以随时取消打印的窗口,该变量缺省为True。该函数正确执行则返回1,执行过程中发生错误则返回-1。
虽然该函数和PrintDatawindow一样都可以打印数据窗口,但是它们之间是有区别的。Print函数使用设置在数据窗口对象的打印规范来打印数据窗口,而PrintDatawindow函数使用打印机当前的设置来打印数据窗口。本节开始介绍的一个简单的打印编程,在调用函数printsetup时虽然可以设置打印机,但不是所有的设置都可以在调用Print函数来打印时体现出来,只有那些在数据窗口对象中没有指出的可以起作用。因为按钮“打印设置”仅仅调用函数printsetup(),没有修改数据窗口对象的打印设置。后面章节详细介绍开发一个通用的可以设置打印机、打印份数、打印方向、打印纸张、打印哪些页面等等很多选项的通用的打印配置窗口。
2005-4-13 19:44
kevin1982
[img]http://202.107.127.122/bbscs5/showimg?picdir=imgup/10/31/20050328/img_262189_1111980288390.gif[/img]
5.2 编 写 脚 本
Powers cript画板中提供了很多辅助的功能,可以提高开发效率、减少开发人员死记硬背。尤其对于初学者,掌握这里介绍的功能可以加快学习的进度。
5.2.1 粘贴语句
对于PB的初学者或使用多种语言进行开发的编程人员来说,记清楚一些语言的语法规则是件很烦人的事情。PB在Powers cript画板中提供了很强的语句粘贴功能,可以很方便地在Powers cript任意位置粘贴PB的流程控制语句,可以像小学生做填空题似的在流程控制语句中加入条件和语句即可。
可以在窗口菜单Edit中的Paste Special下选择适当的命令,也可以在s cript视窗的鼠标右键弹出菜单中进行同样的选择。比如,需要在s cript视窗中粘贴Choose Case的语法格式,可以在Paste Special下选择Statement->Choose Case,在s cript窗口的当前光标位置自动放置如下脚本:
CHOOSE CASE /*expression*/
CASE /*item*/
/*statementblock*/
END CHOOSE
接下来只要修改部分注释就可以了。
当需要粘贴PB提供的函数时,选择Function->Built-in,然后稍等片刻(这时的鼠标不要点任何按键,显示内建函数的时间稍长)即可在s cript视窗中当前光标位置显示一个包含系统中当前可用函数的选择框。该选择框中一般包含的函数比较多,可以使用右侧的滚动条翻页查找,也可以直接使用键盘上的PgUp或者PgDn键翻页查找,还可以直接输入需要函数的首字母来自动翻到以该字母开头的函数处。注意,这时鼠标不要在该选择框之外进行操作,否则选择框将自动关闭。找到需要的函数后,双击,选择框自动关闭,在s cript视窗当前光标位置放置该函数。除了可以粘贴PB提供的函数外,还可以粘贴用户自定义函数和扩展函数,操作方法相同。如果选择了命令后,在s cript视窗中没有显示相应的选择框,则表示没有可供粘贴的对象。
需要粘贴SQL语句时,系统自动和数据库联接并进入到另外一个窗口中,在该窗口中一般进行选择数据表、字段和条件,返回这几个操作步骤,即可将SQL语句返回并粘贴到刚才的s cript视窗的当前光标位置。下面以粘贴Select SQL语句为例介绍操作过程。首先选择Paste Spaciel->SQL->Select,如果还没有同数据库建立联接,则此时需要联接数据库,在窗口底部信息栏中显示Connecting Database。建立联接后,显示一个选择数据表的对话框。接下来要指定哪些字段,如果从多个表中选择字段,应该指明表之间的联接条件。这里的操作和创建视图时的操作相同,可以参见上一章创建视图时的介绍。组合完毕后,点击PainterBar上最右侧的小功能图标返回到原来的画板中,并在s cript视窗中光标的当前位置插入刚刚组合的SQL语句。放弃粘贴该SQL语句,直接使用窗口右上角的关闭按钮关闭窗口。
在菜单中选择其他项目可以粘贴其他内容。比如,粘贴全局变量可以选择Global,粘贴共享变量选择Shared,粘贴实例变量选择Instance,粘贴应用系统中的窗口名称选择Window,粘贴本画板中控件名称选择Object,粘贴当前事件参数使用Argument,粘贴其他文件中的文字内容可以选择From File,粘贴流程控制语句使用Statement。
5.2.2 添加注释
给脚本添加注释,虽然延长了开发时间,好像是降低了开发效率,从长远来看却恰恰相反。因为一个软件的生存周期很长,只要软件在使用,就存在维护的问题,而从事维护工作的不一定就是开发人员,程序的可读性直接影响到程序的可维护性,添加详细的注释可以使程序有很好的可读性。即使开发人员自己从事软件的维护工作,随着时间的推移,一些具体的细节也会淡忘。除了维护,开发期间的调试工作也需要很好的注释,规模稍微大一点的软件项目,都要涉及到合作问题。如果程序没有很好的注释,别人总要反复询问,势必影响开发的进度。注释实际上是程序文档的一部分,从严格意义上来说,是开发人员必须要提交的。
s cript的注释有两种方式,一种是从“//”开始到换行时结束的注释,这种注释方式比较简短,使用快捷。如果在整行的开头使用“//”,还可以使用快捷方式,点击图标可以在选中行的行头添加注释标记“//”,点击图标可以取消选中行的行头注释标记。这两个小图标用来添加注释和取消注释,可以对多行操作。另一种注释方式是以“/*”开始,以“*/”结束,一般在注释较长时使用。例如,下面的注释:
/*---------------------------------------------------------------------------------------------------------
* 函数定义:Integer FUNCTION f_set_table_select (REF adw_dataWindow)
* 函数作用:将数据窗口adw_dataWindow的SQL语句设置为用户在查询条件组合
* 窗口中设定的SQL语句。
* 特别说明:参数adw_dataWindow的传递方式为by referrence,这点一定要注意。
* 作 者: 郭宝利
* 编写日期: 2001-1-2
*---------------------------------------------------------------------------------------------------*/
String ls_rtn //用来保存用户在查询条件组合窗口中设置的信息
String ls_Filter //用来获取用户指定查询条件
OpenWithParm(w_query_tj,"gz") //打开查询条件组合窗口
Ls_rtn = Message.stringparm //格式: SQL检索条件+'^'+汉字检索条件
If ls_rtn <> '' Then //用户设置了查询条件
Ls_filter = Left(ls_rtn,Pos(ls_rtn, '^') – 1) //获取数据窗口的过滤条件
/*--------------------- 开始用用户的查询条件检索数据 -----------------*/
If dw_1.RowCount()< 1 Then return //如果没有数据,则没有过滤的必要了
dw_1.SetFilter(ls_filter) //设置过滤条件
dw_1.Filter() //过滤数据
/*-------------------------------- 检索数据完毕 -----------------------------*/
Else
Return //直接返回
End If
建议在自定义函数的开始、小功能模块的开始、判断的各条件分支、比较难懂的算法、变量定义时、调用其他函数或窗口处、窗口调用返回的信息格式等地方书写注释。注释要讲明要点,清晰明了。对于协作开发的大型软件项目,公共模块的注释要讲清楚作者、编写日期、调用接口、函数的返回值、使用方法,并对模块的功能做简短的描述。上面的注释是一个自定义函数中常见的注释格式。
5.2.3 查找替换
使用Windows 95的人对查找和替换操作应该很熟悉了,PB在s cript画板中也提供了这种风格的查找和替换,但它提供了更多的选项。查找和替换这两个对话框在PowerBuilder中大同小异,这里只介绍替换窗口。在s cript视窗中,使用鼠标右键弹出菜单中的Replace Text、窗口菜单Edit中的Replace Tex和快捷键Ctrl+H,都可以打开如图5-2所示的对话框。
该对话框中有5个选项开关可以使用。Match Case表示是否匹配大小写;Search Selection Only表示只在选中的范围内进行搜索匹配;Search Backwards表示向相反的方向查找;Wrap at Beginning or End表示当查找到文件结尾时是否继续从文件的开始搜索,直到搜索的起点结束,该选项默认是选中的;Regular Expression表示进行常规表达式搜索,该搜索是比较有技巧的搜索,例如输入“err[1234]”并选中Regular Expression可以查找出err1,err2,err3,err4。关于这种搜索方法的匹配表达式可以参阅函数match的详细解释。
5.2.4 编译脚本
当关闭s cript视窗时,在s cript视窗中选择其他事件或者其他控件时,选择s cript窗口右键弹出菜单中的Compile窗口菜单Edit中的Compile或者快捷键Ctrl+L时都可以导致编译当前s cript视窗中的当前脚本。
直接关闭s cript视窗,系统对脚本进行编译。编译通过,则保存脚本,没有通过则显示一个对话框询问是否放弃脚本的修改并关闭。如果确认,则关闭s cript视窗并放弃刚刚做的脚本修改,如果选择“否”,则显示编译错误信息,并且不关闭s cript视窗。如图5-3所示。其他方法进行编译脚本出错时都会停留在s cript视窗,并显示编译错误信息,其界面和图5-3相同。
上面的程序中有一个错误信息。双击错误信息光标会跳到与该错误信息对应的语句位置,便于修改找错。
2005-4-13 19:45
kevin1982
5.3 Powers cript语言基础(1)
不管哪种语言,都有自己特定的数据类型、操作符、保留字、流程控制、函数和书写规则。学习一种编程语言,最基本的要求就是要掌握这些方面,较高要求就是灵活地运用函数。单个函数的用法比较好理解,但怎么用这些函数来解决实际问题,用哪些函数的效率比较高,这不仅仅是一个算法问题,而且在很大程度上取决于对函数灵活地运用(当然,程序执行效率也有赖于界面的设计,这在后面的界面设计章节介绍)。
所以,在学习Powers cript语言时,不能拘泥于一些死的规则、规定,要多实践、勤动手。很多工作上的事情都可以用Powers cript语言来解决,在学习Powers cript语言时可以尝试着用学过的内容来解决这些问题。例如,人民币的习惯表示都是大写的汉字,如何将数字转变为大写汉字表示;Powers cript语言提供了在字符串中查找指定子串的函数,如何编写通用的函数,来查找某字符串中最后一个指定的子串;和数据库打交道的应用程序一般都要涉及到排序和查询,如何编写一个通用的排序模块和查询模块等等。
具体到Powers cript语言的学习,要掌握变量、表达式、语句、事件和函数,其中要灵活运用函数。
5.3.1 注释
注释用于书写关于脚本的说明,提供功能上的解释或设计思路的简短描述,可以增强程序的可读性,便于日后的维护工作。有两种注释方式:
= 以“//”开头,后面是注释内容,在一行内书写下。这种注释方式不能在语句中间插入注释,只能是在一行的开头或一行的后面书写注释。s cript画板中为这种注释方式提供了快捷方式,使用比较方便。
= 在“/*”和“*/”符号之间的为注释内容,可以多行书写注释。这种注释方式可以在语句的中间插入注释内容,一般用于书写内容比较多的注释。
例如,下面的脚本中使用了这两种注释方式:
String ls_name //该变量用于保存职工姓名
/*下面的程序定义光标,从数据库workers表中提取所有的职工姓名。为保证不重复提取,在name字段名前使用了关键字distinct,然后对光标逐条数据进行处理,将姓名加入到下拉列表框中,以供用户选择。*/
declare name_cur /*光标名称*/ cursor for
select distinct /*该关键字可以确保不重复提取*/ name from workers;
……
5.3.2 标识符
标识符是指在脚本中使用的变量、标号、窗口、控件、菜单以及其他所有引用的对象的名称。该名称的命名应该遵循如下规则:
(1)必须以字母或下划线开头。
(2)最长为40个字符,不能包含空格。
(3)不区分大小写,例如name,Name,NAME是相同的。
(4)不能是保留字,如This,Parent。
(5)可以包含数字、字母和一些特殊字符,特殊字符有短线“-”、下划线“_”、美元符号“$”、号码符号“#”和百分号“%”。
在变量中可以使用短线,使标识符的表达能力更强,但同时也带来了麻烦。因为像li_in-li_out这样的表达式就有了歧义:到底是变量li_in减li_out呢,还是一个变量名称呢?为此,PowerBuilder规定减操作符号为“ – ”,也就是说短线的两端都带有空格表示为减操作,否则是变量中的一个普通字符。因为空格不能出现在变量中,所以只在短线前面加空格表示减操作符就足够了,也就是“ –”。为了避免这样的麻烦,可以在s cript画板中配置不允许在标识符中使用短线(取消Allow dashes in identifiers选项即可)。
标识符命名时,应该使标识符有一定的字面含义,有助于程序的调试和脚本可读性的提高。可以在标识符中使用大写、短线、下划线来体现标识符的意义。例如:CharacterCounter、character_counter,character-count肯定都比charactercount的可读性更好一些。一个好的标识符命名建议是:
变量作用域+变量类型+“_”+具有一定字面含义的名称
使用该建议,上面的标识符character_counter改为li_CharacterCount,能反映出更多的含义:“l”代表是本地变量,是Local的缩写,“i”代表是integer类型的变量,CharacterCount表名该变量是用来记录字符个数的计数器。
按照上面的规则和建议,下面是变量的一些例子:
(1) is_code
(2) li_count
(3) firstbuttoncaption
(4) FirstButtonCaption
(5) 2nd_quantity
(6) first button caption
(7) child’sage
其中,前四个是正确的变量名称,后面三个是错误的。(1)和(2)是比较好的变量命名,能够体现变量的类型、作用域和含义;(3)的可读性不是太好,(4)的可读性稍好,但不能体现类型和作用域。
5.3.3 特殊ASCII字符
字符串中可以包括一些特殊的控制字符或者字符串的界定符,例如可以在字符串中加入制表符以保证显示格式的整齐。使用方法如表5-1所示。
表5-1 特殊ASCII字符
名 称
符 号
名 称
符 号
换行(Formfeed)
~f
新行
~n
横向制表符
~t
垂直制表符
~v
回车
~r
退格(Backspace)
~b
单引号
~’
双引号
~”
波浪号~
~~
通过上表可以看出,特殊字符都是使用~符号来引用的。实际上,其他任意字符都可以使用~符号再加上数字来表示。数字的表示可以是从0到255的十进制,也可以是从0到hFF的十六进制数,还可以是从0到377的八进制数。例如:
~121 //ASCII码值为121的符号,也就是“y”
~h79 //ASCII码值为十六进制的79的符号,也就是“y”
~o171 //ASCII码值为八进制的171的符号,也就是“y”
注意,h是十六进制英文Hexadecimal的缩写,o是八进制英文单词Octal的缩写,都是小写。
5.3.4 标号
标号是和GOTO语句配合使用的,是GOTO语句要跳转的地方。标号由一个标识符后面跟一个冒号(:)构成,可以在一个脚本中放入任意数目的标号,但是同一段脚本中,标号不能重复。GOTO语句跳转时也只能跳转到当前脚本中的标号处,不能跳转到其他脚本的标号处。在结构化的程序设计中不提倡使用GOTO语句,但是Powers cript还是支持它。标号可以单独占用一行,也可以和语句同在一行。例如:
OtherProcess:
If lb_selected Then Continue_process=True
……
OtherProcess: If lb_selected Then Continue_process=True
……
以本人的开发经验,标号都是可以避免的。
2005-4-13 19:47
kevin1982
5.3 Powers cript语言基础(2)
5.3.5 NULL值
NULL代表不知道或者没有定义,和空值完全不同。例如,在数据库中某字段取值为NULL,代表用户还没有处理这个字段的数据,有待于处理;而如果该字段取值为空,代表这个字段的数据用户做了处理,这个字段的取值是清楚的。
当变量定义后,变量没有取值,应该为NULL,但实际上不是这样。PowerBuilder为每种类型的变量都规定了默认值,当刚刚定义了某类型的变量时,该变量的取值为这种类型变量的默认值。如integer类型的默认值为0,string类型的变量的默认值为“”。
大多数情况下,NULL和数据库打交道时才经常使用。当某个取值为NULL的字段取值读入到变量时,该变量被置为NULL。也可以通过函数将变量的值置为NULL,但是不能直接把NULL赋值给变量。任何类型的变量都可以被赋值为NULL,NULL是一个非常特殊的值。函数用法是:
SetNull(变量名)
执行成功时返回1,否则返回-1。
例如:
String ls_name //此时变量ls_name的取值为“”
SetNull(ls_name) //ls_name变量此时为NULL
判断某变量的取值是否为NULL时,使用函数IsNull(变量名),不能使用“=”来判断。例如“if ls_name=NULL then…”是不正确的,“if IsNull(ls_name) then …”是正确的。当某布尔表达式的取值为NULL时,Powers cript处理为False。例如,下面的语句都不能执行beep(1)语句:
Int li_Nbr
// Set li_Nbr to NULL.
SetNull(Nbr)
If li_Nbr =1 Then Beep(1)
If li_Nbr <> 1 Then Beep(1)
If Not (li_Nbr = 1) Then Beep(1)
正因为NULL的特殊性,在条件语句中判断时要备加小心。例如,下面的语句就能正确执行ELSE中的信息显示:
Int li_count
SetNull(li_count)
If li_count = 1 Then
MessageBox("Value", "li_count= 1")
Else
MessageBox("Value", "li_count= NULL")
End If
但是,如果把上面的语句做语法上的等价修改,就什么显示信息也没有:
Int li_count
SetNull(lli_count)
If li_count = 1 Then MessageBox("Value", "li_count= 1")
If li_count <> 1 Then MessageBox("Value", "li_count= NULL")
当字符串的联接运算中有NULL时,取值也为NULL。例如:
String ls_name,ls_temp
SetNull(ls_temp)
Ls_name = "张三"
Ls_name = ls_name + ls_temp
If IsNull(ls_name) Then MessageBox("提示","空值!")
函数MessageBox()经常用来显示一定的信息,和用户交互时经常使用。如果要显示的信息是NULL时,提示窗口不会显示。例如,下面语句执行时,没有任何的信息提示窗口:
String ls_name,ls_temp
SetNull(ls_temp)
Ls_name = "张三"
If IsNull(ls_name) Then MessageBox("提示",ls_name + ls_temp)
5.3.6 保留字
保留字是PowerBuilder内部使用,不能用做标识符的单词。总共有99个,基本上都是些逻辑运算符、流程控制语句、事务处理语句、指代词等。为避免标识符命名时和保留字发生冲突,现将PowerBuilder的保留字罗列于表5-2。标有“*”号的保留字可以用做函数名,标有“+”号的是在PB 7.0中没有、PB 8.0新添加的保留字。
表5-2 保留字
Alias
Dynamic
Immediate
Privatewrite
Systemwrite
And*
Else
Indirect
Procedure
Then
Autoinstantiate
Elseif
Insert
Protected
This
Call
End
Into
Protectedread
Throw+
Case
Enumerated
Intrinsic
Protectedwrite
Throws+
Catch+
Event
Is
Prototypes
To
Choose
Execute
Last
Public
Trigger
close
Exit
Library
Readonly
True
Commit
External
Loop
Ref
Try
Connect
False
Next
Return
Type
Constant
Fetch
Not
Rollback
Until
Continue
Finally+
Of
Rpcfunc
Update*
Create*
First
On
Select
Updateblob
Cursor
For
Open*
Selectblob
Using
Declare
Forward
Or
Shared
Variables
Delete
From
Parent
Static
While
Describe*
Function
Post*
Step
With
Des criptor
Global
Prepare
Subroutine
Within
Destroy
Goto
Prior
Super
_debug
Disconnect
Halt
Private
System
Do
If
Privateread
Systemread
注意,这些保留字是不区分大小写的。例如,parent、Parent、PARENT是相同的保留字。命名标识符时,为了避免和保留字冲突,除了不使用表5-2中的保留字外,还可以有以下两种方法。
= 通过颜色来区分:所有保留字在s cript画板中都是绿色显示的,保证命名时,变量不是绿色即可。
= 养成良好的标识符命名习惯,例如使用匈牙利命名法、在变量中使用短线或下划线等等。
5.3.7 指代词
PowerBuilder提供了4个指代词,它们是This,Parent,ParentWindow,Super,可以代替对象名称来引用对象,从而增强程序的可移植性。例如,假设在窗口w_main中,按键“退出”的Clicked事件上有语句Close(w_main),可以关闭窗口w_main,当窗口名称改变时,必须修改该语句才能正确执行;使用指代词就不同了,Close(parent)可以在窗口名称发生变化时仍然能够正确执行,并且该控件拷贝到其他窗口中不加修改仍然可以正确使用。
指代词根据脚本所在对象周围环境的不同而指代不同的对象。比如,在窗口上的按键,它的Clicked事件中,脚本的Parent就是指代按键所在的窗口;可视用户对象内,按键Clicked事件中,脚本的Parent就是指代按键所在的用户对象。表5-3列出了各个指代词可以在哪些对象中使用以及所指代的对象。
表5-3 指代词
指 代 词
使 用 位 置
含 义
Parent
窗口中的控件
父窗口
可视用户对象中的控件
用户对象
菜单
上一级菜单
This
窗口、定制用户对象、应用对象、控件
对象或控件自身
ParentWindow
菜单
运行时菜单所在的窗口
Super
子对象或控件
父对象或控件
子窗口或用户对象
直系祖先
子窗口或用户对象中的控件
用户对象的直系祖先
下面再对这些指代词做详细介绍。
1.This
指代词用于引用当前对象。This指代词代表脚本所在的对象,虽然和直接使用该对象的名称作用相同,但是对象更名或者将脚本拷贝到其他对象的事件中时,This具有更好的通用性,使用This指代词可以类属地引用为其编写脚本的对象,这样,开发人员不必将脚本指向一个特定的对象名称来编写代码。例如,在按键中编写脚本时使用This,可以理解成是为按键这个类编写脚本,而不仅仅是为当前按键编写脚本。这有利于开发人员开发更通用的脚本。
在对象或控件的脚本中,开发人员可以不加指代词This或本身的对象名来引用对象或者控件的属性,但是,良好的编程风格应该提倡使用指代词This,使脚本更清晰易读。例如,下面是一个按键Clicked事件下的语句,省略了指代词This仍能够完成this.x=this.x+50所完成的任务:
x=x+50
但是,如果上面的语句所在脚本中定义了变量x,脚本的执行就不能完成预期的功能了。PowerBuilder在变量处理时,认为x是变量而不是按键的X坐标了。当有同名的全局变量和实例变量时,PowerBuilder将会首先找到全局变量,需要引用本地变量,就必须使用指代词,因为实例变量是属于窗口或用户对象的,指代词应该指代窗口或者用户对象。所以,在窗口或用户对象中编程则使用指代词This,在窗口中的控件或用户对象中的控件上编程则指代词是parent。例如,某窗口中有一个和全局变量test_str同名的实例变量,在该窗口中引用该实例变量的语句是this.test_str,在该窗口的控件上引用该实例变量的语句是parent.test_str,当然,全局变量可以直接使用。
在函数的参数中也可以使用this,用来传递调用函数的对象。例如:
wf_SetPosition(this)
2.Parent
指代词Parent用于引用包含当前对象的对象。在窗口或用户对象的控件中,Parent指代窗口或者用户对象;在菜单中,Parent指代包含当前菜单项的上一级菜单。例如,在窗口的“改变标题”按键上编写如下脚本:
If parent.title="实验窗口" Then
parent.title = "hello!"
Else
parent.title = "实验窗口"
End If
用户点击该键会看到窗口的标题在“实验窗口”和“hello!”两个之间来回切换。在窗口w_main的“退出”按键上使用close(parent)和close(w_main)语句是相同的,前面的更通用些,即使窗口的名称改变了,程序仍然能够正常运行。
在定制的用户对象内,使用Parent的任何控件都是引用用户对象本身,而不是引用放置用户对象的窗口;需要访问用户对象的父对象时不能使用Parent了,只能使用GetParent()函数。在标准用户对象中,Parent仍然指代用户对象放置的对象。例如,某可视用户对象中有一个“关闭”按键用来关闭该用户对象所在的窗口,就应该在该按键的Clicked事件上编写如下脚本:
Close(GetParent(parent))
2005-4-13 19:48
kevin1982
5.3 Powers cript语言基础(3)
3.ParentWindow
该指代词只用于菜单中,用来指代在运行时和菜单所联系的窗口。例如,在菜单项“关闭”的Clicked事件中可以编写下面脚本,用来关闭和该菜单关联的窗口:
Close(ParentWindow)
当然也可以使用窗口名称。比如窗口w_main上的菜单项Clicked事件中,Close(ParentWindow)和Close(w_main)功能是相同的。当编写MDI类型的应用软件时,可能多个名称不同的窗口使用同一个菜单,使用语句Close(w_main)就不行了。
除了使用ParentWindow的通用性比直接使用窗口名的通用性要强外,它们之间实际上还有其他的区别。假设窗口w_main中包含静态文本控件st_1,下面两个语句虽然在逻辑上是完全相同的,但是下面的语句就是错误的:
ParentWindow.st_1.text = "测试" //该语句是错误的,编译不能通过
而下面语句就是正确的:
w_main.st_1.text = "测试" //能够正确运行。
所以,这些语句要慢慢体会,尤其这些逻辑上相同但实际却不同的语句。写出上面的语句,无非想构造比较通用的程序。在一个对象中直接修改其他对象中的内容,这种设计是不合理的,导致了环境耦合。好的方法是在菜单项中触发父窗口的某个事件,通过窗口的事件脚本让窗口自己来修改自己的内容,这样一个菜单挂到几个窗口上都可以使用相同的脚本,只要窗口中自定义事件不同就可以了。可以如下修改:
在菜单项中编写:parentWindow.TriggerEvent('ue_menupost')
在窗口的ue_menupost事件中编写: st_1.text = '测试'
4.Super
该指代词只有在处理继承时才使用,用来引用后代对象的祖先。使用祖先名称也可以引用,但是更为通用的是Super。例如,想调用父对象的Clicked事件处理程序时,子对象中可以这样写:
CALL Super::Clicked
当调用在子类中被重载的父对象的函数时,也可以使用super来指代父对象,例如,子对象重载了父对象的函数f_func(),在子对象中调用父对象的f_func()函数的语句可以这样编写:
Super::f_func()
5.3.8 续行和断句
通常情况下,Powers cript的一条语句书写在一行上,语句书写完毕后,按Enter键(回车键)转到下一行,开始下一条语句。有时候,为了阅读方便等原因,需要把一条语句书写在几行上,这时就需要使用续行符了。Powers cript的续行符为符号&,放在一行的末尾指示下一行是当前行的继续,例如,我们可以把语句:MessageBox("SQL语句出错","出错编号="+String(SQLCA.SQLCode))书写在两行上:
MessageBox("SQL语句出错","出错编号="+&
String(SQLCA.SQLCode))
注意,不能在标识符或保留字的中间续行。例如,下面的续行是错误的,原因在于将标识符分成了两行:
MessageBox("SQL语句出错","出错编号 = "+String(SQLCA.SQLCode))
在SQL语句中也不要使用续行,因为SQL语句使用“;”作为语句结束,从SQL语句开始到“;”符号之间的所有字符都认为是SQL语句,如果使用了“&”就会产生错误。
在一行中写多条语句,在语句之间用分号进行分隔,例如:
a = b + c;g = h + j;cnt = a + g + cnt
但最好不要在一行中写多条语句,每条语句占据一行或多行既直观又清晰,何必自寻烦恼呢!
5.3.9 空白
空白包括空格、Tab符、换行符和注释。除非空白被包括引号中,否则编译器忽略空白,不考虑它们所占的空间。例如:
ls_FirstName + ls_LastName //加号两边空格被忽略,同时该注释也被忽略//
MessageBox("提示","这 里 的 空 格 没 有 被 忽 略!")
特殊情况下,短线“-”两端的空格不能被忽略,这时,短线加空格被处理成减号。
2005-4-13 19:49
kevin1982
[img]http://202.107.127.122/bbscs5/showimg?picdir=imgup/10/31/20050328/img_262194_1111980404307.gif[/img]
9.2 关于菜单的编程(1)
关于菜单的编程涉及菜单项、工具条和小图标等的属性,函数,以及菜单项的事件等等。最经常的编程是在菜单项的Clicked事件中编写相应的脚本执行其字面表达的功能,其次是在窗口打开事件中初始化工具条的位置,或者在工具条位置改变时保存用户的配置,或者是打开Sheet时处理菜单共享,或者是在其他控件上响应点击鼠标右键时弹出菜单等等,下面分别介绍。
9.2.1 为“退出”菜单项编写脚本
在菜单中指代词parentwindow是指菜单挂接的窗口,比直接使用窗口的名称有更好的通用性。所以在“退出”菜单项的Clicked事件中可以这样编写脚本:
Close(parentwindow)
9.2.2 选中某个菜单项
可以使用Checked标记来表示某个菜单项选中,可以在菜单项的Clicked事件中编写如下脚本来选中菜单项:
If This.Checked Then //如果菜单为选中状态
This.Checked = False //则取消选中
Else //如果为非选中状态
This.Checked = True //则选中该菜单项
End If
也可以使用函数Check和UnCheck来代替属性修改。上面的脚本可以改成:
If This.Checked Then
This.UnCheck()
Else
This.Check()
End If
注意: 当菜单项选中时,菜单项的左侧有一个“√”符号,并且对应的小图标为按下状态。
9.2.3 隐含某个菜单项
要隐含某个菜单项,可以将该菜单项的Visible属性修改为False,如果该菜单项位于菜单条的最高级别上,则可以将整个下拉菜单隐含。例如,某窗口上挂接了菜单m_main,在窗口的打开事件中将菜单中的“文件”整个下拉菜单隐含,可以在该窗口的Open事件中编写如下脚本:
m_main.m_文件.Visible=False
但是,将菜单项隐含并不能将其对应的在工具条中的小图标隐含,并且小图标的功能照样能够正常执行。要将小图标隐含,应该设置ToolBarItemVisible属性为False。例如,隐含菜单项“打开”对应的小图标,在窗口的打开事件中可以使用下面的语句:
m_main.m_文件.m_打开.ToolBarItemVisible = False
9.2.4 使某个菜单项失效
将菜单项的Enabled属性置为False,即可使菜单项显示变灰而不能使用,同时对应的小图标也变灰。如果是下拉菜单则整个下拉菜单不能使用,但其下拉项目对应的小图标可以正常使用。如果是级联菜单则级联菜单不能被打开,但是级联菜单下的各个菜单项对应的小图标可以正常使用。下面是在窗口打开事件中将菜单m_main的“文件”下的m_open修改为无效的脚本:
m_main.m_文件.m_open.Enabled=False
9.2.5 调整工具条的位置
如果工具条的ToolBarUserControl属性设置为True,则用户可以在工具条上按鼠标右键弹出菜单。使用该弹出菜单可以调整工具条的显示位置。用直接拖拉的方式也可以改变工具条的显示位置。当工具条的位置发生变化时将触发窗口的ToolBarMoved事件,在该事件中可以编写脚本来判断工具条的移动情况。当该事件触发时,PowerBuilder自动修改Message中的参数WordParm和LongParm,使用这两个属性可以判断工具条的移动情况,属性含义如下:
Message.LongParm属性的含义:
l 0:移动到左侧;
l 1:移动到顶部;
l 2:移动到右侧;
l 3:移动到底侧;
l 4:变为浮动显示。
Message.WordParm属性的含义:
l 0:FrameBar在移动;
l 1:SheetBar在移动。
关于工具条位置的编程一般是在窗口打开和关闭时应用。关闭时应该保存用户关于工具条的设置,以便在窗口打开时将工具条恢复到上次关闭时的状态。这些信息可以保存在应用的ini文件中,具体含义只要开发人员清楚即可,打开时按同样的含义来设置工具条的位置。例如,若按照上述message.logparm的含义在Frame关闭时保存工具条关于位置的设定,则脚本如下:
Choose Case this.ToolbarAlignment
Case AlignAtBotTom!
SetProfileString("myapp", "setup", "Toolbarpos", "3")
Case AlignAtLeft!
SetProfileString("myapp", "setup", "Toolbarpos", "0")
Case AlignAtRight!
SetProfileString("myapp", "setup", "Toolbarpos", "2")
………
End Choose
当然,也可以用同样的方法保存更多的设置,例如是否隐含工具条,是否显示文字,SheetBar的显示情况等等。在上述脚本中用到了函数SetProfileString,它的语法格式是:
SetProfileString ( filename, section, key, value )
它的含义是将value值写入文件filename的section节的key关键字中,关于ini文件的格式详细参见本书前面的介绍。
同样,在Frame的打开事件中可以根据ini文件的设置来决定工具条的显示。例如,下面的脚本在打开事件中读取ini文件中的配置来决定工具条的显示位置:
String ls_pos
ls_pos = ProfileString("myapp","setup","Toolbarpos", "1")
Choose Case ls_pos
Case "0"
This. ToolbarAlignment = AlignAtTop!
Case "1"
……
End Choose
2005-4-13 19:52
kevin1982
9.2 关于菜单的编程(2)
9.2.6 修改工具条上鼠标右键弹出菜单中的内容
应用对象的属性ToolBarUserControl控制着用户是否可以在工具条上使用右键弹出菜单,该属性缺省是True。右键弹出菜单可以控制工具条的很多属性,但是美中不足的是该菜单项都是英文,可以使用应用对象的属性ToolBarPopMenuText来改变该弹出菜单的文字内容。弹出菜单的内容如图9-3所示。弹出菜单中包括三部分,最上面的两项来自应用对象的ToolBarFrameTitle属性和ToolBarSheetTitle属性,中间和最下面两部分的文字内容可以使用应用对象的属性ToolBarPopMenuText来修改成其他的文字。可以在Frame的打开事件中编写如下脚本:
GetApplication().ToolBarPopMenuText = "左侧,顶部,右侧,底部,浮动,文字,提示信息"
文字以逗号分隔,如果多了则被自动忽略。只能修改该弹出菜单中的文字,但不能修改对应各项的功能。其中函数GetApplication可以获取当前应用的句柄(handle)。也可以在Frame的打开事件中使用下面的语句来限制用户不准使用工具条上的弹出菜单:
GetApplication ().ToolBarUserControl = False
9.2.7 动态设置MicroHelp
使用函数SetMicroHelp可以动态改变菜单项的MicroHelp内容。该函数的语法是:
windowname.SetMicroHelp ( string )
其中windowname是菜单所挂接的窗口名称,string是要设置的MicroHelp内容。函数正确执行返回1,否则返回-1,如果有参数为Null则返回Null。一般在菜单项的Selected事件中使用该函数,下面是一段将MicroHelp修改为“测试修改SetMicroHelp函数”,脚本如下:
parentwindow.SetMicroHelp("测试修改SetMicroHelp函数")
如果在定制Frame窗口的控件中修改MicroHelp,还得在相关事件中取消MicroHelp。一般在控件的GetFocus事件中显示MicroHelp提供关于该控件的解释信息,在LoseFocus事件中将MicroHelp修改为“Ready”,脚本如下:
在控件的GetFocus事件中:
Parent.SetMicroHelp("该控件的帮助信息")
在控件的LoseFocus事件中:
Parent.SetMicroHelp("Ready")
一个保存MicroHelp的好方法是使用控件的Tag属性,在窗口画板中给每个要显示MicroHelp的控件的Tag属性都指定适当的内容,然后在需要改变MicroHelp的脚本中将Tag属性作为函数SetMicroHelp的参数即可。
9.2.8 使用右键弹出菜单
弹出式菜单通常在用户点击鼠标右键时显示,而一般可视控件都具有事件RbutTonDown,可以在该事件下使用函数PopMenu来显示弹出式菜单。该函数的语法是:
menuname.PopMenu ( xlocation, ylocation )
其中menuname是要弹出的菜单的名称,可以是某个下拉菜单中的某个下拉项目或者某个级联菜单。 xlocation和ylocation是菜单显示的位置,该坐标的取值是相对于窗口而言的。通常使用函数PoInterX和PoInterY来获取该坐标,以便在鼠标点击的位置弹出菜单。这两个函数的语法格式相同,PoInterX的格式是:
objectname.PoInterX ( )
其中objectname是控件名称,X是鼠标点击位置和objectname左边框的距离,缺省为脚本所在控件。在和函数PopMenu搭配使用时应该获取相对于窗口而言的坐标位置。虽然每个控件的RbutTonDown事件中都有xpos和ypos参数可用,但这都是相对于控件而言的,下面的脚本都不会使弹出菜单在鼠标点击位置显示(除非该控件的起始坐标为(0,0)):
m_main.m_文件.PopMenu(xpos,ypos)
或者:
m_main.m_文件.PopMenu(PoInterX(),PoInterY())
上面两种语句的执行效果是完全相同的。正确的编程应该是在控件的RbutTonDown事件中编写如下脚本:
m_main.m_文件.PopMenu(Parent.PoInterX(),Parent.PoInterY())
在窗口的RbutTonDown事件中使用上述两种方式的哪一种都完全可以。
9.2.9 改变窗口中的菜单
函数ChangeMenu可以动态改变窗口的菜单。它的语法格式是:
windowname.ChangeMenu ( menuname {, position } )
其中,windowname是要改变菜单的窗口的名称;menuname是要挂接到窗口上的菜单的名称;position是可选参数,仅用于MDI Frame窗口中,用来指示给当前的窗口列表中追加的菜单标题的位置。缺省值为1,这时所有打开的Sheet出现在第一个菜单标题的下拉式菜单的底部。例如,下面的语句改变窗口w_employee的菜单为m_emp1:
w_employee.ChangeMenu(m_emp1)
9.2.10 设计一个通用工具条配置窗口
只要使用菜单一般就提供工具条,就涉及工具条的设置问题。可以将这个普遍性的问题用通用的方法解决。设计一个通用的工具条设置窗口,界面如图9-4所示。在需要进行工具条设置的窗口菜单项Clicked事件中使用带参数方式打开该窗口,语句如下:
OpenWithParm(w_setToolbar,parentwindow)
其中窗口名称为w_setToolbar,在它的打开事件中根据当前工具条的情况初始化该窗口中控件的属性。打开事件的脚本如下:
iw_window = message.powerobjectparm
/* 根据当前工具条的位置设置单选按钮 */
Choose Case iw_window.ToolbarAlignment
Case AlignAtBotTom!
rb_botTom.Checked = True
Case AlignAtLeft!
rb_left.Checked = True
Case AlignAtRight!
rb_right.Checked = True
Case AlignAtTop!
rb_Top.Checked = True
Case Floating!
rb_floating.Checked = True
End Choose
/* 根据工具条的是否可视来设置按键cb_visible上的文字 */
If iw_window.ToolbarVisible Then
cb_visible.Text = "隐藏"
Else
cb_visible.Text = "显示"
End If
/* 根据工具条是否显示文字来设置“显示文字”复选框的状态 */
cbx_showtext.Checked = GetApplication ( ).ToolbarText
/* 根据工具条是否显示提示信息来设置“显示提示信息”复选框的状态 */
cbx_showtips.Checked = GetApplication ( ).ToolbarTips
在单选按钮“左侧”的Clicked事件中编写如下脚本:
iw_window.ToolbarAlignment = AlignAtLeft!
对其他单选按钮也做类似的编程,来设置工具条的显示位置。
在“显示文字”复选框的Clicked事件中编写如下脚本:
GetApplication ( ).ToolbarText = this.Checked
因为ToolbarText是应用对象的属性,所以上面的语句中用到了函数GetApplication。该属性的取值为Boolean型,和复选框的Checked属性类型相同。
在“显示提示信息”复选框中也做类似的编程:
GetApplication ( ).ToolbarTips = this.Checked
在按键“隐藏”的Clicked事件中如下编写脚本:
If This.Text = "隐藏" Then
iw_window.ToolbarVisible = False
This.Text = "显示"
Else
iw_window.ToolbarVisible = True
This.Text = "隐藏"
End If
通过以上10个方面的编程,介绍了很多关于菜单项、工具条的属性、函数以及菜单的引用方法和技巧。
2005-4-13 20:32
kevin1982
[img]http://202.107.127.122/bbscs5/showimg?picdir=imgup/10/31/20050328/img_262197_1111980463395.gif[/img]
第9章 菜单和MDI
9.3 什么是MDI应用
MDI是Multiple Document Interface的缩写,在这样的应用系统中,可以同时打开多个窗口,每个窗口都被作为一个Document处理。同时打开多个窗口,用户可以很方便地在其间切换,以便获取关于当前工作更多的参考信息,更符合现实世界中的工作情况。试想在用户处理工资数据时,很有可能同时还要翻阅人事数据、出勤数据、甚至还有本月工资调整的红头文件,这都是进行工资处理必不可少的参考信息,如果不让翻阅这些数据,用户的工作该是多么别扭。MDI就是这样的应用,用户可以同时打开多个窗口,随意在窗口之间切换,工作流程完全由用户掌握,这样的应用系统界面更加友好。最典型的例子就是Excel。
用户在窗口之间的任意切换可以通过菜单进行,每个MDI都肯定有一个菜单,它是MDI应用不可缺少的对象。所以,本章将菜单和MDI应用放在一起介绍,除了介绍它们的设计方法,还介绍它们的编程技巧。
图9-5是一个MDI应用的外观。由六部分构成,其中工具条中的每个小图标代表某个菜单项的功能,是菜单项的快捷方式;状态条提供相应的帮助、提示信息,可以是关于菜单项的,也可以是关于工具条的,还可以是有关Sheet等其他所有可以出现在MDI应用中的对象的提示信息;Sheet是MDI应用中的重要的对象,可以是任何非MDI类型的窗口,是执行用户工作的;客户区是用户的工作区间,所有Sheet都显示在该区域;MDI框架是包含客户区在内的窗口框架,也叫做Frame。在这六部分中,Frame和菜单中都需要编写脚本,脚本中可能用到这六部分各自的属性、特有的函数,在后面都将详细介绍。
9.3.1 菜单条和工具条
菜单是MDI应用中必不可少的,不仅Frame必须有一个菜单,就连在客户区打开的各个Sheet也应该有菜单,如果没有就会自动继承前面Sheet的菜单。有两种通常的方式来为MDI的Sheet表提供菜单:在框架和每个Sheet之间共享一个菜单,或者分别为每个Sheet提供一个单独的菜单,当然也可以这两种方式混合使用。当打开多个有菜单的Sheet时,只有当前Sheet的菜单显示在Frame顶部,其他Sheet的工具条可以显示,而它们的菜单不显示。菜单提供各个Sheet间切换的功能,以及在Sheet中能够完成的绝大部分功能。每个菜单可以有自己对应的工具条,也可以没有,还可以对应多行工具条,这些都是在菜单画板中设计的,在后面设计菜单时加以介绍。
工具条是一个对界面有非常强的装饰效果的对象,并且使用户的操作也会很方便。但如果没有相应的提示、帮助信息,用户很难记住众多小图标各自的功能,开发人员在设计时应该考虑到用户的这点难处。首先小图标的图片应该有一定的含义,有很多的PowerBuilder开发人员仅仅随便使用一个PowerBuilder自带的小图标就了事,显然是对这个问题没有重视,而这一点对用户是否愿意熟悉工具条的功能有非常大的影响,以至于很多PowerBuilder应用软件中工具条仅仅是一个装饰,而不是一个快捷的工具。奉劝开发人员不要把工具条在用户心目中改成装饰条。其次,工具条中每个小图标都可以显示一定的文字内容,简短的描述文字对用户有很大的提示作用,没有哪个用户不愿意使他的工作效率更高,这些文字内容将会使用户很乐意去熟悉工具条。可以通过属性ToolBarText来设置。但小图标上的空间毕竟有限,不能显示更多的文字,如果能够再显示更为详细的信息,这是哪个用户都不会反对的事情。我经常看见用户在拿到新的应用软件时,将鼠标停留在工具条上然后在屏幕上到处寻找是否有更多的信息出现,可见用户有这方面的强烈要求和使用经验,而开发人员千万不能成为消灭用户要求的杀手。在小图标上面停留片刻后的提示信息称为ToolBarTips,可以在设计菜单时指定,该提示信息一般也比较简短。更长的提示信息可以在MDI窗口底部状态条中显示,该信息称为MicroHelp,既可以在设计菜单时指定,也可以在脚本中使用函数SetMicroHelp来动态设定。所有这些提示信息、属性的设定都将在菜单设计中详细介绍。
9.3.2 客户区
客户区是MDI窗口中用户的工作区间,Sheet都是在该区域显示的,它们的显示范围不会超过该区域。如果在MDI框架中放置了控件,这样的MDI窗口称为定制Frame,否则就称为标准Frame。客户区实际上是一个非常特殊的控件,该控件叫做mdi_1,只有当运行时才可用。当使用定制Frame时mdi_1控件不能正确显示在可视区域,这时打开Sheet也不会正常显示,应该在Frame的Resize事件中编写脚本,对mdi_1重新进行定位。假设在Frame的上部放置了命令按键cb_1,在命令按键的下面和状态条之间显示mdi_1。脚本如下:
UInt li_x,li_y,li_width,li_height
li_x = WorkSpaceX() //获取工作区的起始坐标x
li_y = cb_1.y + cb_1.Height //获取命令按键cb_1下部的坐标
li_width = WorkSpaceWidth() //获取工作区宽度
//获取命令按键和状态条之间的高度
li_height = WorkSpaceHeight() - li_y - mdi_1.MicroHelpHeight
mdi_1.Move(li_x,li_y) //将mdi_1移动到指定坐标位置
mdi_1.Resize(li_width,li_height) //按指定的宽、高改变mdi_1的大小
上述脚本中的函数WorkSpaceX、WorkSpaceWidth、WorkSpaceHeight和另外的函数WorkSpaceY用来获取关于工作区域的参数,使用方法都比较简单,只要注意这些函数都不包括工具条所占用的区域。通过上述脚本,mdi_1控件就可以正确显示在Frame的可视范围内,再打开Sheet也不会出现什么异常了。
当使用标准Frame时,系统会自动维护客户区的位置和大小。
2005-4-13 20:33
kevin1982
第9章 菜单和MDI
9.4 菜单和MDI(1)
菜单是MDI应用中非常重要的控件,用来执行动作、导航Sheet。所以,熟练开发MDI应用系统必须要掌握菜单和MDI的关系,这包括两个方面,一个是 菜单和Frame的关系,一个是菜单和Sheet的关系。
在MDI应用系统中,Frame窗口必须包含一个菜单,该菜单可以有工具条也可以没有。在Frame菜单中可以编写打开Sheet的脚本。Sheet并非特殊类型的窗口,只是在Frame中打开就称为了Sheet,实际上就是非MDI类型的窗口。虽然Sheet就是普通的窗口,但不能使用Open函数打开,要使用OpenSheet或者OpenSheetWithParm函数来打开。Sheet可以有菜单也可以没有菜单。当打开带有菜单的Sheet时,Sheet的菜单自动取代Frame的菜单;当打开没有菜单的Sheet时,该Sheet自动使用Frame的菜单(当然,可能是Frame本身自带的菜单,也有可能是其他Sheet的菜单)。这时就出现了问题,当打开带菜单的Sheet时,Sheet的菜单覆盖了Frame的菜单,Frame菜单中的功能如何执行?好的解决方法就是使用工具条。Sheet的菜单替换了Frame的菜单后,Frame的工具条始终可以显示,使用工具条照样可以正常执行Frame菜单的功能。所以,在创建Frame的菜单时,好的习惯就是要同时创建菜单的工具条。但是,使用这种解决方法时,工具条中小图标功能的易记易用性就非常重要了,开发人员应该尽可能提供更多关于小图标的信息,如MicroHelp,Tips和 ToolBarItemText等,这些在“设计菜单”一节中已经详细介绍过了。另外一种解决方法可能有一定的适用情况,就是让所有的Sheet都共享Frame的菜单,当所有的Sheet都进行同一类型或相似类型的操作时,比如像微软的Word似的,Sheet都进行文件编辑的情况下,这种方法就比较实用,菜单的维护就比较简单。
在清楚了菜单和Frame及Sheet之间的关系后,还应该掌握一些常用的编程方法和技巧。在Frame菜单中打开Sheet、在Sheet之间导航,获取当前Sheet和安排Sheet的布局等都是最基本的操作,根据用户的操作情况维护菜单项是否可用,获取工具条的信息及保存工具条的设置等都是常用的技巧,遍历菜单中的各个菜单项在某些特殊情况下也能用到。下面对这些情况下的编程一一加以介绍。
9.4.1 打开Sheet
有两个函数可以用来打开Sheet,其中OpenSheet函数不给Sheet传递参数,而函数OpenSheetWithParm可以在打开Sheet的同时传递参数。OpenSheet的语法格式是:
OpenSheet(Sheetrefvar{,windowtype},mdIframe{,position{,arrangeopen}})
其中,Sheetrefvar是要打开的Sheet的名称(注意,不能是MDI类型的窗口);mdIframe是Frame的名称,表示要在这个Frame的客户区打开名称为sheetrefvar的Sheet;这两个参数指明了在哪个Frame中打开哪个Sheet,都是必须的。变量windowtype为String类型,用来表示要打开的窗口的名称,如果指明该参数,则sheetrefvar的类型必须是能够接受window类型的赋值,这时的函数表示要打开windowtype名称的窗口,并将其实例保存在变量sheetrefvar中,可以使用该变量引用这个Sheet。position是一个Integer类型的参数,表示在菜单条中的位置,新打开的Sheet名称作为一个菜单项添加在该位置的下拉菜单中。菜单条最左面的项目位置为1,依次增加,当该参数大于菜单条中的项目数时则将Sheet名称添加到右侧倒数第二个菜单的下面。如果打开的Sheet数目超过9个,多余的则以级联菜单的形式显示在该菜单的more windows下。参数arrangeopen是指打开Sheet后在客户区中的布局方式,是ArrangeOpen枚举类型的值,有3个可以使用的取值。
l Cascaded!:将打开的Sheet层叠起来,使所有的标题栏都可见。这是缺省方式。
l Layered!:将打开的Sheet完全地覆盖在客户区,后打开的覆盖前面的Sheet。
l Original!:用窗口的原始尺寸打开,打开后并以Cascaded!方式安排布局。
下面是打开Sheet的实例。比如,在名称为MDI_my的Frame中打开窗口名称为w_edit的窗口,并将窗口w_edit的标题添加到Frame菜单的第三个下拉菜单中,打开后做Cascaded!方式的布局,可以使用下面的语句:
OpenSheet(w_edit,MDI_my,3,Cascaded!)
下面的例子是打开名称为w_edit的窗口的实例,并将该实例保存在window类型的变量wedit中,其他情况和上面的例子相同。语句如下:
window wedit
OpenSheet(wedit, "w_edit",MDI_my,3,Cascaded!)
最简单的情况,在名称为MDI_my的Frame中以缺省布局方式打开名称为w_edit的窗口,语句如下:
OpenSheet(w_edit,MDI_my)
该语句执行后,自动将窗口w_edit的标题添加到Frame菜单右侧倒数第二个下拉菜单中。
和OpenSheet函数类似,OpenSheetWithParm函数中的很多参数含义完全相同。其语法格式如下:
OpenSheetWithParm(Sheetrefvar,parameter{,windowtype},mdIframe{,position{,arrangeopen}})
其中parameter是要传递的参数,可以是String,Numeric或者PowerObject类型。该函数的用法及参数含义和OpenSheet完全相同,不再赘述。
9.4.2 获取当前Sheet
使用函数GetActiveSheet可以获取当前的Sheet,该函数的语法如下:
mdIframewindow.GetActiveSheet ( )
其中mdIframewindow是当前Frame的名称。该函数返回window类型数值,是当前活动的Sheet。执行过程中发生错误则返回无效数据,如果mdIframewindow为Null则返回Null。该函数经常和IsValid配合使用,来检查函数返回的窗口是否有效。下面是在一个菜单项的Clicked事件中的脚本:
If IsValid(ParentWindow.GetActiveSheet()) Then //如果当前有活动窗口
MessageBox("提示",ParentWindow.GetActiveSheet().title) //则显示活动窗口标题
Else //如果没有
Beep(2) //则响铃两声
MessageBox("提示","没有活动Sheet!") //并且显示提示信息
End If
在上面的脚本中用到了函数IsValid ,可以用它来判断指定对象是否有效,该函数语法为:
IsValid( objectname )
其中objectname是要进行是否有效判断的对象。该函数返回为Boolean类型的值。
9.4.3 安排Sheet的布局
使用函数ArrangeSheets可以安排当前打开的和最小化的Sheet的布局,该函数的语法是:
mdIframe.ArrangeSheets ( arrangetype )
其中,mdIframe为Frame的名称,arrangetype是arrangetypes枚举类型的数值,可以取为:
l Cascade!:将打开的Sheet层叠起来,使所有的标题栏都可见。
l Layer!:将打开的Sheet完全地覆盖在客户区。
l Tile!:将打开的Sheet以平铺的方式从左到右排列。
l TileHorizontal!:将打开的Sheet并排放置。
l Icons!:仅将最小化的Sheet沿Frame的底部放置。
该函数正确执行则返回1,否则返回-1,如果有参数为Null则返回Null。在Frame的菜单项“平铺工作表”中可以编写下面的脚本:
ParentWindow.ArrangeSheets(Tile!)