PB编程,API函数和PB编程绝招

发布时间 2023-05-26 09:47:15作者: djd66

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)表示在后台循环播放。

[此贴子已经被作者于2005-4-14 8:12:41编辑过]

 

[ 本帖最后由 kevin1982 于 2006-5-13 20:46 编辑 ]

 

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

 

数据表操作(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

 

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) + &amp;

 

",错误信息:" + 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

 

数据缓冲区

 

在应用程序运行时,用户对数据窗口进行的操作情况是很复杂的,删除、插入、修改等操作有可能都会发生。记录哪些数据修改过、哪些数据需要删除、哪些数据是新增加的等等,都是很重要的工作,以便数据保存时能够正确修改数据库。这些工作都是由数据窗口缓冲区来完成的。

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("删除","是否真要删除姓名为" + &amp;

 

dw_cur.GetItemString(dw_cur.GetRow(),"name") + "的数据?",&amp;

 

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() &gt; 0 Then //如果有删除了的数据

 

If MessageBox("恢复","是否真要恢复姓名为" + &amp;

 

dw_del.GetItemString(dw_del.GetRow(),"name") + "的数据?",&amp;

 

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

 

查询和排序(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() &lt; 1 Then return //如果数据窗口中没有数据则直接返回

 

ll_find = dw_1.Find("left(name,2) = '" + Trim(sle_1.text) + "'",1,dw_1.RowCount())

 

If ll_find &gt; 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

 

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 &lt;= 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()))&gt;=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(),没有修改数据窗口对象的打印设置。后面章节详细介绍开发一个通用的可以设置打印机、打印份数、打印方向、打印纸张、打印哪些页面等等很多选项的通用的打印配置窗口