主题文章___数据空间___维知工作室

（上一页）（下一页）
PB相关 |  | 资料来源：
Table | （20）PB修改数据库表结构的技巧 | 李清
System | （19）用PB5设计串口通讯程序 | 王学军
TreeView | （18）在PB的TreeView控件中实现自动查询 | 周剑峰
Datawindow | （17）PowerBuilder数据窗口编程技巧十则 | 蔡伟龙
System | （16）如何在POWER BUILDER中使用WINSOCK控件 | 达伟春
OTHER | （15）在PB打印中实现人民币大写 | 侯晓伟
MENU | （14）用PB做一个通用右键菜单 | 田俊国
SQL | （13）SQL基础 |
System | （12）用PowerBuilder开发多媒体播放器 | 河北 黄万璞
控件 | （11）动态创建可视控件的基本方法 | 鹿寒
WEB | （10） 用PowerBuilder创建Internet应用 | Sybase 软件(北京)有限公司 霍军
SQL | （9）同时访问多个数据库 | 大庆石化总厂信息中心 (邮编163714) 李畅
System | （8）如何在PB中使用OCX控件？ | 陶清网站
Datawindow | （7）美化DataWindow的显示效果 | 程序中心PB站
Datawindow | （6）当Retrieve时不清除原有Datawindow数据 | 程序中心PB站
Datawindow | （5）当表(Table)的定义发生改变以后同步DataWindow columns | 程序中心PB站
Datawindow | （4）在表格(Grid)风格的DataWindow放入标题 | 程序中心PB站
Datawindow | （3）使用Line建立漂亮的Grid DataWindow的技巧 | 程序中心PB站
System | （2）使Sybase SQL Anywhere的启动窗口不出现在任务栏 | 程序中心PB站
SQL | （1）使用SQL产生BCP命令快速备份/恢复你所有数据 （仅用于Sybase和MS SQL Server数据库) | 程序中心PB站

（上一页）（下一页）


PB相关 |  | 资料来源：
System | （40）Delphi4.0的Winsocket编程 | 蒋立冬
Script | （39）如何在PB中切换中英文输入法 |
Script | （38）PowerBuilder与外界的交互方式 | 天顺公司软件部 陈俊明
Script | （37）在MicroHelp Bar上显示时间 |
Script | （36）PowerBuilder下实现多用户安全访问 | 张锟
ODBC | （35）PB内置OEACLE数据库接口的使用方法 | 马长明
Print | （34）PB中打印预览的实现 | 北京燕化集团化工二厂动力处 刘荣芳
System | （33） PB调用外部程序及判断其完成的方法 | Panya整理
Struct | （32）结构体与blob数据的转换 | 编辑 PB能量建造者
Print | （31）PowerBuilder中数据窗口的精确页面打印技 术 | 中国计算机世界 国永杰 张颖
System | （30）在PB中动态调用函数 | 编辑整理 PB能量建造者
ODBC | （29） 在PB程序中实现ODBC数据源的添加 | 作者：鹿寒
System | （28）如何在PB中调用RAS | panya编辑整理
WINDOW | （27）Powerbuilder 中制作进度条 | panya整理，同事程先生提供
System | （26）将窗口最小化为时钟旁的小图标 | 编辑整理：panya
install shield | （25）PB应用程序所需的运行环境 | 鹿寒
WINDOW | （24）在PB窗口之间传递参数的方法 | panya
WINDOW | （23）PB中一个类Word打印对话框的实现 | 许昌市电力设计研究所 高灵超
OLE | （22）PB6.5中有关OLE控件功能的三个缺点 | 北京安美建
Datawindow | （21）数据窗口快速点击时出现错误的解决 | 北京天顺公司软件部 陈俊明


PB相关 |  | 资料来源：
Other | （60）关于PB应用程序的2000年问题 | 广东核电合营有限公司 步建华
COM | （59）与Excel通讯 | Adam Kierce
COM | （58） PowerBuilder与外界的交互方式 | 天顺公司软件部 陈俊明
InstallShield | （57）PowerBuilder 5.0 InstallBuilder 使用技巧 | WJK
System | （56）用PB5设计串口通讯程序 | 石家庄铁道学院计算中心 王学军
InstallShield | （55）用InstallShield制作专业Windows安装软件[1999-06-01] | 苏贵洋
Datawindow | （54）在一个DataWindow中修改多个Table | http://techinfo.sybase.com
Script | （53）允许从空的非字符字段跳离 | http://techinfo.sybase.com
Datawindow | （52）数据窗口间的数据传送方法 |
System | （51）在PB6.5中利用Win9X的OCX控件美化程序界面 | 广东省韶钢集团财务部 赖晓敏
Script | （50）PB中实现图片按钮的mousemove事件 | 四川省绵阳市 李万昌
SQL | （49）触发器一 触发器介绍 |
SQL | （48）存储过程一 存储过程介绍 |
SQL | （47）视图 |
SQL | （46）事务处理 |
Menu | （45）PowerBuild 的高级菜单设计技术 | 武警学院计算机中心 程连孚
SQL | （44）数据管道 |
Script | （43）向窗口传递多个参数 |
Script | （42）在PowerBuilder中更改窗口继承 | 柯建勋 http://kjx.126.com
SQL | （41）PB：关于触发器的应用 | TQ


PB相关 |  | 资料来源：
Table | （80）Powersoft资料库在系统开发中的应用 | 军械工程学院计算机室 谭月辉
Print | （79）PB打印机选择列表的分析与实现 | 江苏省姜堰市第五中学 李中华
All | （78）PowerBuilder之我见 | 飞马
Datawindow | （77）引用其他行的数据 |
Datawindow | （76）使可编辑的数据窗口只读 |
Datawindow | （75）查看DataStore中的内容与变化 |
Datawindow | （74）高亮显示分组条 |
System | （73）让应用程序只运行一次 |
控件 | （72）在EXE文件中注册OCX部件 |
System | （71）播放声音文件 |
Table | （70） PowerBuilder 5.0对SQL Server 6.0数据库的访问 | 胜利石油管理局钻井工艺研究院计算机所 李 玉 (257017) 胜利石油管理局钻井地质公司 李 健 山东东营外经委 卢文纲
WEB | （69） CGI环境变量的解说 | 天顺公司软件部 陈俊明
WEB | （68）在PB 应 用 中 收 发 电 子 邮 件 | 广 东 珠 海 卞 国 斌
WEB | （67）使用WEB.PB时系统的文件配置 | 天顺公司软件部 陈俊明
PFC | （66） PFC方法与老方法 | Alexander Kjerulf 96.12.8
PFC | （65）PB 5中PFC的使用 | 中原石油勘探局计算中心系统集成部 刘少青
PFC | （64）怎样实现一个列焦点指示器PFC数据窗口服务（译得不全） | Alexander Kjerulf
PFC | （63）不要害怕PFC（译文） | Dana P. Crenshaw
Table | （62）PowerBuilder系统表 | 摘自Sybase China LTD.《PowerBuilder用户手册》
Install Sheild | （61）也 谈 用InstallShield 制 作 纯 中 文 化 的 安 装 程 序 | dbzhou@163.net


（上一页） （下一页）
PB相关 |  | 资料来源：
DBA | （100）Power Builder同时连接多个数据库 | info web
DBA | （99）使Sybase SQL Anywhere的启动窗口不出现在任务栏 | info web
system | （98）取得DOS环境变量值 | info web
howto | （97）启动PB时打开库画板 | info web
script | （96）给tab page增加快捷键 | info web
system | （95）如何运行自己的应用程序 | info web
datawindow | （94）如何终止retreive | info web
system | （93）如何获得机器的IP地址 | info web
datawindow | （92） PB中如何得到Crosstab中的列名 | 北京市·清河 赵韶平
DDE | （91） 用PowerBuilder 5.0 开 发DDE 应 用 程 序 | zhou_haoli@ccw.com.cn
OLE | （90）OLE 在PB 数 据 窗 口 中 的 实 现 | 江苏省盐城市社会劳动保险处 丁铖
Script | （89）介绍一些datawindow 未定义的 events |
Datawindow | （88） 怎样刷新下拉式数据窗口 | 李金山
SQL | （87）在PowerBuild函数中使用SQL语言 | 汪祥
Script | （86）PB中窗口自动居中 | 李晶
Datawindow | （85）PB数据窗口实现报表输出 | 褚健耘
System | （84）在PowerBuilder中创建图标栏应用 | 国防科技大学理学院 陈玉教
Datawindow | （83）Retrieve后不回卷的数据窗 |
Datawindow | （82）数据从文本文件写入数据库表的捷径 |
SQL | （81）Powerbuilder中游标的应用 | 山东胶州 孙亚冰

（上一页） （下一页）


使用SQL产生BCP命令快速备份/恢复你所有数据（仅用于Sybase和MS SQL Server数据库)

BCP命令是Sybase和MS SQL Server用来备份和恢复数据用的工具，它使用方便，备份/恢复速度快。当Table过多时，
编写批处理是一件繁琐的事情。可以使用下面方法快速生成BCP的批处理
select 'bcp database..' + name + ' out ' + '/data/' + name + '.out' + ' -n -Sservername -Usa -Pxxx' from database..sysobjects where type = 'U' 将上面database换成自己需要备份的数据库名称,
-Sservername改为对应SQL Server名称 -Pxxx 将xxx换成实际sa密码，上面语法是用来备份数据，将第一行中
的out改为in即可生成恢复数据的批处理 以上可以在Sybase或MS SQL Server的ISQL中执行(MS SQL Server 7.0中ISQL已变为Query Analyzer），然后将执行结果通过剪贴板Copy到记事本
（注意不要Copy结果集的标题)，保存为Bat文件。在执行最后的Bat文件时，
需要在Bat所在目录建立Data子目录，备份的*.out文件将存放在此子目录下。 也可以通过PB的Database Administration中执行(需要最后补充;号才可以执行)，然后将结果保存为Text类型，改名为Bat文件。


使Sybase SQL Anywhere的启动窗口不出现在任务栏

Sybase SQL Anywhere在启动时，会在Win95任务栏出现一个图标，如果不小心shutdown
或关掉此窗口，程序将不能运行，这种情况在设计下还不严重，但一旦给用户安装系统后，
在任务栏出现窗口即不美观又不安全，还得多废口水给用户解释。按照下面方法可去掉任务
栏上的提示。 在[控制面板]或PB中配置ODBC，指向你原来设置的数据源，然后单击“配置”
按钮， 再选择“Options...”按钮，将Start Command由下面： 原来语法：
c:\SQLAny50\Win32\dbeng50.exe -d -c512 改为： c:\SQLAny50\Win32\dbeng50.exe -d -Q -c512
即多加一个 -Q （注意Q一定是大写）


使用Line建立漂亮的Grid DataWindow的技巧

虽然PowerBuilder有Grid风格的DataWindow，但不够灵活，如果想打印一张边框粗线条或双线，
内框为细线条的表格直接使用Grid风格的DataWindow就不能实现。因此许多人都用Tabular风格
的DataWindow，然后自己用Line来实现Grid。 在调整线条位置是一个即伤脑筋又伤眼睛的事件，
而一旦需要调整行高，所有工作都得重来，试一试用下面方法，会大大减轻你的工作量： 在vertical lines，改变它的属性，在Expressions Tab上，y1行上输入0，y2行上输入rowheight() - 1 在
horizontal lines, 设置 y1 和 y2 = rowheight() - 5 这样PowerBuilder会自动调整线的坐标，
你只需要设置横线的长度和x1,x2的坐标即可。 马上Preview一下，看一看效果！ 你再也不需要因为行的高度发生改变而重新调整线条位置。


在表格(Grid)风格的DataWindow放入标题

在国内企业的报表表现形式通常以表格居多，PowerBuilder提供了表格风格的DataWindow，
但打印表头需要稍稍下一点功夫。 第一步：先放一个Text(文本框)对象到DataWindow的表头上，
在此Text上点击鼠标右键选择Properties->Postion->layer,将layer改为Foreground,这样你就
可以改变Text的位置和宽度，然后将此Text的内容改为正确的报表标题即可，注意还需要将
此Text的背景改为非透明色，否则会看到表线。 第二步：按第一步做完以后，
当用户拖动Column改变宽度时，Text框是不会自动变化大小，可以有两种方法处理此问题。
一是改动DataWindow的设置，不允许用户改动Column宽度，这种方法可用于报表为固定格
式，另一种方法是通过读取DataWindow的Syntax.Modified属性来动态设置Text的宽度。


当表(Table)的定义发生改变以后同步DataWindow columns

有许多普遍现象是当DataWindow建立以后发现原来字段长度不够而改动Table。
例如：将原字段由Char(2)改为Char(4),同步的方法是打开原Datawindow，选择菜单Design->Data Source，先单击此字段(改为不选择状态)紧接着再次单击此字段（重新选择此字段）；如果你的Data Source使用的是SQL语法而不是图形方式，则先将此字段加亮选择以后，用“剪切”然后“粘贴”的方法同步。 返回DataWindow设计窗口，PowerBuilder已经自动将Column的Type改正，可以通过菜单Row->Column specifications...查看结果。 另外，如果此字段可编辑，还需要查看它的编辑风格(Edit Styles)是否正确，特别改动是Limit，应从2改为4。


 当Retrieve时不清除原有Datawindow数据

当你调用Retrieve函数，PowerBuilder自动清除原有DataWindow然后Retrieve数据。在Datawindow RetrieveStart事件中，使用Return 2，这样PowerBuilder不会清除原有数据而是追加新数据。


 美化DataWindow的显示效果

使DataWindow的单双行显示颜色不同，不仅仅可以使你的应用程序更显专业性，并使数据
的可读性增强。先调出你需要改动的DataWindow，在Detail band按下右键选择Properties，
选择Tab页中的Expressions，在color属性中输入下面内容：（注意是在Detail明细显示段按
右键，而不是在Column上) IF(MOD(GETROW(),2)=0,RGB(192, 192, 192), RGB(255, 255, 255)) 马上Preview一下，看一看效果如何。


如何在PB中使用OCX控件？

从PB 5.0开始支持OCX，使用方法如下（在PB 5.0自身的例程中就有关于OCX的使用，如Formula One)： 第一步：建立一个新Window，选择菜单Controls->OLE,然后选择第三个tab页Insert Control,从列表中选择你需要使用的OCX控件放置到Window上。 第二步：使用OCX的方法(function)和属性(properties)，通常OCX的在线帮助都是对于
VB和C而言，而PB中调用时，需要在对象名和方法/属性之间加下Object，例如： 调用方法
ole_1.object.save() 设置属性 ole_1.object.x = 100 如果需要在设计环境设置OCX属性，
可以在OCX上单击鼠标右键，选择ocx properties...即可


使用PowerBuilder同时访问多个数据库

大庆石化总厂信息中心(163714) 李畅

PowerBuilder 4.0是一套强大的数据库应用系统开发工具,它具有完整的客户/服务器体系结构、
面向对象的应用程序开发方法以及可视化的开发环境,因而成为近几年流行的数据库前端开发工具。 随着网络的延伸和信息系统的不断开发,系统集成面临着如何将分布在不同服务器上的数据库系统
集成到一起的问题。如下图所示: 某个客户应用可能需要在同一窗口同时访问两个服务器上的
两个不同种数据库,PowerBuilder为这种需求提供了令人满意的解决方案。
方案一: 通过Transaction Object同时打开多个事务,再通过多个事务存取建立在不同数据库上的DataWindows。 方案二: 通过数据管道(DataPipeLine)将其它数据库上基表中的数据传送到主数据库上,
对数据管道还可加上过滤条件,以便只传送需要的数据。
具体实现方案
本文在一个窗口上的两个按钮具体显示方案一和方案二,这个窗口如右图所示。
在这个窗口中的三个事件脚本(Scripts)如下:
(1)打开窗口事件脚本(Open for W_LC) Transaction lc_oracle,lc_ingres ∥初始化oracle数据库事务 lc_oracle=CREATE transaction lc_oracle.DBMS ="OR7 ORACLE v7.x"
lc_oracle.Database ="ora7" lc_oracle.logId ="Ljb"
 lc_oracle.logPassword ="Ljbljb" lc_oracle.ServerName ="@t:132.147.111.8" ∥初始化ingres数据库事务 lc_ingres=CREATE transaction lc_ingres.DBMS ="ODBC"
lc_ingres.Database ="xxzx2" lc_ingres.userid ="nt"
lc_ingres.dbpass ="ingres"
lc_ingres.dbparm ="ConnectString='DSN=kfml;& SRVR=nt;DB=xxzx2;OPTS=;UID=ingres'" (2)同时访问两个数据库的脚本(Clicked for CB_MutiDBMS) ∥访问 oracle数据库 connect using lc_oracle; dw_oracle.SetTrans(lc_oracle) dw_oracle.retrieve() ∥访问ingres数据库 connect using lc_ingres; dw_ingres.SetTrans(lc_ingres) dw_ingres.retrieve()
(3)数据管道的脚本(Clicked for CB_PipeLine) /*说明一个支持数据管道的对象,lc_object是在 User Object Painter中定义的PipeLine对象*/
lc_object lcl_object ∥启动数据管道 integer start_result connect using lc_oracle; connect using lc_ingres; lcl_object=create lc_object /*lc_pipeline是在PipeLine Painter中定义的数据管道,以便将ingres基表中的数据传送到oracle数据库基表中*/ lcl_object.dataobject="lc_pipeline" start_result=lcl_object.start &(lc_oracle,lc_ingres,dw_oracle)
If start_result=-3 messagebox("数据管道错","此基表已存在");
return end if disconnect using lc_oracle; disconnect using lc_ingres;
小结 PowerBuilder不仅为应用系统的快速开发提供了强有力的支持工具,而且为30多种数据库提供了直接连接或
ODBC连接,再加上Transaction Object和Data PipeLine同时存取多个数据库的功能,为大中型企业在
信息系统建设的中后期完成系统的互连提供了简捷有效的方法。


用PowerBuilder创建Internet应用

Sybase 软件(北京)有限公司 霍军

Internet和World Wide Web(WWW)已经风靡全球,在WWW上有上百万的商业机会。通过WWW,
可以立即给用户提供信息,提高用户的满意程度。还可以提供更好的通讯和信息访问,提高企业内部的生产率。 要充分发挥这些优势,企业必须改变它们在Web节点上的"市场小册子",并且提供动态的、数据驱动
内容的节点。它们需要开发工具开发这些新的Internet应用。 新的Internet开发工具不断涌现,PowerBuilder 5.0就是其中之一。它包含了很多新的Internet特性,使得开发者可以构造动态的Web节点,比如:
·支持"ActiveX控件";
·数据窗口可以将任何数据库中表的数据存储为HTML格式文件;
·数据窗口的Plug-in功能,可以让浏览器在Internet上查看报表;
·窗口的Plug-in功能可以在WWW浏览器上运行PowerBuilder应用; ·Web.PB用于创建动态的、基于Web的应用服务器。 PowerBuilder是一个较优秀的RAD(快速应用开发)工具,非常适应Internet的开发需求,其表现如右表所示: 下面我们详细讨论PowerBuilder 5.0的Internet特性。
支持ActiveX PowerBuilder提供对Mcrosoft ActiveX Internet控件的广泛支持。
使用从第三方厂家提供的ActiveX控件,可以为PowerBuilder应用增加各种Internet功能,
例如: ·WWW HTML浏览器功能; ·FTP控件用于传输和共享文件; ·NNTP控件访问新的群件; ·POP控件用于Internet电子邮件服务。 表1 例如,如果希望PowerBuilder应用的最终用户可以在PowerBuilder应用的窗口中浏览WWW。
通过使用Visual Components的WebViewerActiveX,在窗口中嵌入一个HTML Web浏览器并且
可以通过编程控制它。 开发者可以使用WebViewer内置的函数并且允许用户输入URL(Uniform Resourse Locator),以及在Web节点间向前和向后移动。这个控件同时还提供很多属性和方法允许开发者
通过编程控制它的功能。例如,开发者可以使用PageURL属性和PowerScript语言设置URL地址。
开发者还可以通过截断到其它Web节点的连接请求来限制PowerBuilder应用的用户只访问公司的主页。 使用ActiveX的优点表现在以下几个方面:
·扩展PowerBuilder客户/服务器应用;
·开发者可以完全通过编程控制这些控件并且可以定制应用的Internet功能;
·市场上有上百个可供选择的ActiveX组件; ·不必重复工作——使用预制的、测试过的Internet组件。

数据窗口生成HMNL文件 数据窗口技术是PowerBuilder访问数据库和进行数据表现的重要技术。数据窗口提供了鼠标点击方
式的SQL语句生成,并且可以将数据以各种形式展现给用户,包括图形方式、交叉表格形式、列表形式
和RichText格式。数据窗口可以将其中的数据存储为各种文件格式,例如dBASE、dif、SQL、Excel、
WKS、Wndows mctafile和Powersoft Report(PSR)。
PowerBulder 5.0现在支持一种新的存储格式HTML,可以将数据窗口中的数据存储为HTML文件格式。 使用数据窗口生成HTML文件的优点如下: ·无需HTML编程,就可以在Internet或Intranet上发布PowerBuilder或InfoMaker的静态数据和报表; ·将数据窗口生成的HTML合并到其它任何HTML页中,可以增强数据表现; ·最终用户可以不需要PowerBuilder就能在他们的PC机上查看相关的信息——只需要一个浏览器。
数据窗口Plug-in 数据窗口的Plug-in功能可以在Web页上显示Powersoft Report(PSR)文件,这必须在Netscape Navigator或Microsoft Internet Explorer 3.0上查看。用PowerBuilder DataWindow或者InfoMaker Report创建的报表,可以存储为PSR文件格式,这样就可以通过浏览器来访问。
这种报表保存了数据窗口中所有高级的格式,例如阴影、彩色文本和编辑风格等。 例如:要显示数据窗口文件compensation-report.psr,必须将下面的嵌入标记插入HTML页上: 〈embed src=compensation_report.psr width=650 height=350〉 一旦该HTML被装入,compensation_report.psr会自动从Web服务器上卸下。使用数据窗口的Plug-in功能用户可以使用滚动条浏览数据,打印数据窗口报表,还可以将报表存储成其它文件格式。
数据窗口Plug-in的优点表现在以下几个方面:
·在WWW上发布复杂的Data Window/InfoMaker报表,加快向WWW用户的发布; ·克服HTML的限制,提供复杂的图形报表,包括列表形式、交叉表格形式和图形方式。
·节省拷贝报表的时间和资金;
·可以在一个中央位置更新报表。 窗口Plug-in PowerBuilder窗口的Plug-in功能是一个将
客户/服务器应用扩展到Web上的技术。窗口的Plug-in功能允许在支持Plug-in的浏览器(如:Netscape Navigator、MicrosoftsInternet Explorer 3.0)上运行PowerBuilder应用。这种Plug-in 功能允许打开一个包含所有PowerBuilder标准控件的子窗口,例如tab控件、treeh和list view以及OLE对象。也可以 使用数据窗口技术,通过PowerBuilder的直接接口和ODBC接口访问各种数据源。另外,PowerBuilder窗口Plug-in应用可具有和分布式PowerBuilder应用的接口,这些应用可以运行在其它平台上,如UNIX或Windows NT。 PowerBuilder窗口包含在PowerBuilder动态库(pbd)中,放在Web服务器上。例如:要装入某PowerBuilder窗口, 在HTML页上嵌入如下标记: 〈embed src=windows_plugin.pbd width=370 height=320 window=w_product〉 这里,windows_plugin.pbd是PowerBuilder库的名称,w_product是由浏览器打开的子窗口的名称。width和height 定义了在浏览器中窗口的尺寸。可以同样用这个嵌入标记来指定其它需要的对象的PowerBuilder库,
并且可以用commandparm属性向窗口传递一个字符串参数。最终用户的PC机上必须安装有
PowerBuilder运行时所需的动态链接库,以便在WWW浏览器中运行窗口。 窗口Plug-in功能的优点
表现在以下几个方面:
·PowerBuilder窗口可以运行在WWW浏览器中,避免了编写大量的HTML和CGI程序; ·只做很小的修改就可以让现有的应用具有运行在Web上的能力; ·PowerBuilder支持的高级用户界面功能克服了HTML的不足;
·可以使用数据窗口的读/写 /更新和删除功能。 使用Web.PB进行基于服务器的开发 Web.Pb是一种新的技术,可以让PowerBuilder建立基于服务器的Internet应用,允许WWW浏览器访问分布式 PowerBuilder应用的服务。这些分布式的应用可以使用PowerBuilder所有的功能,例如数据窗口、
PowerScript、面向对象等,来建立基于服务器的应用。并且,PowerBuilder服务器可以动态生成
HTML页"飞到"或将HTML发送到 Web浏览器。这允许用户支持真正的"瘦客户机"体系结构,
也就是在客户机上只需要一个WWW浏览器。
Web.PB2体系结构如下: 如上图所示,Web.PB的体系结构非常简单,并且建立在分布式PowerBuilder的基础上。
Web.PB实际上代替了传统的PowerBuilder客户端应用的位置,允许用户通过WWW浏览器访问分布式
的PB应用。在浏览器中,用户可以访问在分布式PowerBuilder应用中的任何不可视对象的属性和方法。
用户还可以向方法传递参数。Web服务器与Web.PB通讯将这些信息发送给分布式PowerBuilder应用,
应用会执行方法并且将结果以动态HTML的形式返回给浏览器。 例如,下面的代码放在一个分布式PowerBuilder应用中的"of_get_data"函数中。这个函数在SQL Anywhere数 据库中搜索姓名的第一个字母与用户输入的字母相同的雇员,用户输入的字母放在search_letter参数中:
//定义变量
String ls_html String ls_search_arg long ll_rows_retrieved ls_search_arg=search_letter+% //创建数据存储(非可视化数据窗口)并与一个数据窗口相关联
ds_names=create datastore ds_names.dataobjecet="d_names" ds_names.settransobject(sqlca)
//检索行
ll_rows_retrieved=ds_names.retrieve(ls_scarch_arg)
//用htmltable属性以HTML表格形式存储结果
if ll_rows_retrieved＞0 then ls_html=ds_names.object.DataWindow.data.htmltable
else ls_html=‘No employees found for this search' end if
//返顺包含HTML表格的字符串。
return ls_html
建立了函数后,在HTML页中用下列语法调用它:
〈FORM METHOD="GET"ACTION="/cgi-shl/pbcgi050.exe/of_get_data"〉请键入搜索字母〈INPUT NAME="search_letter"〉 〈INPUT TYPE="SUBMIT" VALUE="search"〉 ＜/FORM＞ Action语句指出使用Web.PB的接口,在这个例子中使用pbcgi050.exe。
PowerBuilder还 支持Microsoft(ISAPI)和Netscape(NSAPI)Web服务器。
被调用函数"of_get_data"参数名称是"search_letter"。一旦这个需求被提交,
分布式PowerBuilder获取数据,将它格式化为HTML表格并且将表格返回给浏览器:
Web.PB的优点表现在以下几个方面:
·用户可以访问动态的、数据驱动的Web节点; ·创建与服务器无关的应用,可以使用Microsoft、Netscape或任何CGI兼容的WWW服务器; ·客户端的PC机只需要WWW浏览器;
·使用数据窗口技术在WWW节点上加入数据库连接能力;
·简单地提交复杂的、基于数据库的事务应用;
·只在服务器上修改,简化应用的维护和支持。
支持的平台 下表是PowerBuilder 5.0 Internet特性支持的平台。


 动态创建可视控件的基本方法 鹿寒

下面简单的介绍一下前一段时间设计的一个程序中所采用的方法(创建一个BUTTON控件为例) 。
1.建立一个uo_button用户对象(从VISUAL->STANDARD->COMMANDBUTTON派生).
(1).申明用户对象实例变量 Private String h_event PowerObject u_obj
(2).申明一些用户对象函数 void SetWidth(long u_width) void SetHeight(long u_height) void SetX(long u_x) void Sety(long u_y) void EnableDrag() void DisEnableDrag() void SetEvent(window obj,String u_event) //这里用window就只能在window中调用
(3).函数的实现
void SetWidth(long u_width) {this.width=u_width}
void SetHeight(long u_height) {this.height=u_height}
void SetX(long u_x) {this.x=u_x}
void Sety(long u_y) {this.y=u_y}
void EnableDrag() {this.dragauto=true}
void DisEnableDrag() {this.dragauto=false}
void SetEvent(window obj,String u_event) { this.h_event=u_event this.u_obj=obj }
(4).uo_button 的CLICKED事件脚本 u_obj.triggerevent(h_event)
2.建立以上用户化的按扭后就可以在程序运行时动态创建控件 例如:
(1).建立窗体W_MAIN和一个按扭m_button
(2).申明窗体实例变量 uo_button test
(3).W_MAIM的OPEN脚本 test=create uo_button
(4).W_MAIM的CLOSE脚本 destroy test
(5).申明窗体用户事件MY_EVENT 脚本 MESSAGEBOX("测试","这是一个动态创建的按扭!")
(4).m_button CLICKED事件脚本 OPENUSEROBJECT(TEST) test.SetEvent(parent,"MY_EVENT") 这样就动态创建了一个按扭,当CLICK动态按扭时就会弹出对话框,显示信息.如果调用Enable Drag()函数后, 在W_MAIN的DRAGWITHIN事件中加入以下脚本,就可以拖动按扭定位. SetX(this.pointerX()) SetY(this.pointerY()) 还可以利用其他的用户对象函数改变动态按扭的属性.
以上简单的介绍了动态创建可视控件的基本方法,供大家参考.可以根据自己的需要设计用户 对象函数.如果TEST申明成数组就可以创建一系列动态控件,还可以把动态控件的属性放入数据 库中,实现设计向导的功能.


 用PowerBuilder开发多媒体播放器

河北　黄万璞

    PowerBuilder作为一种优秀的数据库前端开发工具，深受广大数据库开发者的青睐，
现在的数据库涉及到多媒体应用越来越多，而PowerBuilder没有像VB、Delphi那样提供开
发多媒体的控件，但并不是说利用PowerBuilder就不能开发多媒体。下面就介绍利用PowerBuilder
开发多媒体的三种方法(以播放视频文件AVI为例)。
    一、利用OLE方法。打开要播放的多媒体的窗体，点按OLE控件，系统弹出“Insert object”对话框，选择“CreateNew”页，选取视频剪辑，在窗体上放上该OLE控件，
编写窗体Open事件的脚本(比如要播放的视频文件为sample.avi)： ole_1.InserFile(“sample.avi”)运行该窗体，双击该OLE，视频就播放出来了。
    二、利用MCI方法。这种方法要调用Windows的API函数MciSendStringA,因此，首先要声明
该函数。选“Declare/Local External Functions”菜单项，弹出“Declare Local External Functions”对话框，声明局部外部函数：
Function Long mciSendStringA(red string scommand,ref string returnstr,int ilength,UNITucallback)。
在窗体上放上一个命令按钮cb_play， 其Text为“播放”，在cb_play的Click事件输入脚本：
string ls_command,ls_buffer,ls_filename
filename=″sample.avi″
ls_buffer=fill(Char(0),255)
ls_command=″open″＋ls_filemane＋″type avivideo alias avi″ mciSendStringA(ls_command,ls_buffer,255,handle(parent))
ls_command=″play avi notify″
mciSendStringA(ls_command,ls_buffer,255,handle(parent))
    运行窗体,点击该按钮就可以播放视频了。
    三、利用OCX控件。Windows95的系统提供了Amovie.ocx控件，该控件提供了很多属性和函数，
可通过编程修改其属性和调用其函数来控制多媒体，开发出高质量的多媒体播放器。下面我们就以
开发具有播放和停止功能的多媒体播放器为例。
    在要播放的窗体上，点按OLE控件，系统弹出“Insert objet”对话框，选择“Insert control”页，
“选取ActiveMovieControlObject”,在窗体上放上该定制OLE控件，然后在窗体上放两个命令按钮
cb_play和cb_stop，其Text分别赋值为“播放”和“停止”。
    在窗体的open事件输入脚本：
ole_1.Objet.Filename(″sample.avi″)
cb_play的Click事件的脚本为：
ole_1.Object.play()
cb_stop的Click事件的脚本为：
ole_1.object.stop()
    这样简单的多媒体播放器就开发好了，如果想对多媒体进行更多的控制，调用该控件相应的函数
和修改其属性即可。至于Amovie.ocx控件有哪些事件、属性和函数，可通过点按Browser图标来查看。


SQL基础

    本章内容 SQL介绍 使用SELECT 语句从表中取数据 创建新表 字段属性 向表中添加数据 删除和修改表 为了建立交互站点，你需要使用数据库来存储来自访问者的信息。例如，你要建立一个职业介绍服务的站
点，你就需要存储诸如个人简历，所感兴趣的工作等等这样的信息。创建动态网叶也需要使用数据库，如
果你想显示符合来访者要求的最好的工作，你就需要从数据库中取出这份工作的信息。你将会发现，在许
多情况下需要使用数据库。
    在这一章里，你将学会怎样使用“结构化查询语言”（SQL〕来操作数据库。SQL语言是数据库的标
准语言。在Active Sever Pages 中，无论何时你要访问一个数据库，你就要使用SQL语言。
因此，掌握好SQL对ASP编程是非常重要的。 注意： 你可以把“SQL”读作“sequel”，也可以按单个字母的读音读作S－Q－L。
两种发音都是正确的，每种发音各有大量的支持者。在本书里，认为“SQL”读作“sequel”。 通过这一章的学习，你将理解怎样用SQL实现数据库查询，你将学会怎样使用这种查询从数据表中取出
信息，最后，你将学会怎样设计和建立自己的数据库。 注意：
通过下面几章对SQL的介绍，你将对SQL有足够的了解，从而可以有效地使用Active Sever Pages。但是，
SQL是一种复杂的语言，本书不可能包括它的全部细节。要全面掌握SQL语言，你需要学习在
Microsoft SQL Sever 中使用SQL。你可以到附近的书店去买一本Microsoft SQL Sever 6.5。 SQL介绍：
本书假设你是在SQL操作Microsoft SQL Sever 的数据库。你也可以用SQL操作许多其它类型的数据库。
SQL是操作数据库的标准语言。（事实上，关于SQL语言有一个专门的ANSI标准〕 注意：
不要在你的站点上试图用Microsoft Access代替Microsoft SQL Sever。SQL Sever可以同时服务于许多用户，
如果你希望你的站点有较高的访问率，MS Access是不能胜任的。 在学习SQL的细节之前，
你需要理解它的两大特点。一个特点容易掌握，另一个掌握起来有点困难。
第一个特点是所有SQL数据库中的数据都存储在表中。一个表由行和列组成。例如，下面这个简单的表
包括name 和e-mail address：
Name Email Address
Bill Gates billg@microsoft.com
president Clinton president@whitehouse.com
Stephen Walther swalther@somewhere.com
这个表有两列（列也称为字段，域〕：Name和Email Address。有三行，每一行包含一组数据。
一行中的数据组合在一起称为一条记录。 无论何时你向表中添加新数据，你就添加了一条新记录。
一个数据表可以有几十个记录，也可以有几千甚至几十亿个记录。虽然你也许永远不需要存储十
亿个Email地址，但知道你能这样做总是好的，也许有一天你会有这样的需要。 你的数据库很有可能
包含几十个表，所有存储在你数据库中的信息都被存储在这些表中。当你考虑怎样把信息存储在
数据库中时，你应该考虑怎样把它们存储在表中。 SQL的第二个特点有些难于掌握。
这种语言被设计为不允许你按照某种特定的顺序来取出记录，因为这样做会降低SQL Sever取记录的效率。
使用SQL，你只能按查询条件来读取记录。 当考虑如何从表中取出记录时，自然会想到按记录的
位置读取它们。例如，也许你会尝试通过一个循环，逐个记录地扫描，来选出特定的记录。在使用
SQL时，你必须训练自己，不要有这种思路。 假如你想选出所有的名字是“Bill Gates”的记录，
如果使用传统的编程语言，你也许会构造一个循环，逐个查看表中的记录，看名字域是否是“Bill Gates”。
这种选择记录的方法是可行的，但是效率不高。使用SQL，你只要说，“选择所有名字域等于Bill
Gates的记录”，SQL就会为你选出所有符合条件的记录。SQL会确定实现查询的最佳方法。
建设你想取出表中的前十个记录。使用传统的编程语言，你可以做一个循环，取出前十个记录后
结束循环。但使用标准的SQL查询，这是不可能实现的。从SQL的角度来说，在一个表中不存在
前十个记录这种概念。 开始时，当你知道你不能用SQL实现某些你感觉应该能实现的功能，
你会受到挫折。你也许会以头撞墙甚至想写恶毒的信件给SQL的设计者们。但后来你会认识到，
SQL的这个特点不仅不是个限制，反而是其长处。因为SQL不根据位置来读取记录，它读取记录可以很快。
综上所述，SQL有两个特点：所有数据存储在表中，从SQL的角度来说，表中的记录没有顺序。
在下一节，你将学会怎样用SQL从表中选择特殊的记录。 使用SQL从表中取记录。 SQL的主要功能之一
是实现数据库查询。如果你熟悉Internet 引擎，那么你已经熟悉查询了。你使用查询来取得满足
特定条件的信息。例如，如果你想找到有ASP信息的全部站点，你可以连接到 Yahoo!并执行一个对Active Sever
Pages的搜索。在你输入这个查询后，你会收到一个列表，表中包括所有其描述中包含搜索表达式的站点。
多数Internet 引擎允许逻辑查询。在逻辑查询中，你可以包括特殊的运算符如AND、OR和NOT，
你使用这些运算符来选择特定的记录。例如，你可以用AND来限制查询结果。如果你执行一个对
Active Sever Pages AND SQL的搜索。你将得到其描述中同时包含Active Sever Pages 和SQL的记录。
当你需要限制查询结果时，你可以使用AND。 如果你需要扩展查询的结果，你可以使用逻辑操作符OR。
例如，如果你执行一个搜索，搜索所有的其描述中包含Active Sever Pages OR SQL的站点，
你收到的列表中将包括所有其描述中同时包含两个表达式或其中任何一个表达式的站点。
如果你想从搜索结果中排除特定的站点，你可以使用NOT。例如，查询
“Active Sever Pages ”AND NOT “SQL”将返回一个列表，列表中的站点包含Active Sever Pages，
但不包含SQL。当必须排除特定的记录时，你可以使用NOT。
用SQL执行的查询与用Internet搜索引擎执行的搜索非常相似。 当你执行一个SQL查询时，通过使用包括逻辑运算符的查询条件，你可以得到一个记录列表。
此时查询结果是来自一个或多个表。 SQL查询的句法非常简单。假设有一个名为email_table
的表，包含名字和地址两个字段，要得到Bill Gates 的e_mail地址,你可以使用下面的查询：
SELECT email from email_table WHERE name="Bill Gates" 当这个查询执行时，
就从名为email_table的表中读取Bill Gates的e_mail 地址。这个简单的语句包括三部分：
■ SELECT语句的第一部分指名要选取的列。在此例中，只有email列被选取。当执行 时，只显示email列的值 billg@microsoft.com。
■ SELECTT语句的第二部份指明要从哪个（些）表中查询数据。在此例中，要查询的表名为email_table 。
■ 后，SELECT语句的WHERE子句指明要选择满足什么条件的记录。在此例中，查询条件为只有
name列的值为Bill Gates 的记录才被选取。 Bill Gates很有可能拥有不止一个email地址。如果表中包含Bill
Gates的多个email地址。用上述的SELECT语句可以读取他所有的email地址。
SELECT语句从表中取出所有name字段值为Bill Gates 的记录的email 字段的值。 前面说过，查询可以在查询条件中包含逻辑运算符。假如你想读取Bill Gates 或Clinton总统的所有email地址，
你可以使用下面的查询语句：
SELECT email FROM email_table WHERE name="Bill Gates" OR name="president Clinton"
此例中的查询条件比前一个复杂了一点。这个语句从表email_table中选出所有name列为Bill Gates
或president Clinton的记录。如果表中含有Bill Gates或president Clinton的多个地址，所有的地址都被读取。
SELECT语句的结构看起来很直观。如果你请一个朋友从一个表中为你选择一组记录，你也许以非常相
似的方式提出你的要求。在SQL SELECT语句中，你“SELECT特定的列FROM一个表WHERE
某些列满足一个特定的条件”。
下一节将介绍怎样执行SQL查询来选取记录。这将帮助你熟悉用SELECT语句从表中取数据的各种不同方法。
使用ISQL执行SELECT查询 当你安装SQL Sever时，你同时安装了一个叫作ISQL/w的应用程序。
ISQL/w允许你执行交互的SQL查询。在把查询包括到你的ASP网页中之前，用ISQL/w对其进行测试是
非常有用的。 注意： 在这本书的第一部份，你学习了怎样安装和配置Microsoft SQL Sever 。如果没有
安装SQL Sever或者SQL Sever不能运行，请参阅第三章“安装和使用SQL Sever”。 选择任务上SQL
Sever程序组中的ISQL_w以启动该程序。程序启动时，首先会出现一个对话框，要求输入服务器信息
和登录信息（见图10.1）。在Sever框中，输入你的SQL服务器的名字。如果服务器正运行在本地计算
机上，服务器名字就是你计算机的名字。在登录信息框中，输入一个登录帐号和密码或选择使用“可信
连接”，然后单击Connect按钮。 图10。1
注意： 如果你将SQL Sever配置为使用完整安全或混合安全，那么你可以使用可信连接。如果你使用
标准安全，你则需要提供用户帐号和密码。要了解更多信息，参见第三章。 如果一切正常，
在你单击连接按钮后会出现一个查询窗口，如图10.2所示。（如果有异常，请参考第三章） 　 图10.2
?在执行查询之前，你需要选择数据库。安装 SQL Sever时你已为自己创建了一个数据库，SQL
Sever还有许多系统数据库，如master，model，msdb，和tempdb。 方便的是，SQL Sever带有一个
特殊的名为pubs的例子数据库。库 pubs中包含供一个虚拟的出版商使用的各个表。文档中所有的例子
程序都是针对这个库来设计的。本书中的许多例子也使用这个数据库。 在查询窗口顶部的DB下拉框中选择
数据库pubs，这样你就选择了数据库。你所有的查询都将针对这个库中的各个表来执行。现在你可以
执行你的第一个查询了。这真让人兴奋！ 你的第一个查询将针对一个名为autrors的表，表中包含所有
为某个虚拟出版商工作的作者的相关数据。单击查询窗口并输入以下的语句：
SELECT phone FROM authors WHERE au_name="Ringer"
输入完成后，单击执行查询按钮（一个绿色三角形，看起来像VCR播放键）。单击此按钮后，
任何出现在查询窗口中的语句均会被执行。查询窗口会自动变成结果显示窗口，你可以看到查询的
结果（见图10.3）。 你看到的查询结果也许与图10.3所示的不同。在SQL Sever的不同版本中，
库pubs中的数据会有所不同。对SQL Sever 6.5来说，将会找到两条记录。结果显示窗口中应显示
如下内容： phone ………………. 801 826_0752 801 826_0752 (2 row(s) affected) 图10.3 　 　 你所执行的SELECT语句从表authors中取出所有名字为Ringer的作者的电话号码。你通过在WHERE
子句中使用特殊的选择条件来限制查询的结果。你也可以忽略选择条件，从表中取出所有作者的电话
号码。要做到这一点，单击Query标签，返回到查询窗口，输入以下的SELECT语句：
SELECT Phone FROM authors
这个查询执行后，会取出表authors中的所有电话号码（没有特定的顺序）。如果表authors中包含一
百个电话号码，会有一百个记录被取出，如果表中有十亿个电话号码，这十亿条记录都会被取出（这
也许需要一些时间）。 表authrs的字段包括姓，名字，电话号码，地址，城市，州和邮政编码。
通过在SELECT语句的第一部份指定它们，你可以从表中取出任何一个字段。你可以在一个SELECT
语句中一次取出多个字段，比如： SELECT au_fname ,au_lname, phone FROM authors
这个SELECT语句执行后，将取出这三个列的所有值。下面是这个查询的结果的一个示例（为了节省
纸张，只显示查询结果的一部分，其余记录用省略号代替）：
au_fname au_lname phone
………………………………………………………………………….
Johnson White 408 496_7223
Marjorie Green 415 986_7020
Cheryl Carson 415 548_7723
Michael O’Leary 408 286_2428
…
(23 row(s) affected) 在SELECT语句中，你需要列出多少个字段，你就可以列出多少。不要忘了把字段名用逗号隔开。
你也可以用星号（*）从一个表中取出所有的字段。这里有一个使用星号的例子： SELECT * FROM authors
这个SELECT语句执行后，表中的所有字段的值都被取出。你会发现你将在SQL查询中频繁使用星号。 技巧：
你可以使用星号来查看一个表的所有列的名字。要做到这一点，只需要在执行完SELECT语句后看一下查询
结果的列标题。 操作多个表 到现在为止，你只尝试了用一句SQL查询从一个表中取出数据。
你也可以用一个SELECT语句同时从多个表中取出数据，只需在SELECT语句的FROM从句中列出要从中
取出数据的表名称即可： SELECT au_lname ,title FROM authors, titles
这个SELECT语句执行时，同时从表authors和表titles中取出数据。从表authors中取出所有的作者名字，
从表titles中取出所有的书名。在ISQL/w程序中执行这个查询，看一下查询结果。你会发现一些奇怪的出乎
意料的情况：作者的名字并没有和它们所著的书相匹配，而是出现了作者名字和书名的所有可能的组合，
这也许不是你所希望见到的。 出了什么差错？问题在于你没有指明这两个表之间的关系。
你没有通过任何方式告诉SQL如何把表和表关联在一起。由于不知道如何关联两个表，服务器只能简单地
返回取自两个表中的记录的所有可能组合。 要从两个表中选出有意义的记录组合，你需要通过建立两表
中字段的关系来关联两个表。要做到这一点的途径之一是创建第三个表，专门用来描述另外两个表的字段
之间的关系。 表authors有一个名为au_id的字段，包含有每个作者的唯一标识。表titles有一个名为
title_id的字段，包含每个书名的唯一标识。如果你能在字段au_id和字段title_id 之间建立一个关系，
你就可以关联这两个表。数据库pubs中有一个名为titleauthor的表，正是用来完成这个工作。表中的每个
记录包括两个字段，用来把表titles和表authors关联在一起。下面的SELECT语句使用了这三个表以得到
正确的结果：
SELECT au_name,title FROM authors,titles,titleauthor WHERE authors.au_id=titleauthor.au_id AND
titles.title_id=titleauthor.title_id
当这个SELECT语句执行时，每个作者都将与正确的书名相匹配。表titleauthor指明了表authors和表titles
的关系，它通过包含分别来自两个表的各一个字段实现这一点。第三个表的唯一目的是在另外两个表的字
段之间建立关系。它本身不包含任何附加数据。 注意在这个例子中字段名是如何书写的。为了区别表authors和表titles中相同的字段名au_id，每个字段名
前面都加上了表名前缀和一个句号。名为author.au_id 的字段属于表authors，名为titleauthor.au_id的字段属
于表titleauthor，两者不会混淆。 通过使用第三个表，你可以在两个表的字段之间建立各种类型的关系。
例如，一个作者也许写了许多不同的书，或者一本书也许由许多不同的作者共同完成。当两个表的字段之
间有这种“多对多”的关系时，你需要使用第三个表来指明这种关系。 但是，在许多情况下，两个表之间
的关系并不复杂。比如你需要指明表titles和表publishers之间的关系。因为一个书名不可能与多个出版商
相匹配，你不需要通过第三个表来指明这两个表之间的关系。要指明表titles和表publishers之间的关系，
你只要让这两个表有一个公共的字段就可以了。在数据库pubs中，表titles和表publishers都有一个名为
pub_id的字段。如果你想得到书名及其出版商的一个列表，你可以使用如下的语句：
SELECT title,pub_name FROM titles,publishers WHERE titles.pub_id=publishers.pub_id
当然，如果一本书是由两个出版商联合出版的，那么你需要第三个表来代表这种关系。 通常，当你予先知道两个
表的字段间存在“多对多”关系时，就使用第三个表来关联这两个表。反之，如果两个表的字段间只有
“一对一”或“一对多”关系，你可以使用公共字段来关联它门。 操作字段 通常，当你从一个表中
取出字段值时，该值与创建该表时所定义的字段名联系在一起。如果你从表authors中选择所有的作者
名字，所有的值将会与字段名au_lname相联系。但是在某些情况下，你需要对字段名进行操作。
在SELECT语句中，你可以在缺省字段名后面仅跟一个新名字来取代它。例如，可以用一个更直观易读的
名字Author Last Name来代替字段名au_lname： SELECT au_lname "Author Last Name" FROM authors
当这个SELECT语句执行时，来自字段au_lname的值会与“Author Last Name”相联系。查询结果可能是这样： Author Last Name
……………………………………………………………………..
White Green Carson O’Leary Straight
…
(23 row(s) affected)
注意字段标题不再是au_lname，而是被Author Last Name所取代。 你也可以通过执行运算，来操作从一
个表返回的字段值。例如，如果你想把表titles中的所有书的价格加倍，你可以使用下面的SELECT语句：
SELECT price*2 FROM titles
当这个查询执行时，每本书的价格从表中取出时都会加倍。但是，通过这种途径操作字段不会改变存储
在表中的书价。对字段的运算只会影响SELECT语句的输出，而不会影响表中的数据。为了同时显示书
的原始价格和涨价后的新价格，你可以使用下面的查询：
SELECT price "Original price", price*2 "New price" FROM titles
当数据从表titles中取出时，原始价格显示在标题Original price下面，加倍后的价格显示在标题New
price下面。结果可能是这样：
original price new price
……………………………………………………………….
39.98
11.95
23.90
5.98
39.98
…
(18 row(s) affected)
你可以使用大多数标准的数学运算符来操作字段值，如加（+），减（-），乘（*）和除（/）。你也可以一次对
多个字段进行运算，例如： SELECT price*ytd_sales "total revenue" FROM titles
在这个例子中，通过把价格与销售量相乘，计算出了每种书的总销售额。这个SELECT语句的结果将是
这样的：
total revenue
……………………………………………..
81,859,05
46,318,20
55,978,78
81,859,05
40,619,68
…
(18 row(s) affected)
最后，你还可以使用连接运算符（它看起来像个加号）来连接两个字符型字段：
SELECT au_fname+" "+au_lname "author name" FROM authors
在这个例子中，你把字段au_fname和字段au_lname粘贴在一起，中间用一个逗号 隔开，并把查询结果
的标题指定为author name。这个语句的执行结果将是这样的：
author names
…………………………………………………………
Johnson White
Marjorie Green
Cheryl Carson
Michael O’Leary
Dean Straight
…
(23 row(s) affected) 可以看到，SQL为你提供了对查询结果的许多控制。你应该在ASP编程过程中充分利用这些优点。
使用SQL来操作查询结果几乎总是比使用有同样作用的脚本效率更高。 排序查询结果 本章的介绍中
曾强调过，SQL表没有内在的顺序。例如，从一个表中取第二个记录是没有意义的。从SQL的角度看来，
没有一个记录在任何其他记录之前。 然而，你可以操纵一个SQL查询结果的顺序。在缺省情况下，
当记录从表中取出时，记录不以特定的顺序出现。例如，当从表authors中取出字段au_lname时，
查询结果显示成这样：
au_lname
…………………………………….
White Green
Carson O’Leary Straight
…
(23 row(s) affected)
看一列没有特定顺序的名字是很不方便的。如果把这些名字按字母顺序排列，读起来就会容易得多。
通过使用ORDER BY子句，你可以强制一个查询结果按升序排列，就像这样：
SELECT au_lname FROM authors ORDER BY au_lname
当这个SELECT语句执行时，作者名字的显示将按字母顺序排列。ORDER BY子句将作者名字按升序排列。
你也可以同时对多个列使用ORDER BY子句。例如，如果你想同时按升序显示字段au_lname
和字段au_fname，你需要对两个字段都进行排序：
SELECT au_lname,au_fname FROM authors ORDER BY au_lname ,au_fname
这个查询首先把结果按au_lname字段进行排序，然后按字段au_fname排序。记录将按如下的顺序取出：
au_lname au_fname
…………………………………………………………………….
Bennet Abraham Ringer
Albert Ringer
Anne Smith Meander
…
(23 row(s) affected)
注意有两个作者有相同的名字Ringer。名为Albert Ringer的作者出现名为Anne Ringer的作者之前，这是因为姓Albert按字母顺序应排在姓Anne之前。 如果你想把查询结果按相反的顺序排列，
你可以使用关键字DESC。关键字DESC把查询结果按降序排列，如下例所示：
SELECT au_lname,au_fname FROM authors WHERE au_lname=”Ringer”
ORDER BY au_lname ,au_fname DESC
这个查询从表authors中取出所有名字为Ringer的作者记录。ORDER BY子句根据作者的名字和姓，
将查询结果按降序排列。结果是这样的：
au_lname au_fname
……………………………………………………………………………………….
Ringer Anne
Ringer Albert
(2 row(s) affectec)
注意在这个表中，姓Anne出现在姓Albert之前。作者名字按降序显示。 你也可以按数值型字段对一个查询结果进行排序。例如，如果你想按降序取出所有书的价格，你可以
使用如下的SQL查询：
SELECT price FROM titles ORDER BY price DESC 这个SELECT语句从表中取出所有书的价格，显示结果时，价格低的书先显示，价格高的书后显示。 警告：
不是特别需要时，不要对查询结果进行排序，因为服务器完成这项工作要费些力气。这意味着带有ORDER BY
子句的SELECT语句执行起来比一般的SELECT语句花的时间长。 取出互不相同的记录 一个表有可能在同
一列中有重复的值。例如，数据库pubs的表authors中有两个作者的名字是Ringer。如果你从这个表中取
出所有的名字，名字Ringer将会显示两次。 在特定情况下，你可能只有兴趣从一个表中取出互不相同的值。
如果一个字段有重复的值，你也许希望每个值只被选取一次，你可以使用关键字DISTINCT来做到这一点：
SELCET DISTINCT au_lname FROM authors WHERE au_lname="Ringer"
当这个SELECT语句执行时，只返回一个记录。通过在SELECT语句中包含关键字DISTINCT，你可以删除
所有重复的值。例如，假设有一个关于新闻组信息发布的表，你想取出所有曾在这个新闻组中发布信息的人的
名字，那么你可以使用关键字DISTINCT。每个用户的名字只取一次——尽管有的用户发布了不止一篇信息。
警告： 如同ORDER BY子句一样，强制服务器返回互不相同的值也会增加运行开销。福气不得不花费
一些时间来完成这项工作。因此，不是必须的时候不要使用关键字DISTINCT。 创建新表 前面说过，
数据库中的所有数据存储在表中。数据表包括行和列。列决定了表中数据的类型。行包含了实际的数据。
例如，数据库pubs中的表authors有九个字段。其中的一个字段名为为au_lname，这个字段被用来存储
作者的名字信息。每次向这个表中添加新作者时，作者名字就被添加到这个字段，产生一条新记录。
通过定义字段，你可以创建一个新表。每个字段有一个名字和一个特定的数据类型（数据类型在后面的
“字段类型”一节中讲述），例如字段au_lname存储的是字符型数据。一个字段也可以存储其它类型的
数据。 使用SQL Sever，创建一个新表的方法是很多的。你可以可执行一个SQL语句或使用SQL事务管
理器（SQL Enterprise Manager）来创建一个新表。在下一节里，你将学会如何用SQL语句来创建一个新表。
用SQL创建新表
注意： 如果你还没有建立自己的数据库，现在就跳回到第三章创建这个库。你绝不能向master,tempdb
或任何其他任何系统数据库中添加数据。 从SQL Sever程序组（在任务栏中）中启动ISQL/w程序。出现
查询窗口后，从窗口顶部的下拉列表中选择你在第三章所创建的数据库。下一步，在查询窗口中键入下面
的SQL语句，单击执行查询按钮，执行这个语句：
CREATE TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate DATETIME)
如果一切正常，你会在结果窗口中看到如下的文字（如果出现异常，请参阅第三章）：
This command dit not return data ,and it did not return any rows 祝贺你，你已经建立了你的第一个表！
你所创建的表名为guestbook，你可以使用这个表来存储来字你站点访问者的信息。你是用REEATE
TABLE语句创建的这个表，这个语句有两部分：第一部份指定表的名子；第二部份是括在括号中的各字段
的名称和属性，相互之间用逗号隔开。 表guestbook有三个字段：visitor,comments 和entrydate。visitor
字段存储访问者的名字，comments字段存储访问者对你站点的意见，entrydate字段存储访问者访问你站
点的日期和时间。 注意每个字段名后面都跟有一个专门的表达式。例如，字段名comments
后面跟有表达式TEXT。这个表达式指定了字段的数据类型。数据类型决定了一个字段可以存储什么样的数据。
因为字段comments包含文本信息，其数据类型定义为文本型。 字段有许多不同的数据类型。
下一小节讲述SQL所支持的一些重要的数据类型。 字段类型 不同的字段类型用来存放不同类型的数据。
创建和使用表时，更你应该理解五种常用的字段类型：字符型，文本型，数值型，逻辑性和日期型。
字符型数据 字符型数据非常有用。当你需要存储短的字符串信息时，你总是要用到字符型数据。
例如，你可以把从HTML form的文本框中搜集到的信息放在字符型字段中。 要建立一个字段用来存放可变长
度的字符串信息，你可以使用表达式 VARCHAR。考虑你前面创建的表guestbook：
CREATE TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate DATETIME)
在这个例子中，字段visitor的数据类型为VARCHAR。注意跟在数据类型后面的括号中的数字。这个数字指
定了这个字段所允许存放的字符串的最大长度。在这个例子中，字段visitor能存放的字符串最长为四十个字
符。如果名字太长，字符串会被截断，只保留四十个字符。 VARCHAR类型可以存储的字符串最长为255个字符。
要存储更长的字符串数据，可以使用文本型数据（下一节中讲述）。
一种字符型数据用来存储固定长度的字符数据。
下面是一个使用这种数据类型的例子：
CREATE TABLE guestbook (visitor CHAR(40),comments TEXT,entrydate DATETIME)
在这个例子中，字段visitor被用来存储四十个字符的固定长度字符串。表达式CHAR指定了这个字段应该
是固定长度的字符串。 VARCHAR型和CHAR型数据的这个差别是细微的，但是非常重要。
假如你向一个长度为四十个字符的VARCHAR型字段中输入数据Bill Gates。当你以后从这个字段中取出此数据时，
你取出的数据其长度为十个字符——字符串Bill Gates的长度。 现在假如你把字符串输入一个长度为四十个字符的
CHAR型字段中，那么当你取出数据时，所取出的数据长度将是四十个字符。字符串的后面会被附加多余
的空格。 当你建立自己的站点时，你会发现使用VARCHAR型字段要比CHAR型字段方便的多。使用
VARCHAR型字段时，你不需要为剪掉你数据中多余的空格而操心。 VARCHAR型字段的另一个突出
的好处是它可以比CHAR型字段占用更少的内存和硬盘空间。当你的数据库很大时，这种内存和磁盘空间
的节省会变得非常重要。 文本型数据 字符型数据限制了字符串的长度不能超过２55个字符。
而使用文本型数据，你可以存放超过二十亿个字符的字符串。当你需要存储大串的字符时，应该使用文
本型数据。 这里有一个使用文本型数据的例子：
CREATE TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate DATETIME)
在这个例子中，字段comments被用来存放访问者对你站点的意见。注意文本型数据没有长度，而上一节中
所讲的字符型数据是有长度的。一个文本型字段中的数据通常要么为空，要么很大。 当你从HTML
form的多行文本编辑框（TEXTAREA）中收集数据时，你应该把收集的信息存储于文本型字段中。
但是，无论何时，只要你能避免使用文本型字段，你就应该不适用它。文本型字段既大且慢，滥用文
本型字段会使服务器速度变慢。文本型字段还会吃掉大量的磁盘空间。 警告：
一旦你向文本型字段中输入了任何数据（甚至是空值），就会有2K的空间被自动分配给该数据。除非删除
该记录，否则你无法收回这部分存储空间。 数值型数据 SQL Sever支持许多种不同的数值型数据。
你可以存储整数、小数、和钱数。 通常，当你需要在表中的存放数字时，你要使用整型（INT）数据。
INT型数据的表数范围是从-2，147，483，647到2，147，483，647的整数。下面是一个如何使用INT型数据的
例子： CREATE TABLE visitlog (visitor VARCHAR(40),numvisits INT)
这个表可以用来记录你站点被访问的次数。只要没有人访问你的站点超过2，147，483，647次，
nubvisits字段就可以存储访问次数。 为了节省内存空间，你可以使用SMALLINT型数据。SMALLINT
型数据可以存储从-32768到32768的整数。这种数据类型的使用方法与INT型完全相同。
最后，如果你实在需要节省空间，你可以使用TINYINT型数据。同样，这种类型的使用方法也与INT型
相同，不同的是这种类型的字段只能存储从０到255的整数。TINYINT型字段不能用来存储负数。
通常，为了节省空间，应该尽可能的使用最小的整型数据。一个TINYINT型数据只占用一个字节；一个
INT型数据占用四个字节。这看起来似乎差别不大，但是在比较大的表中，字节数的增长是很快的。另
一方面，一旦你已经创建了一个字段，要修改它是很困难的。因此，为安全起见，你应该预测以下，一
个字段所需要存储的数值最大有可能是多大，然后选择适当的数据类型。 为了能对字段所存放的数据有更多的控制，你可以使用NUMERIC型数据来同时表示一个数的整数部分
和小数部分。NUMERIC型数据使你能表示非常大的数——比INT型数据要大得多。一个NUMERIC型字
段可以存储从-1038到1038范围内的数。NUMERIC型数据还使你能表示有小数部分的数。
例如，你可以在NUMERIC型字段中存储小数3.14。
当定义一个NUMERIC型字段时，你需要同时指定整数部分的大小和小数部分的大小。这里有一个使用
这种数据类型的例子：
CREATE TABLE numeric_data (bignumber NUMERIC(28,0), fraction NUMERIC (5,4) )
当这个语句执行时，将创建一个名为numeric_data的包含两个字段的表。字段bignumber可以存储
直到28位的整数。字段fraction可以存储有五位整数部分和四位小数部分的小数。 一个NUMERIC
型数据的整数部分最大只能有28位，小数部分的位数必须小于或等于整数部分的位数，小数部分可以是零。
你可以使用INT型或NUMERIC型数据来存储钱数。但是，专门有另外两种数据类型用于此目的。如果你希
望你的网点能挣很多钱，你可以使用MONEY型数据。如果你的野心不大，你可以使用SMALLMONEY型数
据。MONEY型数据可以存储从-922，337，203，685，477.5808到922，337，203，685，477.5807
的钱数。如果你需要存储比这还大的金额，你可以使用NUMERIC型数据。 SMALLMONEY型数据只能存储
从-214，748.3648到214，748.3647的钱数。同样，如果可以的话，你应该用SMALLMONEY型来代替
MONEY型数据，以节省空间。下面的例子显示了如何使用这两种表示钱的数据类型：
CREATE TABLE products (product VARCHAR(40),price MONEY, Discount_price SMALLMONEY)
这个表可以用来存储商品的折扣和普通售价。字段price 的数据类型是MONEY，字段discount_price的数据类
型是SMALLMONEY。 存储逻辑值 如果你使用复选框（CHECKBOX）从网页中搜集信息，
你可以把此信息存储在BIT型字段中。BIT型字段只能取两个值：0或1。这里有一个如何使用这种字段的例子：
CREATE TABLE opinion (visitor VARCHAR(40),good BIT) 这个表可以用来存放对你的网点
进行民意调查所得的信息。访问者可以投票表示他们是否喜欢你的网点。如果他们投YES，就在
BIT型字段中存入1。反之，如果他们投NO，就在字段中存入0（在下一章里，你将学会如何计算投票）。
当心，在你创建好一个表之后，你不能向表中添加BIT型字段。如果你打算在一个表中包含BIT型字段，
你必须在创建表时完成。 存储日期和时间 当你建立一个网点时，你也许需要记录在一段时间内的访问者数量。
为了能够存储日期和时间，你需要使用DATETIME型数据，如下例所示：
CREATE TABL visitorlog( visitor VARCHAR (40), arrivaltime DATETIME , departuretime DATETIME)
这个表可以用来记录访问者进入和离开你网点的时间和日期。一个DATETIME型的字段可以存储的日期范
围是从１７５３年１月１日第一毫秒到9999年12月31日最后一毫秒。 如果你不需要覆盖这么大范围的日期和
时间，你可以使用SMALLDATETIME型数据。它与DATETIME型数据同样使用，只不过它能表示的日期和时间
范围比DATETIME型数据小，而且不如DATETIME型数据精确。一个SMALLDATETIME型的字段能够存储从
１９00年１月１日到２０７９年６月６日的日期，它只能精确到秒。 DATETIME型字段在你输入日期和时间
之前并不包含实际的数据，认识这一点是重要的。在下一章，你将学习怎样使用大量的SQL函数来读取和操
作日期和时间（参见下面的“缺省值”一节）。你也可以在VBScript和JScript 中使用日期和时间函数来向
一个DATETIME型字段中输入日期和时间。 字段属性 上一节介绍了如何建立包含不同类型字段的表。在这
一节中， 你将学会如何使用字段的三个属性。这些属性允许你控制空值，缺省值和标识值。 允许和禁止空值
大多数字段可以接受空值（NULL）。当一个字段接受了空值后，如果你不改变它，它将一直保持空值。
空值（NULL）和零是不同的，严格的说，空值表示没有任何值。 为了允许一个字段接受空值，你要在字段
定义的后面使用表达式NULL。例如，下面的表中两个字段都允许接受空值：
CREATE TABLE empty (empty1 CHAR (40) NULL,empty2 INT NULL( 注意： BIT型数据不能是空值。
一个这种类型的字段必须取0或者１。 有时你需要禁止一个字段使用空值。例如，假设有一个表
存储着信用卡号码和信用卡有效日期，你不会希望有人输入一个信用卡号码但不输入有效日期。为了强制
两个字段都输入数据，你可以用下面的方法建立这个表：
CREATE TABLE creditcards (creditcard_number CHAR(20) NOT NULL, Creditcard_expire
DATETIME NOT NULL)
注意字段定义的后面跟有表达式NOT NULL。通过包含表达式NOT NULL，你可以禁止任何人只在一个字段中插入数据，
而不输入另一个字段的数据。 你将会发现，在你建设自己的网点过程中，这种禁止空值的能力是非常有用的。
如果你指定一个字段不能接受空值，那么当你试图输入一个空值时，会有错误警告。这些错误警告可以为程
序调试提供有价值的线索。 缺省值 假设有一个存储地址信息的表，这个表的字段包括街道、
城市、州、邮政编码和国家。如果你预计地址的大部分是在美国，你可以把这个值作为country字段的缺省值。
为了在创建一个表时指定缺省值，你可以使用表达式DEFAULT。请看下面这个在创建表时使用缺省值的例子：
CREATE TABLE addresses (street VARCHAR(60) NULL, city VARCHAR(40) NULL,
state VARCHAR(20) NULL zip VARCHAR(20) NULL, country VARCHAR(30) DEFAULT ‘USA’)
在这个例子中，字段country的缺省值被指定为美国。注意单引号的使用，引号指明这是字符型数据。
为了给非字符型的字段指定缺省值，不要把该值扩在引号中：
CREATE TABLE orders(price MONEY DEFAULT $38.00, quantity INT DEFAULT 50,
entrydate DATETIME DEFAULT GETDATE())
在这个CREATETABLE语句中，每个字段都指定了一个缺省值。注意DATETIME型字段entrydate所指定
的缺省值,该缺省值是函数Getdate()的返回值,该函数返回当前的日期和时间。 标识字段 每个表可以有
一个也只能有一个标识字段。一个标识字段是唯一标识表中每条记录的特殊字段。例如，数据库pubs
中的表jobs包含了一个唯一标识每个工作标识字段：
job_id job_desc
…………………………………………………………….
New Hire Job not specified Chief Executive officer Bushness Operations Manager Chief Financial Officier Publisher 字段job_id为每个工作提供了唯一的一个数字。如果你决定增加一个新工作，新增记录的job_id字段会
被自动赋给一个新的唯一值。 为了建立一个标识字段，你只需在字段定义后面加上
表达式IDENTITY即可。你只能把NUMERIC型或INT型字段设为标识字段，这里有一个例子：
CREATE TABLE visitorID (theID NUBERIC(18) IDENTITY,name VARCHAR(40))
这个语句所创建的表包含一个名为theid的标识字段。每当一个新的访问者名字添加到这个表中时，
这个字段就被自动赋给一个新值。你可以用这个表为你的站点的每一个用户提供唯一标识。 技巧：
建立一个标示字段时，注意使用足够大的数据类型。例如你使用TINYINT型数据，那么你只能向表中
添加255个记录。如果你预计一个表可能会变得很大，你应该使用NUMERIC型数据。 标识字段的存在会使你想尝试许多不可能的事情。例如，你也许想利用标识字段来对记录进行基于它们
在表中位置的运算。你应该抛弃这种意图。每个记录的标识字段的值是互不相同的，但是，这并不禁止
一个标识字段的标识数字之间存在间隔。例如，你永远不要试图利用一个表的标识字段来取出表中的前
十个记录。这种操作会导致失败，比如说６号记录和7号记录根本不存在。 使用SQL事务管理器创建新表
你可以使用前面几节所讲的方法创建新表。但是，使用事务管理器创建新表会更容易。这一节介绍如何
使用这个程序创建新表。 从任务栏的SQL Sever程序组中选择SQL Enterprise Manager，启动该程序，
你会看到如图10.4所示的窗口。浏览服务管理器窗口中的树形结构，选择名为Database的文件夹。
打开文件夹Database后，选择你在第三章中所建立的数据库。 注意： 如果你还没有创建自己的数据库，
回到第三章创建它。你决不要向master,tempdb或任何其它系统数据库中添加数据。 在选择了数据库之后，
你会看到一个名为Group/users的文件夹和一个名为objects的文件夹。打开文件夹objects，你会看到许多
文件夹，其中一个名为Tables。用右键单击文件夹Tables并选择New table，就会出现如图10.5所示的窗口。
你可以使用Manager Tables窗口来创建一个新表。Manager Tables窗口有７个列：
Key,Column,Name,Datatype,Size,Nulls和Default。Manager Tables窗口中的每一行标明表中一个字段
的信息。 图10.4 10.5 要建立一个新表，你至少要输入一行信息。在名为Column Name的列下面键入mycolumn。
下一步，选择Datatype列，并从下拉列表中选择CHAR。当你在这两个列中输入信息后，窗口将是如
图10.6所示的样子。 图10.6
你已经建立了一个只有一个字段的简单的表。单击保存按扭保存这个新表。当要求你输入新表的名字时，
输入mytable并单击OK。现在这个表已经保存到了你的数据库中。 如果你打开服务管理器窗口中的文件夹Tables，
你会看到你所建立的新表被列出。你可以双击该表的图表来编辑它，这时Manager Tables窗口会重新出现，
你可以增加新的字段并重新保存。 用SQL事务管理器可以做的工作，你都可以用SQL语句来实现。
但是，事务管理器使得建表过程变得更加简单。 向表中添加数据 下一章将讨论如何使用SQL向一个
表中插入数据。但是，如果你需要向一个表中添加许多条记录，使用SQL语句输入数据是很不方便的。
幸运的是，Microsoft SQL Sever带有一个称为Microsoft Query 的客户端应用程序，这个程序使得向表中添
加数据变得容易了。 启动位于任务栏SQL Sever程序组中的Microsoft Query程序。从窗口顶部的菜单中选择
File|New Query。这时会显示一个Select Data Source对话框（见图10.7）。选择你的数据源名字并单击Use。
图10。7 输入你的登录帐号和密码后，程序要求你选择一个表和一个数据库。选择你在上一节中所建立的
表（mytable ），单击按钮Add，然后单击按钮Close关闭该对话框。 在窗口的左上角会出现一个对话框，
框中是取自表mytable的一列字段名。你可以双击任何一个字段，把它添加到主窗口中。如果你双击星号
（*）字符，所有的字段都会被添加到主窗口中。 如果你的表中有记录，它们现在已经出现在主窗口的
字段标题下面了。但是，因为你刚刚建立了这个表，表还是空的。要添加新记录，选择Records|Allow
Editing，主窗口中就会出现一条新记录。输入一行数据完成这个记录，就向表中添加了一条新记录。
图10。8
当你转到下一条新记录时，你向上一条记录中输入的值会自动被保存。如果你需要，你可以用Microsoft Query 向表中输入几百条记录。 删除和修改表 你应该在建立表之前仔细设计它们，因为你在改变一个
已经存在的表时会受到很大的限制。例如，一旦已经建立了一个表，你就不能删除表中的字段或者改变
字段的数据类型。在这种情况你所能做的是删除这个表，然后重头开始（参及中级SQL”中
的“使用SQL创建记录和表”一节）。
要删除一个表，你可以使用SQL语句DROP TABLE。例如，又从数据库中彻底删除表mytable，你要使用如下的
语句： DROP TABLE mytable 警告： 使用DROP TABLE命令时一定要小心。一旦一个表被删除之后，
你将无法恢复它。 当你建设一个站点时，你很可能需要向数据库中输入测试数据。
而当你准备向世界提供你的网点时，你会想清空表中的这些测试信息。如果你想清除表中的所有数据
但不删除这个表，你可以使用TRUNCATE TABLE语句。例如，下面的这个SQL语句从表mytable中删除
所有数据： TRUNCATE TABLE mytable
虽然你不能删除和修改已经存在的字段，但你可以增加新字段。最容易的实现方法是使用SQL事务管理器
中的Manager Tables窗口。你也可以使用SQL语句ALTER TABLE。下面是一个如何使用这种语句的例子：
ALTER TABLE mytable ADD mynewcolumn INT NULL 这个语句向表mytable中增加了一个新字段
mynewcolumn。当你增加新字段时，你必须允许它接受空值，因为表中原来可能已经有了许多记录。
总结 这一章向你介绍了SQL。使用SQL，你可以操作Microsoft SQL Sever数据库。
你已经学会了使用SELECT语句从数据库中取出数据，你还学会了怎样使用CREATE TABLE语句和SQL
事务管理器来创建新表。最后，你学会了如何指明一系列重要的字段属性。


用PB做一个通用右键菜单 作者：田俊国 ----

对象被右键单击就弹出一个功能菜单是Windows应用程序一项不可缺少的功能。在PowerBuilder
应用开发中，可在需弹出右键菜单对象的rbuttondown事件中调用该菜单的Popmenu( )方法实现这一
功能。但多数情况下，应用中有多处要用到不同的弹出式菜单，如一一用这种方法就需要做大量的工
作。能不能做一个通用的菜单，可以用任何对象的rbuttondown事件激发弹出，并在弹出前动态改变
弹出菜单项的内容，当点击菜单项时又可执行被右击对象的相应用户事件？由于这一技术最大的特点
是当函数创建成功后，可在任何应用的任何对象中随心所欲的使用，随时随地弹出自己想要显示的菜
单项，并执行相应的自己需要的功能脚本，但目前各种资料上鲜有系统、成型的介绍。笔者抱着一种
探索的态度，作了一番仔细深入的研究，并小有收获，愿和大家共同探讨。
一、 创建弹出菜单m_popup
创建一个菜单m_popup,定义菜单条m_main，其下有十五个菜单项，分别命名为m_item1,m_item2,
……m_item15,各项的显示文本(text)分别为item1,item2,… …item15； 为m_popup菜单定义一个
Powerobject类型的Instance变量：Anyobject 脚本为： Powerobject Anyobject 给m_item1,m_item2,…
…m_item15各菜单项的Clicked事件下分别输入脚本：
Anyobject.triggerevent("ue_item1") //m_item1的clicked事件
Anyobject.triggerevent("ue_item2")
… …
Anyobject.triggerevent("ue_item15")
---- 4、定义几个菜单函数：
---- 1) setmenuitem(string itemstring),返回值为Null。
---- 参数Itemstring是由多个子串组成的。各个子串间用“|”间隔，每个子串为一个菜单项的显示文本(text)。
---- 该函数功能是把itemstring分解为多个子串，并把子串赋给相应菜单项的text。脚本如下：
int itempos,itemorder=1,i string currentitem
if len(itemstring)=0 then return itempos=pos(itemstring,"|") DO WHILE itempos<>0
// itempos为间隔符"|"的位置
currentitem=left(itemstring,itempos
-1) //取出子串
itemstring=mid(itemstring, itempos+1)
this.m_main.item[itemorder].text=currentitem
itempos=pos(itemstring,"|")
itemorder++
LOOP
this.m_main.item[itemorder].text=itemstring
for i=1 to itemorder
this.m_main.item[i].visible=true
this.m_main.item[i].enabled=true
end if
for i=itemorder+1 to 15
this.m_main.item[i].visible=false
next
2）
setitemdisable(integer itemorder) 返回值Null。 该函数把第itemorder菜单项置灰(disable)。 脚本如下：
if itemorder<1 or itemorder>15
then
return this.m_main.item[itemorder].enabled=false
3)popupmenu(integer x, integer y) 返回值Null。 该函数弹出菜单条m_main。 脚本如下： this.m_main.popmenu(x,y)
二、 rbuttondown事件激发弹出菜单m_popup
---- 至此，我们就可以在窗口中任意对象如DataWindow、 Picture、 SingleLineEdit、 ListBox、
PictureListBox、 DropDownPictureListBox、 MultiLineEdit、 ListView、 TreeView等的rbuttondown
事件中写脚本调用m_popup的函数来实现右键单击弹出一个菜单。
下面以数据窗口dw_1为例，在其rbuttondown事件下写入脚本，使得右击dw_1 可弹出菜单：
刷新/插入/删除/修改。
步骤如下： 1、事先给窗口定义一个m_popup 型instance变量om_1: m_popup om_1 2、dw_1的rbuttondown事件脚本：
if not isvalid(om_1)
then om_1= CREATE m_popup
end if //把菜单的anyobject指向被右击的对象(dw_1) om_1.anyobject=this
om_1.setmenuitem("刷新|插入|删除|修改") //可在此调用om_1.setitemdisable(itemorder)函数
disable某 菜单项。
om_1.popupmenu(this.x+this.pointerx(), this.y+this.pointery())
3、 给dw_1定义用户事件ue_item1、ue_item2、ue_item3 及 ue_item
4。 事件ue_item1脚本：
dw_1.retrieve()
事件ue_item2脚本：
long newrow newrow=dw_1.insertrow(0)
dw_1.scrolltorow(newrow)
事件ue_item3脚本：
dw_1.deleterow(0)
事件ue_item4脚本：
dw_1.update()
---- 这样，只要用户单击dw_1弹出的右键菜单，就可完成对dw_1的插入、删除、修改等功能。
三、 推广为全程函数
---- 如果应用中有许多地方要用此功能，我们可以把dw_1的rbuttondown事件脚本改造成一个全程函数。
---- 1、将变量om_1定义为global变量: ---- m_popup om_1
---- 2、定义一个全程函数 ---- pupmenu(powerobject sender,string itemstring,integer x,integer y)
返回值Null。其中参数sender为被右击的对象，itemstring为弹出菜单的菜单项字符串，x、y为菜单弹
出的坐标位置。 脚本如下：
if not isvalid(om_1)
then om_1= CREATE m_popup
end if
om_1.anyobject=sender
om_1.setmenuitem(itemstring)
sender.triggerevent("ue_beforepop") //激活sender用户事件。
om_1.popupmenu(x,y)
这样，上述dw_1的rbuttondown事件脚本就可改写为：
string items="刷新|插入|删除|修改"
popmenu(this, items ,this.x+this.pointerx(), this.y+this.pointery())
注意：在MDI 应用中，popmenu()函数需要改为：
popmenu(this, items ,w_frame.pointerx(),w_frame.pointery()) 其中w_frame为MDI主窗口名。
---- 在函数popmenu中,又激活了被右击对象的ue_beforepop用户事件。如有必要，你可以给被右击
对象定义一个ue_beforepop事件，在该事件中可调用om_1.setitemdisable()函数来屏蔽某个菜单项。
---- 最后，别忘了在应用的close事件里加上下列语句，及时释放系统内存。
---- if isvalid(om_1) then destroy om_1
---- 以上代码在Windows98 和Powerbuilder6.5平台上开发，并在多个大型系统中应用，效果很好。


在PB打印中实现人民币大写 侯晓伟

---- 根据我国的财务票据的当前特点，仍需要财务票据中人民币有大写一项。本人原来在利用
FOXPROW开发系统时，曾经利用FOXPROW编写过人民币大写的函数。现在本人又利用PB
编写了一个人民币大写的函数。您只要将您数据窗口中的人民币小写项数据值取出，利用我的
这个函数将其转换成大写方式，然后再赋到数据窗口的某项中即可。
---- 函数名：自定义函数：no_to_char_sell()
---- 函数内容：
string hz[15]
string money[2] //hz[16]='零'
if not rmb
then
hz[1]='一'
hz[2]='二'
hz[3]='三'
hz[4]='四'
hz[5]='五'
hz[6]='六'
hz[7]='七'
hz[8]='八'
hz[9]='九'
hz[10]='十'
hz[11]='百'
hz[12]='千'
hz[13]='万'
hz[14]='亿'
hz[15]='点'
else
hz[1]='壹'
hz[2]='贰'
hz[3]='叁'
hz[4]='肆'
hz[5]='伍'
hz[6]='陆'
hz[7]='柒'
hz[8]='捌'
hz[9]='玖'
hz[10]='拾'
hz[11]='佰'
hz[12]='仟'
hz[13]='万'
 hz[14]='亿'
hz[15]='元'
money[1]='角'
money[2]='分'
end if
string val, num2
val=num
do while left(val,1) = '0'
val = right(val,len(val)-1)
loop
if pos(val,'.') < > 0
then
num = left(val,pos(val,'.') - 1)
num2 =mid(val, pos(val,'.') + 1)
end if
string str='',str_tmp
 int i
for i= 1 to len(num)
str_tmp=mid(num,i,1)
choose case str_tmp
case '1' str=str+hz[1]
case '2' str=str+hz[2]
case '3' str=str+hz[3]
case '4' str=str+hz[4]
case '5' str=str+hz[5]
case '6' str=str+hz[6]
case '7' str=str+hz[7]
case '8' str=str+hz[8]
case '9' str=str+hz[9]
case '0' str=str+''
end choose
if str_tmp='0' then
if right(str,2)='零' or str='' or i=len(num)
then str=str
else str=str+'零'
end if
else
choose case len(num) -i +1
case 1 str=str
case 2,6,10 str=str +hz[10]
case 3,7,11 str=str +hz[11]
case 4,8,12 str=str +hz[12] //千
case 5,13 str=str +hz[13] //'万'
case 9 str=str +'亿'
end choose
end if
if len(num) -i +1 = 9 and mid (num ,len(num) - 8,1) ='0'
then if right(str,2)='零'
then str = left(str,len(str) - 2) str=str +'亿'
end if
if len(num) -i +1 = 5 and mid (num ,len(num) - 4 ,1) ='0'
then
if right(str,2)='零' then str = left(str,len(str) - 2) str=str +'万'
end if
next
if right(str,2)='零' then str = left(str,len(str) - 2)
end if
if left(str,4) ='一十' or left(str,4) ='壹拾' then str = right(str,len(str) - 2)
int lpos
lpos = pos(str,'亿万')
if lpos < > 0 then str =left(str , lpos +1) +mid(str,lpos +4)
end if
if pos(val,'.') =0 then
if rmb then return str+hz[15]
else return str
end if
end if
str = str + hz[15] //点or 元
int il_i
il_i = len(num2)
if rmb and il_I > 2 then il_i = 2
end if
for i= 1 to il_i
str_tmp=mid(num2,i,1)
choose case str_tmp
case '1' str=str+hz[1]
case '2' str=str+hz[2]
case '3' str=str+hz[3]
case '4' str=str+hz[4]
case '5' str=str+hz[5]
case '6' str=str+hz[6]
case '7' str=str+hz[7]
case '8' str=str+hz[8]
case '9' str=str+hz[9]
case '0' str=str+'零'
end choose
if rmb and right(str,2) < > ' 零' then str=str+money[i]
next
do while right(str,2) = '零'
str = left(str,len(str) - 2)
loop
return str
以上程序本人考虑到数字的各种组合，基本可以对任何数据进行转换成人民币大写。
本程序供大家参考，请大家指教。


如何在POWER BUILDER中使用WINSOCK控件 达伟春

---- 随着资源共享和实时通讯的需要，很多计算机应用程序早已甩开单兵作战的模式，转入联合行动。
---- 网络在计算机世界里，越来越发挥着举足轻重的作用。在WINDOWS应用程序中，处理实时通
讯最常用到的还是MICROSOFT公司提供的WINSOCK控件。许多资料都细述了WINSOCK在VB中的
使用方法，就连WINDOWS本身提供的hlp文件也是针对VB而写的。笔者因为实际应用的需要，琢磨
出了WINSOCK控件在PB中的应用方法。好东西不敢独自享用，拿出来与大家共享。
---- 下面以一个简单程序为例，说明WINSOCK控件在PB中的使用：
---- 一、在窗口中添加WINSOCK控件：
---- 在应用中新开一个窗口，在窗口画板中点击controls-->OLE菜单项，弹出Insert object窗口，
单击Insert control标签，从列表框中双击选定Microsoft Winsock control，将winsock的图标贴在窗口上。
---- 在程序中该控件名称定为winsock_a（甲方）和winsock_b（乙方）。
---- 二、设置信息输入输出文本框：
---- 在窗口中增加一个按钮cb_1,两个单行文本框sle_1,sle_2，分别用于输入要发送的字符串和接受对
方发送的字符串。
---- 三、设置通讯协议： ---- WINSOCK控件允许用户以UDP和TCP两种协议中任选一种进行通讯。
---- 1.UDP协议设置：UDP协议是一种无连接的通讯协议，在通讯之前，需要绑定remotehost和
remoteport属性，如果需要双向通讯，还要设置localport属性。
---- 在甲方（本机地址为：134.1.1.1)窗口的Open事件中加入如下语句：
winsock_a.object.protocol=1 //winsock通讯协议设为UDP协议
winsock_a.object.remotehost="134.1.1.2" //对方的ip地址
winsock_a.object.remoteport=6000 //对方的winsock通讯端口号
winsock_a.object.localport=6001 //本机的winsock通讯端口号
winsock_a.object.bind //绑定通讯协议
---- 在乙方（本机地址为：134.1.1.2)窗口的Open事件中加入如下语句：
winsock_b.object.protocol=1 //winsock通讯协议设为UDP协议
winsock_b.object.remotehost="134.1.1.1" //对方的ip地址
winsock_b.object.remoteport=6001 //对方的winsock通讯端口号
winsock_b.object.localport=6000 //本机的winsock通讯端口号
winsock_b.object.bin //绑定通讯协议
---- 2.TCP协议设置：TCP协议在通讯前需要进行连接。
---- 在甲方（作为服务器端）窗口的Open事件中加入如下语句：
winsock_a.object.protocol=0 //winsock通讯协议设为TCP协议
winsock_a.object.localport=6001 //本机的winsock通讯端口号
winsock_a.listen() //启动监听
---- 在甲方winsock_a控件的Connectionrequest事件中加入如下语句：
//接受到对方的连接请求后
if winsock_a.object.state< >0 then winsock_a.close()
end if
winsock_a.accept(requestID) //建立直接连接
//requestID是Connectionrequest事件自己的参数
---- 在乙方（作为客户端）窗口的Open事件中加入如下语句：
winsock_b.object.protocol=0 //winsock通讯协议设为TCP协议
winsock_b.object.remotehost="134.1.1.2" //对方的ip地址
winsock_b.object.remoteport=6000 //对方的winsock通讯端口号
winsock_b.connect() //发出连接请求
---- 3.无论采用哪种协议，都要在窗口的Close事件中加入如下语句：
if winsock_a/*或winsock_b*/.object.state< >0 then winsock_a.close()
end if
---- 否则可能第二次使用时发生异常问题
---- 三、开始通讯 ---- 在按钮cb_1（caption属性设为‘发送’）的click事件中加入如下语句：
winsock_a/*或winsock_b*/.object.send (sle_1.text)
---- 在winsock_a/*或winsock_b*/控件的dataarrival事件中加入如下语句：
//接受到对方数据后
string datastr1 winsock_a/*或winsock_b*/.object.getdata (def datastr1) sle_2.text=datastr1 //将数据字符串显示在文本框中
---- 以上程序实际上体现了聊天器的底层工作原理，稍加修改扩充就可以做成一个很好的聊天软件。


 PowerBuilder数据窗口编程技巧十则 蔡伟龙

---- PowerBuilder取得巨大成就的原因就是有Datawindow对象,DataWindow是具有功能强大和灵活多
变的特点，本人用PowerBuilder开发过一段时间后，，总结出一些技巧，以供广大的PB开发者借鉴使用。
---- 一．
---- 如何创建一个报表，如下形式
Quantity Running Total
5,000 5,000
2,500 7,500
3,000 10,500
12,000 22,500
---- 对于Running Total列，我们可使用计算列：CumulativeSum（Quantity for all），即可达到逐渐递
增求和的功能。
---- 二．
---- 数据窗口的数据送缓冲区之前确认的四个步骤 判断数据类型是否正确。如不正确则触发ItemError事件。
判断数据是否符合有效性规则。如不符合有效性规则，同样触发ItemError事件。 判断是否有数据被改动。
判断数据是否通过ItemChanged事件，如果数据和ItemChanged相斥，将触发ItemError事件。
---- 三．
---- 如何在DataWindow中用数据类型为Datetime的列为条件进行查找
---- 1．当要查找的日期条件是一常数时使用如下表达式：
---- ls_Find = "datetime_col
---- = DateTime ('1/1/1999')"
---- 2．当要查找的日期条件是一个变量时使用如下的表达式：
---- ls_Find = "datetime_col = DateTime ('" + ls_Date + "')"
---- 3．当要查找的日期条件是一个DateTime数据类型时使用如下表达式：
---- ls_Find = "datetime_col = DateTime ('" + String (ldt_DateTime) + "')"
---- 四．
---- 设置数据窗口Boolean型属性的三种方法
---- PowerBuilder提供了三种方法设置数据窗口的布尔型属性，分别是True/False, 1/0, 'Yes'/'No'。例如:
dw_1.Object.address.Visible = 0 dw_1.Object.address.Visible = False dw_1.Object.address.Visible = 'No'
---- PowerBuilder在处理上以字符串的形式保存属性，而不考虑属性值是布尔型、长整型或是字符型。
为了进一步理解，可以导出一个数据窗口并查看它的原码，可以发现即使是列的颜色属性它也是使用带双引
号的数字来表达。
---- 五．
---- 如何在DataWindow中快速删除多行
---- 在开发过程中可能经常有要进行多行删除的操作，一般都使用循环语句进行操作：
FOR ll_RowOn = 1 TO dw_1.RowCount() dw_1.DeleteRow(ll_RowOn) NEXT
---- 一个快速的删除方法是把要删除的行从主缓冲区中移到删除缓冲区中。例如，删除缓冲区中所有的行：
dw_1.RowsMove(dw_1, 1, dw_1.RowCount, Primary!, dw_1, 1, Delete!)
---- 不过不要忘了过滤的行在不同的缓冲区中。
---- 六．
---- 如何在DataWindow的SQL语法中不使用SELECT DISTINCT实现删除重复的行
---- 起先对你要显示唯一值的列进行排序："city A"，然后增加如下过滤字符串：" city < > city [-1] or GetRow () = 1"
---- 七．
---- 如何在分组形式的DataWindow中分别显示各组的行号
---- 当我们为Datawindow的每一行显示行号时，可以简单的放一个表达式为GetRow()
---- 计算列。但是对于分组的Datawindow，要分别显示各组的行号，则应使用表达式为
GetRow() - First(GetRow() for Group 1) + 1的计算列。
---- 八．
---- 如何改变列的字体颜色，提醒用户此列已做修改
---- 在列的Color属性中，输入如下表达式
IF (column_name < >column_name.Original, RGB(255, 0, 0), RGB(0, 0, 0))。
在这个条件中，如果此列已改变，则显示红色字体，否则显示黑色字体。这个表达式主要用
column_name < > column_name.Original比较当前列的值和原始列的值是否相同来达到判断的目的。
---- 九．
---- 在数据窗口中移走行,但不是去做过滤或删除操作
---- RowsDiscard()函数可做到这一点,它在数据窗口中执行移除工作,但被移走的行它不可被删除或做任
何修改性的保存。
---- 十．
---- 如何在多行显示的DataWindow 中的Footer Band中显示当前数据的首行和最后行的行号
---- 我们先看两个计算列的表达式： IF (GetRow() = First(GetRow() FOR Page), 1, 0) // 1 为当前页的第一行
IF (GetRow() < > 1 AND GetRow() = Last(GetRow() FOR Page), 1, 0) // 1 为当前页的最后一行
---- 由上面可知，在Footer Band中设置如下计算列表达式：
'Rows ' + String(First(GetRow() FOR Page)) + ' to ' + String(Last(GetRow() FOR Page)) + ' are displayed'。
即可达到这项功能。


 在PB的TreeView控件中实现自动查询 周剑峰

---- PowerBuilder提供的TreeView控件可以使我们以树形方式分层组织和显示数据，使程序的表现更为灵活，
用户的操作更加方便。通常情况下，在TreeView控件中通过展开和折叠各分枝，可以方便地对所需信息进行
查找和定位，但是如果该控件中的内容层次较多，用户对其内容又不非常了解时，要让用户逐一展开各分枝
进行定位就会使效率降低，这种情况下就需要提供一种自动查询方式，让用户输入要找的信息，由程序来完
成在TreeView控件中的查找和定位。
---- PowerBuilder的TreeView控件没有直接提供完成这一功能的函数或方法，也就是说不能直接在TreeView控
件的各节点中查找所需的数据。该控件具有一个FindItem()函数，可用于查找节点项，但该函数不能直接对我
们所要找的数据进行查找，例如字符文本或数值数据，它只能基于TreeView控件的某一节点项的句柄进行查找。
不过我们可以利用该函数来获得节点项的句柄，再通过该句柄进一步获得节点项的详细信息。
FindItem() 函数的语法如下： treeviewname.FindItem ( navigationcode, itemhandle )
---- 其中，treeviewname是我们要在其中进行查找的TreeView控件的名称；
---- navigationcode参数是一个TreeNavigation枚举数据类型，用来表明itemhandle参数与要找的节点项之
间的关系，有效的取值如下： RootTreeItem! 查找第一层的第一项； NextTreeItem! 在同一级中查找itemhandle
后的下一项； PreviousTreeItem! 在同一级中查找itemhandle前面的一项； ParentTreeItem! 查找itemhandle
的父节点； ChildTreeItem! 查找itemhandle的第一个子节点； FirstVisibleTreeItem! 查找在TreeView控件中
可视的 第一项而不论层次； NextVisibleTreeItem! 查找itemhandle后已展开的第一项，不分层次；
PreviousVisibleTreeItem! 查找itemhandle前已展开的第一项，不分层次； CurrentTreeItem! 获得当前所选项；
DropHighlightTreeItem! 查找DropHighlighted属性最近被设置过的项。 Itemhandle参数表示节点项的句柄。
---- 该函数返回一个长整形值，若执行成功返回找到的节点的句柄，若失败则返回 -1。除了ChildTreeItem!，
FindItem不查找未展开的项。利用FindItem只能获得节点项的句柄，要得到某一节点的详细信息，还需要利用
TreeView控件的GetItem()函数。该函数可通过节点项句柄得到节点项的状态信息，如label、picture index、
childen等，只有比较这些状态信息如label的值，我们才能确定是否已经找到所需的节点项。该函数如下所示：
treeviewname.GetItem ( itemhandle, item)
---- 其中，treeviewname是TreeView控件的名称；itemhandle 是要获取信息的节点项的句柄；item参数
是一个TreeViewItem系统对象变量，用于存放节点项的信息。
---- 下面的程序实现在TreeView控件进行自动查找，为了简化篇幅，而不用再建立TreeView控件，我利用
PowerBuilder v6.5所提供的Code Examples中的一个现成的例子来建立查询。在PB 6.5的例子中有一个
用来说明TreeView控件和Datawindows进行联系的例子，该例子中有一个w_tv_dw_link窗口，该窗口中具有
一个TreeView控件和多个Datawindows，原程序已对TreeView控件的相应事件编好了程序，使得运行时
TreeView控件中能显示相应的数据并且能展开各分枝。要在该TreeView控件中进行查询，需要在窗口
w_tv_dw_link中增加以下几个控件，其属性设置如下： 控件 属性 属性值 StaticText Name ‘st_1’ Text
‘查找:’ SingleLineEdit Name ‘sle_search’ CommandButton Name ‘cb_ok’ Text ‘确定’
---- 由于原程序中为了响应用户改变窗口的大小，在窗口w_tv_dw_link的Open和Resize事件中编有相应的
程序，使TreeView控件和各个DataWindow能布满整个窗口。为了使我们新增的几个控件能正常显示，这里
简单地使程序对窗口的Resize事件以及Open事件初始化各控件的位置和大小不作响应，这需要把
w_tv_dw_link的Open和Resize事件中的以下语句屏蔽掉（在行首加‘//’使其成为注释）：
wf_ResizeBars() wf_ResizePanels()
---- 然后就可以在命令按钮cb_ok的clicked事件中编写以下程序，实现在TreeView控件中进行查找：
setpointer(hourglass!)
long ll_handle,ll_handletree[],ll_level
int li_find
treeviewitem ltvi_1
if sle_search.text = "" then messagebox("提示","请先输入查询信息!!")
return
end if
li_find = 0 tv_1.setredraw(false)
ll_handle = tv_1.finditem(RootTreeItem! ,0)
tv_1.expandall(ll_handle)
do while ll_handle >0
tv_1.getitem(ll_handle,ltvi_1)
if ltvi_1.label = sle_search.text then li_find = 1
exit
end if
ll_handle = tv_1.finditem (NextVisibleTreeItem! ,ll_handle)
tv_1.expandall(ll_handle)
loop
ll_level=1
if ll_handle > 0 then ll_handletree[ll_level]=ll_handle
ll_handle=tv_1.finditem (ParentTreeItem! ,ll_handle)
do while ll_handle > 0
ll_level ++
ll_handletree[ll_level]=ll_handle
ll_handle=tv_1.finditem (ParentTreeItem! ,ll_handle)
loop
end if
ll_handle = tv_1.finditem(RootTreeItem! ,0)
do while ll_handle > 0
tv_1.CollapseItem(ll_handle)
ll_handle = tv_1.finditem(NextTreeItem! ,ll_handle)
loop
do while ll_level > 0 and li_find = 1
tv_1.expanditem(ll_handletree[ll_level])
if ll_level = 1 then
tv_1.selectitem(ll_handletree[ll_level])
tv_1.setfocus()
exit
else ll_level --
end if
loop
tv_1.setredraw(true)
if li_find = 0 then messagebox("提示","所给信息没有找到!!")
end if
---- 该程序是在TreeView控件中逐项进行查找比较，因而执行的效率和显示结果都不是很理想，但我们在
实际应用中，通常TreeView控件都是与DataWindow相联系的，因而很容易借助DataWindow实现顺着
TreeView控件的树枝往下查找，执行速度快并且定位准确（不必展开一些不必要的分枝）。而且还很容易
实现模糊查询（部份匹配）。


 用PB5设计串口通讯程序 王学军

---- PowerBuilder5.0是一种面向对象的、具有可视图形界面的、快速的交互式开发工具，它通过不同数据库
采用各自的专用接口或通过ODBC接口，同时支持多种关系数据库系统、支持多文档界面（MDI）、对象嵌入
与链接（OLE）、动态数据交换（DDE）。利用其独特的数据窗口对象，无须编写SQL语句，便可直接对数
据库进行查询、修改、插入、删除、浏览、打印，以多种文件格式打开和存储数据，因此深受广大用户的欢
迎。在实际应用过程中经常遇到PowerBuilder与其它设备通过串口进行通讯的程序设计如IC卡应用系统中需
通过串口与IC卡读写器进行通讯；在控制系统中，需通过串口与下位机进行通讯。下面介绍PowerBuilder5.0
中常用的两种串口通讯设计方法。
一、 调用Windows SDK函数进行串口通讯
---- 1、Windows SDK函数介绍
OpenComm int OpenComm(LpszDevControl, CbInQueue,cbOutQueue)
LPCSTR lpszdevcontro l 设备控制信息的地址 UINT CbInQueue 接受队列的大小（以字节为单位）
UINT cbOutQueue 发送队列的大小
返回：如调用成功，返回值标示此打开的设备否则其返回值小于0
·CloseComm Int CloseComm(idComDev)
Int idComDey 要关闭的设备 返回值：调用成功返回0，
否则返回负值
·WriteComm int WriteComm(idComDev,lpvBuf,cbWrite)
int idComDev 通讯设备标示符 const void FAR * lpvBuf 数据缓存区地址 int ,cbWrite 要写的长度
返回值：如果成功返回所写的字节数，否则返回值小于0
·ReadComm Int ReadComm(idComDev,lpvBuf,cbRead)
int idComDev 通讯设备标示符 const void FAR * lpvBuf 数据缓存区地址 int cbRead 要读的字节数
返回值：如果成功返回所读的字节数，否则返回值小于0
·FlushComm Int FluseComm(idComDev,fnQueue)
Int idComDev 通讯设备标示符 Int fnQueue 要刷新的队列 返回值：成功时返回0，否则返回为负
---- 2、示例：
---- 图一(略)为一人事管理系统中的查询窗口。如果想与被查询人拨打电话，可输入该人姓名，在输入
过程中，数据窗口会显示满足要求的所有人姓名，双击该行即将该人电话号码显示于单行编辑窗内，
然后按“拨号”按钮即可。
---- 程序设计如下：
---- 在窗口设计菜单中选取：Declare菜单?Declare External Function 输入：
Function int OpenComm(String Comm, Uint Inqueue,Uint Outqueue)Library “USER.EXE”
Function int CloseComm(int lpt)Library “USER.EXE”
Function int WriteComm(int lpt,String buf,int Size) Library “USER.EXE”
Function int FlushComm(int lpt, int no_que)Library “USER.EXE”
“拨号”按钮的”Clicked” 事件编程如下：
int ret String port_2,ph_code port_2=”Com2” //取单行编辑框内容，并用atdt拨号
ph_code=”atdt”+sle_code+”~r~n” //打开串口
ret=OpenComm(port_2,128,128) //拨号开始
FlushComm(ret,0)
FlushComm(ret,1)
WriteComm(ret,ph_code,len(ph_code))
WriteComm(ret,”~r~n”,2)
CloseComm(ret)
二、 利用Microsoft公司提供的通讯控件
---- 利用Microsoft公司提供的通讯控件(Mscomm.vbx,用于16位通讯程序、Mscomm32.ocx用于开发32位
通讯程序)可方便实现串口通讯
---- 1、 常用属性 CommPort:设置或返回串行端口号，缺省为1。
Setting:设置或返回串行端的波特率、奇偶校验位、 数据位数、停止位。缺省值为"9600，N，8，1"。
PortOpen：设置它为True则打开端口，设置它为False则关闭端口。
InBufferSize:设置或返回接收缓冲区的大小，缺省为1024 字 节。
InBufferCount:返回接收缓冲区内的等代读取的字节个数，设属性为0清除接收缓冲区。
OutBufferSize:设置或返回发送缓冲区，缺省为512字节。
OutBufferCounter:返回发送缓冲区内等待发送的字符数，可用0来清空缓冲区。
Output:向发送缓冲区传送数据
RThreshold: 该属性确定当接收缓冲区内字节个数达或超过该值后就产生代码为ComEventReceive
的OnComm事件，设置位0，则不产生OnComm事件。
CommEvent:有通讯错误或事件发生时产生OnComm事件，CommEvent事件的值可以用来确定引
发OnComm事件的具体的错误或事件常用的有以下几种（更详细的资料可参考MSComm控件帮助文件）
ComEventbreak：接受到中断信号
ComEventFrame：硬件检测到帧错误。
ComEventRxOver：接收缓冲区溢出。
ComEventTxFull：发送缓冲区溢出。
ComEventReceive：接受到规定的字符数。
InputLen:设置或返回接收缓冲区内用Input读入的个数。若取0，则读取整个缓冲区的内容。
Input:返回并删除接受缓冲区的数据。
---- 2、 示例：
---- 下面举一接收程序示例：制作一通讯接收窗口，选择菜单Controls→OLE→Insert Control→Microsoft
Communications Control，
---- 在窗口中出现通信控件图标单击鼠标右键，选择OCX Properties设置常用属性，也可在程序中进行设置
窗口的Open事件程序如下
ole_1.object.CommPort=2
ole_1.object.Settings="9600,N,8,1"
ole_1.object.PortOpen=ture
ole_1.object.Rthreshold=10
ole_1.object..Inputlen=0 . .
通信控件的oncomm事件的程序如下
choose case ole_1.object.CommEvent
case comEventBreak //接收到中断信号
case comEventCDTO //处理CD超时
. . .
case comEvReceive sle_1.text=ole_1.object.Input
. .
end choose
---- 以上是一个基本的例子，在实际应用过程中应根据不同的情况进行不同的设计，如波特率、帧格式等。
---- 总之，利用以上方法可以方便的在PowerBuilder中进行串口通讯，并对接收的数据利用PowerBuilder进
行数据处理。


 PB修改数据库表结构的技巧 李清

---- PowerBuilder是目前公认的、最佳的数据库前端开发工具之一。在实际开发应用中，经常会遇到修改
数据库表结构的情况。用PowerBuilder修改数据库表结构与用Foxbase修改数据库结构不同。在PowerBuilder
中，除可以在数据库表结构中添加字段名外，无法直接修改数据库表结构，如删除某个字段名或类型或字
段名长度等，都会导致数据库表记录的丢失。我们通过实践探索，总结了几种方法仅供大家参考，请大家
批评指正。
一、 修改数据库表结构，数据库表中没有记录
---- 方法１：将表结构以Export Table 文件格式输出。
从Database中选择要修改的数据库表Table。
从Objects中选择Export Table／View Syntax to Log，取名保存即选Save Log As，以*.sql格式保存。
从Objects中选择Database Administration。
从File中选择Dos Open，选文件名File name，*.sql，打开进行修改。
从Objects中选择Execute　SQL。即产生一新的数据库表结构。
---- 注意：Table名不要同名。
---- 方法２：将表结构以SQL Syntax 文件格式输出。
从Database中选择要修改的数据库表Table。 按Preview键。
从File中选择Save Rows as，File Format 选SQL Syntax，取表名*.sql保存。
从File中选择Dos Open，打开表名*.sql，进行修改。 取表名后，Execute　SQL。
---- 注意：此种方法没有生成Primary Key。
二、修改数据库表结构，保存原数据库表中记录
---- 方法１：先修改数据库表结构，用SQL语句转移数据库表中记录 将表1生成表1.sql，修改结构，将表1另取名为表2。 运行表2.sql，生成表2。此时产生一新的数据库表结构。
从Objects中选择Database Administration，写SQL Statement：
Insert　into 新表名　select *　from 旧表名；
Execute　SQL。将旧表中所有字段内容转入新表中。 删除表1，从File中选择Dos Open，打开文件名File
name即表2.sql，将表2统一改为表1。运行表1.sql，生成新结构的表1，此时无记录。
按步骤3，将表2中的记录全部转入表1中。
    若将部分字段转入，写SQL Statement：
    insert into 新表名（字段１，字段２．．．．．．）
    select 字段１，字段２．．．．．．from 旧表名；
Execute　SQL。旧表中部分字段内容转入新表中。
---- 方法２：将表结构和记录以SQL Syntax 文件格式输出。 从Database中选择要修改的数据库表Table，按Preview键。 从File中选择Save Rows as，
File　Format 选SQL Syntax，取表名*.sql保存。
从File中选择Dos Open，打开表名*.sql，进行修改。 取表名后，Execute　SQL。
---- 方法３：将表结构和记录以TXT 文件格式输出。 从Database中打开数据库表，按Preview键。
从File中选择Save Rows as，File　Format 选TXT，取表名*.txt保存。
将表生成表.sql，修改结构，运行表.sql，此时产生一新的数据库表结构。
从Database中打开数据库表，按Preview键。 从Rows中选择Import，选*.txt。
从File中选择Save changes to Database。
---- 注意：方法１适用于任何修改数据库表结构的任何情况。方法２和方法３对删除字段名不适用。


数据窗口快速点击时出现错误的解决 北京天顺公司软件部 陈俊明

我在使用Powerbuilder 6.0时发现,在表单中使用一个数据窗口来对数据进行操作时,如果用鼠标对数据窗口进行连续、
快速的点击,此时Windows95会弹出一个错误窗口,"Pb60.exe进行了非法操作,程序将被关闭"。后来我又发现，
当数据窗口的‘clicked’事件和‘doubleclicked’事件中写入了代码时，这个错误信息并不出现。因些我想，
如果在‘clicked’事件和‘doubleclicked’事件中并没有任何操作时，如何才能避免出现此错误窗口呢？首先，
我猜想这可能是Powerbuilder的bug，在没有代码时，Powerbuilder并不生成相应的函数，当点击数据窗口时，
Powerbuilder认为产生了一个‘clicked’或‘doubleclicked’事件，于是就去调用相应的函数，当没有代码时，
此函数指针为一空指针，于是就产生了上述现象；而当这两个事件中有代码时，相应的函数被执行。由此我想，
能不能在这两事件中加入一条注释，让Powerbuilder产生一个空的函数呢？事实证明，这个想法是对的。


 PB6.5中有关OLE控件功能的三个缺点 北京安美建

----由于PB6.0在汉字方面有问题，因此利用Sybase公司免费提供的PB6.5补丁程序进行升级是PB6.0用户
较现实的选择。但笔者在用升级后的PB6.5 开发一个需串口操作微机数据库项目的过程中，发现PB6.5中的
OLE(Microsoft喜欢叫她ActiveX)在汉字方面仍存在一 些问题。这里不提PB5.0和6.0中存在的有关
OLE控件的缺点。首先在PB中创建一个窗口w_1。
---- 问题一嵌入PB中的微软OLE控件MSCOMM32.OCX的output属性为char变量或char()函数时，程
序执行时为非法。
----把MSCOMM32.OCX嵌入到w_1中，命名为ole_1。执行以下语句时程序将 提示“在进入output属性时非法，
并中断程序”：ole_1.object.output=char(48)
----如果从以下两点来看，这个错误就不应该存在：
(1)从MSCOMM32.OCX本身来看，output属 性(见MSDN中有关MSCOMM32的内容)完全可以等于与PB中char
变量和char()函数相对应的变 量和函数。
(2)MSCOMM32嵌入到PB中后，PB把其output属性变量当作any型变量来处 。Any型变量就包括char(或character)
型变量。
----处理这个问题的简单方法是：把char 变量或函数的值赋予一个string变量，然后再使output属性值为
该string变量值。如：ole_1.object.output=string(char(48))
----这样PB将正常运行。

----问题二　OLE控件接收到的汉字数据无中生有。
----利用VC++5.0 的MFC　ActiveXControlwizard的所有默认选项创建一个ActiveX控件TempControl，并增加
一个CString属性SetWord(m_setWord为该属性的CString实例)；在OnDraw()函数中用TextOut 函数在屏幕上显示
m_setWord；在处理给SetWord付值的函数OnSetWordChanged()中加上InvalidateControl() 以便显示更新。
----把TempControl控件嵌入w_1中，命名为ole_2。执行如下代码：ole_2.object.setword="开始通讯实验"
----结果在屏幕上TempControl控件内不但显示了“开始通讯试验”，而且后面还增加了多余 的字符。多余字符
且具有一定的规律性，即总是在汉字末尾首先出现0号ASCII字符，随后有几 个莫名其妙的汉字或ASCII字符。
如果用户还停留在用PB6.0的时代，那您的运气将更糟(在PB6.0 调试状态执行就见不到需要的汉字，
更不用说编译成exe文件后将汇合上其他众所周知的汉 字问题了)。但当SetWord为普通 可打印ASCII字符
时不出现该现象。
----在这个简单的控件 里我们没有其他复杂的功能，只是简单地由w_1把汉字数据付予TempControl
控件唯一的属性SetWord， 然后由TempControl控件显示在屏幕上。使用如此简单的控件竟有问题，
因此PB6.5中给OLE控 件进行汉字付值是有一点问题。
----在这个问题里，Microsoft的产品也不是完美无缺的。一 般地，CString实例是不包括0号ASCII字符的。
即把一组中间包括NULL(0号ASCII字符)字符的 字符串付予CString的实例时，该实例自动取NULL以前的字符们，
即m_setWord就不该包括NULL 及其后面的字符。当然了，在Microsoft产品内部不存在这个问题。
----这个问题的解决方法 是很简单的，只需在TempControl中OnSetWordChanged()函数的开头把
NULL以后的内容去掉 就可以了。但现成的商业控件就没这么轻而易举了。

----问题三　在PB中读取到的OLE控件 内部汉字数据将可能残缺不全。
----在TempControl控件的OnSetWordChanged()中InvalidateControl() 之前加上：m_setWord="字符已变";
即在控件内部改变m_setWord值。在问题二所示那行PB程 序后增加如下语句：
stringwordword=ole_2.object.setwordmessagebox("",word)
---- 运行时，对word值与“字符已变”进行对比来看，“字符”尚存，“已变”二字真的已经被变成其他
莫名其妙的字符了。该问题难以补救。
----综合以上看来，PB6.5的OLE在汉字数据处理方面 有一定的缺点。
----奉献如上内容以便众同仁在项目设计时，请谨慎处理PB6.5的OLE控件功 能。


 PB中一个类Word打印对话框的实现 许昌市电力设计研究所 高灵超

----Powerbuilder数据窗口可用于生成各种复杂的报表，但如何让用户在使用时控制打印份数、打印范围等信息呢？
----我们在开发我局MIS系统时，经过对我局报表打印需求的分析，提出了如下的功能要求：
----1.必须能够控制象打印份数、打印（页码）范围等信息，这是基本的要求。
----2.我们系统中装有多台打印机，必须能让用户根据自己需要选择使用哪台打印机，必要时能对所选打印机进行配置。
----3.在设计时，对每一个报表需要什么样的纸张，要多大的边距合适，都已确定，所以使用时不需要页面的设置。但由于不同的报表使用不同的纸张，必须在使用时给出提示，以便用有机会换上合适的纸张（或换用合适的打印机）。
----根据以上要求，我们设计了如下图所示的对话框。下面把设计过程以及用到的技术介绍一下。
----首先使用窗口画板画出窗口,名字为w_printdlg，窗口类型为respons。“打印机”组框中的“名称”文本控制的名字为st_printname，“设置”按钮的名字为cb_printsetup。“页面范围”组框中的无线按钮组名字分别为rb_pagearrangeall,、rb_pagearrangecurrent、rb_pagearrange，单行编辑框名字为sle_pagearrange,其enable属性为false。“副本”组框中的编辑域名字为em_copies。“打印”旁边的下拉列表框名字为ddlb_printwhat，列表有三项，分别为所选页面、偶数页、奇数页。“纸张”组框中的下拉列表框名字为ddlb_paper，列表内容为Poerbuiler定义的43种纸张类型，分别用0-42的数字来表示。“纵向”无线按钮的名字为rb_portrait，“横向”无线按钮的名字为rb_landscape。表示纵向和横向的图片框分别为p_portrait和p_landscape。命令按钮“确定”和“取消”的名字分别为cb_ok和cb_cancel。
----以上工作完成之后，就要着手编写程序。
----首先为窗口定义实例变量idw_toprint，类型为datawindow，用于保存要打印的数据窗口。再定义一个窗口事件ue_initcontrols（也可以定义为窗口函数），其程序内容如下：
//eventue_initcontrols() //设置窗口中各控件的初始值
st_printername.text=idw_toprint.object. datawindow.printer//当前打印机名字
em_copies.text="1"//默认打印份数为1
rb_pagearrangeall.checked=true//默认范围为全部页
sle_pagearrange.enabled=false //页面范围编辑框无效
ddlb_printwhat.SelectItem(1)//默认为“所选页面” //打印方向
integerli_temp
li_temp=integer(idw_toprint.object. datawindow.print.orientation)//取出设计时的方向
ifli_temp=1then//横向
rb_landscape.checked=true//选中
p_landscape.visible=true //图片显示
p_portrait.visible=false elseifli_temp=2then//纵向
rb_portrait.checked=true
p_portrait.visible=true
p_landscape.visible=false
endif //纸张类型
li_temp=integer(idw_toprint.object. datawindow.print.Paper.Size)//设计时的纸张类型
ddlb_paper.SelectItem(li_temp+1)//列表框中选中
然后，在窗口的open事件中编写如下脚本：
//openevent //要使用OpenWithParm()打开，参数中包含要打印的数据窗口。
idw_toprint=message.powerobjectparm //从参数中取出要打印的数据窗口
this.eventue_initcontrols()//初始化窗口各控件
在无线按钮rb_pagearrangall的clicked事件中编写脚本如下：
sle_pagearrange.enabled=false
在无线按钮rb_pagearrangcurrent的clicked事件中编写脚本如下：
sle_pagearrange.enabled=false
在无线按钮rb_pagearrang的clicked事件中编写脚本如下：
sle_pagearrange.enabled=true//当此无线按钮选中时才可用
在命令按钮cb_printersetup中编写如下脚本：
//cb_printersetup
PrintSetup()//设置打印机
parent.eventue_initcontrols()
在无线按钮rb_portrait的clicked事件中编写如下脚本：
//纵向
p_landscape.visible=false
p_portrait.visible=true//显示纵向图片框
在无线按钮rb_landscape的clicked事件中编写如下脚本：
//横向
p_portrait.visible=false
p_landscape.visible=true
在命令按钮cb_cancel的clicked事件中编写如下脚本
//cb_cancel执行程序
CloseWithReturn(parent,2)//如果选择取消，返回2
在命令按钮cb_ok的clicked事件中编写如下脚本
//cb_ok执行程序
stringstr_temp
integerli_temp
longll_row //打印什么
li_temp=ddlb_printwhat.FindItem (ddlb_printwhat.text,0)-1
idw_toprint.object.datawindow. print.page.rangeinclude=li_temp //打印范围
ifrb_pagearrangeall.checkedthen//全部页
str_temp=""
elseifrb_pagearrangecurrent.checkedthen//当前页
ll_row=idw_toprint.GetRow()
idw_toprint.object.datawindow.print.preview="Yes" //设成预览模式
str_temp=idw_toprint.Describe("evaluate ('Page()',"+String(ll_row)+")")//计算页码
idw_toprint.object.datawindow.print.preview="No"
elseifrb_pagearrange.checkedthen//输入范围
str_temp=sle_pagearrange.text
endif
idw_toprint.object.datawindow.print.page.range=str_temp //副本份数
iflen(em_copies.text)>0then
idw_toprint.object.datawindow.print.copies =Integer(em_copies.text) endif //纸的方向
li_temp=0
ifrb_landscape.checkedthen li_temp=1
elseifrb_portrait.checkedthen li_temp=2
endif
idw_toprint.object.datawindow.print.orientation=li_temp //纸的尺寸
li_temp=ddlb_paper.FindItem(ddlb_paper.Text,0) //第li_temp项
idw_toprint.object.datawindow.print.paper.size=li_temp
closewithreturn(parent,1)
----几点说明：
----1.原点访问语法：程序中多次用到了象datawindowcontrol.object.datawindow.print.attribute 语法，用语控制数据窗口的打印属性。原点访问语法用语代替原来的Discribe和Modify函数，更符合面向对象的语言习惯。关于print属性的详细信息，请参见Powerbuilder帮助。
----2.当前打印页的计算：先用GetRow得到当前行数，再用Describe和Evaluate函数来计算数据窗口表达式，得出当前页号。注意在计算只前，先把要打印的数据窗口置成预览模式，否则计算出来的是显示页号，和打印页号不一致。
----3.窗口内外信息的传递：虽然你可以用全局变量传递信息，但那决不是一个好的注意。使用OpenWithParm函数打开窗口可以向窗口传递一个参数，使用CloseWithReturn函数关闭窗口可以返回一个参数，根据参数的类型，其结果被放在系统全局变量message的相应成员中。如果要传递多个参数，可以把要他们定义为一个结构来传递。
返回


 在PB窗口之间传递参数的方法作者：panya

首先，我们看看以下几个函数：
CloseWithReturn ( windowname, returnvalue )
OpenWithParm ( windowvar, parameter, windowtype {, parent } )
OpenSheetWithParm ( sheetrefvar, parameter {, windowtype }, mdiframe {, position {, arrangeopen } } )
windowname.OpenUserObjectWithParm ( userobjectvar, parameter, userobjecttype {, x, y } )。
所有函数都有一个在进行窗口操作（打开或者关闭）时存入Message对象的参数（parameter或returnvalue）。　系统Message对象有三种存储数据属性（依赖于窗口操作时的参数赋予的类型而定）：
Message.DoubleParm NumericMessage.PowerObjectParm PowerObject (PowerBuilder对象，包括用户自定义的结构数据类型)
Message.StringParm String
从上看来，如果是传递一个参数，可以如下写Script：openwithparm(w_wantparm,parmtotrans) 在w_wantparm窗口的open事件中：
string ls_getparmls_getparm=message.stringparm //或者：ls_getparm=message.doubleparm，根据parmtotrans参数的类型而定……//
接下来其他语句　如何需要传递多个参数，可以使用如下两种方法：
方法一、在openwithparm(w_wantparm,parmtotrans)中，parmtotrans为结构数据类型：lstr_delaredstr lstr_getparm接下来在w_wantparm的open事件中：lstr_delaredstr lstr_getparmlstr_getparm=message.powerobjectparm……//接下来其他语句其中，lstr_delaredstr为用户自定义结构数据类型，是值传送的，所以就算源结构变量被DESTROY了，它还是可以照常使用的。　方法二、首先创建一个类用户对象u_n_parmtostore 在该对象中将你需要传递的各种参数申明成实例变量，例如： Public: Integer IDString NameString EmailString Homepage... 则可以如下打开窗口w_wantparm，并传递参数：
u_n_parmtostore lnv_paramtotrans lnv_paramtotrans.id=1 lnv_paramtotrans.name="panya" lnv_paramtotrans.email="panya@163.net"lnv_paramtotrans.homepage="http://panya.163.net"……OpenWithParm ( w_wantparm, lnv_paramtotrans )
在 w_wantparm 的 open 事件中访问传来的参数 u_n_parmtostore lnv_getparaminteger li_getidstring ls_getnamestring ls_getemailstring ls_gethomepagelnv_getparam = Message.PowerObjectParm li_getid=lnv_getparam.idls_getname=lnv_getparam.namels_gethomepage=lnv_getparam.homepagels_getemail=lnv_getparam.email……//接下来其他语句两种方法比较而言，第一种方法比较简单实用，但第二种方法更加灵活。　另外，用Message传递参数必须注意：
  1. 最好执行完窗口操作（打开或关闭）后马上将Message对象的值传给变量，因为其他script 也有可能用到Message对象。
  2. 避免传送NULL对象，以免程序出错。


 PB应用程序所需的运行环境 鹿寒

经过编译生成的PowerBuilder应用程序需要如下运行环境:
PowerBuilder 运行时动态连接库( Runtime Library )
在PowerBuilder 6.0中需要如下DLL库:
PBVM60.DLL (PowerBuilder 虚拟机)(必需)
PBDWE60.DLL (DataWindow 引擎)(可选)
PBRTC60.DLL (Rich Text Control)(可选)
PBTRA60.DLL (DLL used for tracing db calls)(可选)
与所需连接的数据库的直连接口(Native Driver) 或 ODBC接口(ODBC Driver)
PBSYC60.DLL (连接Sybase 的直连接口)
PBO7360.DLL (连接Oracle7.3的直连接口)
所需连接的数据库的Client端(如Sybase的Open Client, Oracle 的SQL Net) 以上是Sybase公司的建议。
我的经验是，编译好的程序必须有pbdwe60.dll和pbvm60.dll才行。
另外，如果使用ODBC联结数据库则还需pbodb60.dll，如果使用的专用接口，则需对应数据库的DLL，如Oracle7.3用PBO7360.DLL，Sybase用PBSYC60.DLL


 将窗口最小化为时钟旁的小图标编辑整理：panya

建立主窗口“w_main”：
  窗口定义如下“Local External Fuctions”:
function long LoadLibrary( ref string string ) LIBRARY "KERNEL32" Alias For LoadLibraryAfunction long FreeLibrary( long long ) LIBRARY "KERNEL32"
function boolean Shell_NotifyIcon( ulong long, ref s_str str ) LIBRARY "SHELL32" Alias FOR "Shell_NotifyIconA"
function long LoadIcon( long long, long long ) LIBRARY "USER32" Alias For LoadIconA
  定义结构型变量：s_str:
size | Unsignedlong
Hwnd | Long
Id | Unsignedlong
Flags | Unsignedlong
Callbackmessage | Unsignedlong
Icon | Long
tips[64] | Character

  定义实例变量：private:s_str istr_icon
  窗口最小化按钮Click事件写入如下
Script:string ls_resourceLong ll_handle// 下三句为装入图标资源,notepad.exe即为写字板，exe执行时显示的图标内定为1
ls_resource = "notepad.exe"
ll_handle = loadlibrary ( ls_resource )
istr_icon.icon = loadicon ( ll_handle, 1 )// 窗口回调事件号,pbm_custom01即为1024,02为1025,依此类推
istr_icon.callbackmessage = 1024
istr_icon.tips = "应用程序"
istr_icon.hwnd = handle ( parent )
istr_icon.size = 88
istr_icon.id = 1// 标识值,即为显示tips 4, 显示icon 2, 激活窗口对应事件号istr_icon.flags = 7// 显示icon关键函数, 0为显示，1为修改，2为删除
shell_notifyicon ( 0, istr_icon )parent.hide ( )// 释放资源
freelibrary ( ll_handle )　为窗口创建用户定义事件ue_event，event id为pbm_custom01，为其写script:
m_popup lm_popup
Integer li_X, li_Y
choose case lparam
Case 513 // LButtonUp// 删除图标
Shell_NotifyIcon( 2, istr_icon )// 显示窗口
this.show ( )
CASE 517 // RButton
Upli_X = This.Xli_Y = This.Y// 移动到屏幕外以免show 时看到，你可关闭此句看什么效果
This.Move ( - This.Width - 10, - This.Height - 10 )// 加这句才能看到菜单条（菜单条属于此windows)
 This.Show ( )
lm_popup = CREATE m_popuplm_popup.m_item.PopMenu ( PointerX ( ), PointerY ( ) )// 恢复设置
This.Hide ( )
This.Move ( li_X, li_Y )
DESTROY lm_popup
end choose 　其中，“m_popup”为最小化成TRAYICON图标时点击右键时弹出的菜单。


Powerbuilder 中制作进度条 panya整理，同事程先生提供

在VB和Delphi等语言中都提供了“进度条”控件，PB中没有现成的控件提供，但我们可以做一个“进度条”的用户定义对象（user object），象控件一样在程序中调用。具体方法如下：新建一个用户对象UO_PROGRESSBAR，加上两个长方形(rectangle)控件r_1和r_2，同时加上两条线(line)控件ln_1和ln_2。线用于增加r_1的立体效果，r_2颜色为兰色，用于显示进度。R_1属性为：
X=5
y=4
width=1408
height=64
Visible=true
LineColor=33554431
FillColor=33554431
FillPattern=solid!
LineStyle=continuous!
LineThickness=12
R_2属性为：
X=5
y=4
width=41
height=60
Visible=true
LineColor=16777215
FillColor=16711680
FillPattern=vertical!
LineStyle=continuous!
LineThickness=12
  先定义窗口的实例变量(Instance Variables)：
private:int thick; //进度条与外框的距离
int bpublic:long max,min; //max,min
long pos; //poslong color;
窗口Constructor的Script：
long w,h
thick=5
b=1
w=this.width
h=this.height
r_1.x=thick
r_1.y=thick
r_2.x=thick
r_2.y=thick
r_1.width=w
r_1.height=h
r_2.height=hln_1.begin
x=thickln_1.begin
y=thickln_1.end
x=w - 2*thickl n_1.end
y=thickln_2.begin
x=thickln_2.begin
y=thickln_2.end
x=thickln_2.end
y=h - 2*thick
  窗口Progress的Script：
long bet
bet=(max - min)/10
if min>max then
return
end if
if pos=max then
r_2.width=r_1.width
b=1return
end if
if pos>=min and pos<max
then
if pos>=b*bet then
r_2.resize(r_1.width*(abs((pos - min)/(max - min))),r_2.height)
b=b+1
end if
end if
以上便完成了用户对象，以后只需调用即可，比如：
int iuo_1.min=0
uo_1.max=10000
for i=1 to 10000
uo_1.pos=iuo_1.triggerevent("progress")
next
其中，uo_1为进度条用户对象。


 如何在PB中调用RASpanya编辑整理

首先建立类用户对象uo_ras。
定义实例变量
ulong il_RASCON = 0
声明结构变量：
St_rasdialparms
dwsize | Unsignedlong
entryname[257] | Character
phonenumber[129] | Character
callbacknumber[129] | Character
username[257] | Character
password[257] | Character
domain[16] | Character
St_rasstatus
Usize | Unsignedlong
rasconnstatus | Integer
uerror | Unsignedlong
f1[33] | Character
f2[17] | Character

声明本地外部函数
Function ULONG RasDialA( string dialextentsion, string phonebook, ref st_rasdialparms rdp, ulong notifier, int hwnd, ref ulong hConn) LIBRARY "RASAPI32.DLL"
Function ulong RasGetConnectStatusA( ulong hConn, ref st_rasstatus rs ) LIBRARY "RASAPI32.DLL"
Function ulong RasHangUpA( ulong hConn ) Library "RASAPI32.DLL"
Function UINT RegisterWindowMessageA( string lpsz ) LIBRARY "USER32.DLL"
定义如下局部函数
Of_dial(string os_entry, string os_user, string os_password, integer oi_hwnd) returns integer//通过RAS拨号函数
st_rasdialparms lst_RDP
string ls_Null
long ll_Ret
st_rasstatus lst_RCSinteger
RASCS_DONE = 8192
integer li_OK
constant ulong ll_Notifier = 4294967295
SetNull( ls_Null )
lst_RDP.dwSize = 1052
l st_RDP.EntryName = os_Entry
l st_RDP.PhoneNumber[1] = Char(0)
lst_RDP.CallbackNumber[1] = Char(0)
lst_RDP.UserName = os_User
lst_RDP.Password = os_Passwordlst_RDP.Domain = '*'
il_RasCon = 0
ll_Ret = RasDialA( ls_Null, ls_Null, lst_RDP, ll_Notifier, oi_hwnd, il_RasCon )
IF ll_Ret = 0 THEN
lst_RCS.uSize = 60DO
li_OK = RASGetConnectStatusA( il_Rascon, lst_RCS )
Yield()
LOOP WHILE lst_RCS.uError = 0 AND lst_RCS.RASConnstatus <> RASCS_DONE
END IF
RETURN ll_RetOf_getmessage()
returns unsignedlongconstant ulong WM_RASDIALEVENT = 52429
uint li_MsgNo
li_MsgNo = RegisterWindowMessageA( "RasDialEvent" )
IF li_MsgNo = 0 THEN
li_MsgNo = WM_RASDIALEVENT
END IF
RETURN li_MsgNoOf_hangup()
RasHangupA( il_RasCON )
Of_getstatus() returns integer//返回RAS的连接信息
st_rasstatus lst_status
ulong ll_Retlst_status.uSize = 60ll_Ret = RasGetConnectStatusA( il_rascon, lst_status )
IF lstr_status.uError <> 0 THEN
RETURN lst_status.uError
ELSER
ETURN lst_status.rasconnstatusEND
IFOf_statustext(integer oi_status)
returns string//将RAS状态码转换成文字
string ls_Message
CHOOSE CASE oi_status
CASE 0ls_Message = "打开端口"
CASE 1ls_Message = "端口已打开"
CASE 2ls_Message = "正在连接…"
CASE 3ls_Message = "设备已连接"
CASE 4ls_Message = "所有设备已连接"
CASE 5ls_Message = "正在验证"
CASE 6ls_Message = "Auth. Notify"
CASE 7ls_Message = "Auth. Retry"
CASE 8ls_Message = "Auth. Callback"
CASE 9ls_Message = "Auth. Change Password"
CASE 10ls_Message = "Auth. Project"
CASE 11ls_Message = "Auth. Link Speed"
CASE 12ls_Message = "Auth. Ack"
CASE 13ls_Message = "重验证"
CASE 14ls_Message = "通过验证"
CASE 15ls_Message = "准备回拨"
CASE 16ls_Message = "MODEM初始化"
CASE 17ls_Message = "等待回拨"
CASE 8192ls_Message = "已连接!"
END CHOOSE
RETURN( ls_Message )
Of_errortext(integer oi_errornumber) returns string//显示错误号
string ls_Errorls_Error = "错误号是：" + String( oi_errornumber ) +" （请查找出错原因）"
RETURN ls_Error
以上便完成了用户对象的建立。接下来，我们只需要在窗口中调用用户对象函数即可完成对RAS的控制。在窗口中定义变量：uo_ras iuo_ras写下如下script：
iuo_ras=create uo_ras
定义按扭cb_dial的script:
integer li_ret
li_ret=iuo_ras. Of_dial(sle_entry.text, sle_username.text, sle_password, handle(parent))
即可用RAS项拨号。其他函数的调用在这就不细说了，如果需要示例程序，请与panya@163.net联系。


在PB程序中实现ODBC数据源的添加

作者：鹿寒

思路：

主要是利用PB对注册表操作函数RegistryGet()和RegistrySet()，这两个函数以及其他注册表函数在PB的帮助里都有详细说明。

再就是利用PB的ProfileString()函数读取INI文件中预先定义好的参数。

写注册表的源程序：

string ls_sys[],ls_driver[],st_driver,st_path

integer li_rtn

ls_me="新星科技股份有限公司（New Star, Inc.）"

ls_version="2.0"

ls_sys[1]="HKEY_LOCAL_MACHINE\Software\ODBC\ODBCINST.INI\Sybase SQL Anywhere 5.0"

ls_sys[2]="HKEY_CURRENT_USER\Software\"+ls_me+"\仓储管理系统\"+ls_version

ls_sys[3]="HKEY_CURRENT_USER\Software\ODBC\ODBC.INI\ODBC Data Sources"

ls_sys[4]="HKEY_CURRENT_USER\Software\ODBC\ODBC.INI"

li_rtn = RegistryGet(ls_sys[1], "Driver", RegString!, st_driver)

IF li_rtn = -1 THEN

messagebox("错误","未安装Sybase SQL Anywhere 5.0 ！",information!,ok!)

RETURN

END IF

li_rtn = RegistryGet(ls_sys[2], "InstPath", RegString!, st_path)

IF li_rtn = -1 THEN

messagebox("错误","读取安装路径出错 ！",information!,ok!)

RETURN

END IF

string envfile,ls_source[],ls_drivepath,ls_temp

integer i

envfile="regedit.ini"

ls_source[1]="cl"

ls_source[2] =ProfileString(envfile,"INI","AutoStop"," ")

ls_source[3] =ProfileString(envfile,"INI","DatabaseFile"," ")

ls_source[4] =ProfileString(envfile,"INI","DatabaseName"," ")

ls_source[5] =ProfileString(envfile,"INI","Description"," ")

ls_source[6] =ProfileString(envfile,"INI","Driver"," ")

ls_source[7] =ProfileString(envfile,"INI","PWD"," ")

ls_source[8] =ProfileString(envfile,"INI","Start"," ")

ls_source[9] =ProfileString(envfile,"INI","UID"," ")

li_rtn=RegistrySet(ls_sys[3], ls_source[1] , RegString!, "Sybase SQL Anywhere 5.0")

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错 ！",information!,ok!)

RETURN

END IF

ls_sys[4]=ls_sys[4] + "\" + ls_source[1]

li_rtn=RegistrySet(ls_sys[4], "AutoStop" , RegString!, ls_source[2])

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错 ！",information!,ok!)

RETURN

END IF

li_rtn=RegistrySet(ls_sys[4], "DatabaseFile" , RegString!, st_path+"\"+ls_source[3])

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错 ！",information!,ok!)

RETURN

END IF

li_rtn=RegistrySet(ls_sys[4], "DatabaseName" , RegString!, ls_source[4])

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错 ！",information!,ok!)

RETURN

END IF

li_rtn=RegistrySet(ls_sys[4], "Description" , RegString!, ls_source[5])

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错?！",information!,ok!)

RETURN

END IF

li_rtn=RegistrySet(ls_sys[4], "Driver" , RegString!, st_driver)

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错 ！",information!,ok!)

RETURN

END IF

li_rtn=RegistrySet(ls_sys[4], "PWD" , RegString!, ls_source[7])

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错 ！",information!,ok!)

RETURN

END IF

ls_temp=st_driver

i=pos(ls_temp,"\")

do while i>0

ls_drivepath=ls_drivepath+left(ls_temp,i)

ls_temp=right(ls_temp,len(ls_temp)-i)

i=pos(ls_temp,"\")

loop

li_rtn=RegistrySet(ls_sys[4], "Start" , RegString!, ls_drivepath+ls_source[8])

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错 ！",information!,ok!)

RETURN

END IF

li_rtn=RegistrySet(ls_sys[4], "UID" , RegString!, ls_source[9])

IF li_rtn = -1 THEN

messagebox("错误","读取系统文件或写注册表出错 ！",information!,ok!)

RETURN

END IF

regedit.ini文件格式如下：

[INI]

AutoStop=yes

DatabaseFile=Psdemodb.db

DatabaseName=仓储管理系统

Description=cl

Driver=

PWD=sql

Start=dbeng50.exe

UID=dba

备注：

程序中ls_sys[2]变量代表的注册表键值是我通过安装程序制作工具自动生成的，每个人使用的制作安装程序的工具不同，这个键值也许不尽相同。其实用意只是在于取得st_path这个变量，它保存应用程序安装后所在路径，其实也可以用API函数得到，至于使用API函数怎么取得当前程序所在路径就不讲了。


在PB中动态调用函数

编辑整理 PB能量建造者

关键：Dynamic关键字

目前流行的大部分应用程序中都提供了Undo功能，在PowerBuilder中也可以利用Undo()函数实现该功能。Undo()函数可用于DataWindow, EditMask, MultiLineEdit, RichTextEdit和SingleLineEdit 对象，如果只对某一个对象进行Undo操作，只需在Undo菜单项的单击事件中键入如下脚本：Objectname.undo()，但是当窗口中有多个对象，我们在编写脚本时并不知道要对哪个对象执行undo()操作，如何解决这一问题呢？在PowerBuilder中，undo()等函数只能用于可视对象，而所有可视对象均继承自系统对象类GraphicObject。因此我们可以定义一个GraphicObject对象的实例变量go_object，等到运行时再用getfocus()函数确定具体操作对象。然后用Typeof()函数确定当前对象的类型，再用Choose case语句根据不同的类型引用不同的实例变量，代码如下：
graphicobject go_object
DataWindow dw_object
EditMask em_object
MultiLineEdit mle_object
RichTextEdit rte_object
SingleLineEdit sle_object

go_object=getfocus()
choose case TypeOf(go_object)
case DataWindow!
dw_object=go_object
dw_object.undo()
case EditMask!
em_object=go_object
em_object.undo()
case MultiLineEdit!
mle_object=go_object
mle_object.undo()
case RichTextEdit!
rte_object=go_object
rte_object.undo()
case SingleLineEdit!
sle_object=go_object
sle_object.undo()
case else
messagebox("Error","Can not undo")
end choose

其实我们可以用动态调用函数的方法简单地解决这一问题(只需三行代码)，即对GraphicObject对象调用
undo()函数，然后在函数名前加上关键字Dynamic。因为对象类GraphicObject并没有undo()这个对象函数，如果不加关键字Dynamic，编译时就会出现错误。使用了Dynamic关键字，PowerBuilder在编译时不检查该函数和所用参数的有效性，而到脚本运行时才去检查该函数。代码如下：
GraphicObject go_object
go_object=getfocus()
go_object.dynamic undo()

允许动态调用函数是的PowerBuilder5.0的特性之一。在程序设计中灵活运用动态函数调用的方法有助于提高所设计程序的可维护性、可重用性。


 PowerBuilder中数据窗口的精确页面打印技 术

中国计算机世界

国永杰 张颖

----摘要本文详细介绍了PowerBuilder中数据窗口打印输出技术，并给出了一个通用的数据窗口打印控制程序。

----关键词PowerBuilderDataWindowPrint

1.引言

----自PowerBuild4.0问世以来，以其简洁高效的集成开发环境、强大的数据窗口技术、（几乎）无所不能的数据库访问能力，越来越受到编程人员的青睐。据Sybase自己 的统计， 目前全 球有60万 程序员 和系统 分析人 员在使 用他们 的Powerbuilder和S-designer,进行各种类型的应用程序开发。最新发布的PowerBuilder6.0族系列开发工具，直接支持ActiveX技术、ServerPush技术，对分布式计算模型的定义更加明确，实现更为简便，并可以利用数据窗口直接生成HTML格式 文件，等 等。所以，PowerBuilder 已成为C/S模式应用和基于InterNet 模式应 用的主 要开发 工具之 一。

----数据窗口(DataWindow)是PowerBuilder的专利，也是PowerBuilder应用程序的核心和精华所在.PowerBuilder通过丰富的属性和函数来控制数据窗口的显示和数据操纵，但对数据窗口的打印支持似乎过于简单，只有一个Print() 窗口函 数和一 个PrintDataWindowPowerScript函 数，并且 这两个 函数的 功能几 乎都是 一样，都 只简单 地将选 定数据 窗口原 样送入 打印队 列，本身 不具备 页面控 制功能。 为此很 多程序 员抱怨 这是PowerBuilder的一个缺陷，其实，PowerBuilder对打印功能的支持一样十分周到全面，只不过应用场合不同，需要用户自行加以选择。

----以下是对PowerBuilder打印控制功能的详尽分析，并介绍一个实用的数据窗口页面控制程序。

2.Powerbuilder实现动态页面控制的途径

----PowerBuilder数据窗口有一对函数Describe 和Modify,用于动态描述和修改数据窗口结构，与其它相关函数结合可以完成动态生成数据窗口、任意改变数据窗口显示方式、修改打印属性等。这两个函数是控制与修改数据窗口的核心函数，其使用方法如下：

----1)Describe获取数据窗口特定属性值

----语法：dwcontrol.Describe(propertylist)

----参数：dwcontrol数据窗口（或datastore）控 件名。

----propertylist用字符串表示的数据窗口属性。

----返回值：返回一个表示属性的字符串。

----数据窗口属性值也可以用dwcontrol.object.Datawindow.property来完成直接取得。

----2)Modify修改数据窗口特定属性

----语法：dwcontrol.Modify(modstring)

----参数：dwcontroldwcontrol数据窗口（或datastore）控 件名。

----modstring用字符串表示的数据窗口属性。

----返回值：如果函数执行成功返回“1”。

----与Describe类似， 修改数 据窗口 属性可 以用：

----dwcontrol.object.Datawindow.property=value直 接赋值。

----3)与打 印有关 的数据 窗口属 性

----数据窗口属性中与打印有关的有24个，其中实现打印页面控制的几个属性是：

----Copies:打印份数。

----Filename:如果不为空，表示输出到文件的文件名。

----Orientation:打印方向1=横向，2=纵 向

----Page.Range:输出页范围，要打印1,2,和5到10 页,输入: "1,2,5-10"。Page.RangeInclude:本 次打印范围，0=全部，1= 偶数页，2= 奇数页。Paper.Size：纸张大小。

----Scale：打 印比例。

----程序中利用Modify函数 设置好 这些属 性后，即 可实现 动态修 改数据 窗口的 目的，再 调用datawindow.print()函数，完成最后的打印输出。

一个通 用Datawindow打印控制程序

----将数据窗口中的报表在打印机上输出是PowerBuilder应用程序开发过程中必不可少的功能，作为例子，这里提供一个使用上述方法研制的通用打印控制窗口，用以替代Prompt属性 所提供 的Print窗口，实现汉化界面，并且可被自行修改扩充。为了书写简单，程序中采用直接赋值的方式修改属性。

实例 变量：

datawindowdw_print

stringprintername,name,port

窗口open 事件脚 本：

dw_print=Message.PowerObjectParm

printername=dw_print.Object.Datawindow.Printer

name=left(printername,pos(printername,"on")-1)

port=right(printername,len(printername)-pos(printername,"on")-1)

plb_printername.additem(name,1)

st_portname.text=port

“设置” 按钮clicked事件脚本

Integercode

Code=printSetup()

Ifcode=1then

printername=dw_print.Object.Datawindow.Printer

name=left(printername,pos(printername,"on")-1)

port=right(printername,len(printername)-pos(printername,"on")-1)

plb_printername.additem(name,1)

st_portname.text=port

endif

打印 范围组 中“全部” 选择钮clicked事件 脚本

dw_print.object.DataWindow.Print.Range=''

sle_1.enabled=false

sle_2.enabled=false

sle_3.enabled=false

“页数” 栏后sle_2单行文本编辑框Modified事件 脚本

dw_print.object.DataWindow.Print.Page.Range=sle_1.text +"-"+sle_2.text

“自定 义”栏后sle_3单行文 本编辑 框Modified事件脚本

dw_print.object.DataWindow.Print.Page.Range=sle_3.text

打印 份数组 中份数 单行文 本编辑 框Modified事件脚本

dw_print.Object.DataWindow.Print.Copies=integer(this.text)

“全部” 选择钮Cliecked事件 脚本

dw_print.Object.DataWindow.Print.Page.Rangeinclude =0

“奇数” 选择钮Cliecked事件 脚本

dw_print.Object.DataWindow.Print.Page.Rangeinclude =1

“偶数” 选择钮Cliecked事件 脚本

dw_print.Object.DataWindow.Print.Page.Rangeinclude =2

“确认” 命令按 钮Clicked事件脚本

longjob

ifIsValid(dw_print)then

job=PrintOpen()

PrintDataWindow(job,dw_print)

PrintClose(job)

close(parent)

endif

“取消” 命令按 钮Clicked事件脚本

close(parent)

----窗口调用方法:在其它带有数 据窗口的窗口中，使用OpenWithParm(w_print_set,dwname) 即可，其 中w_print_set为本窗口名，dwname为要 打印的数 据窗口。 该程序 是一个 通用打 印控制 程序，可 在任何 版本的PowerBuilder 应用中使用。


结构体与blob数据的转换

编辑 PB能量建造者

要用API，如下：

设为结构 str_exam lstr_exam, blob lbl_exam

Function ulong MemoryCopy(ref str_exam source, REF blob destination, u long length) library "kernel32.dll" Alias for "RtlMoveMemory"

Function ulong MemoryCopy(REF blob destination,ref str_exam source, ul ong length) library "kernel32.dll" Alias for "RtlMoveMemory"

设str_exam长度为 li_lenexam

将lstr_exam copy 到 lbl_exam

lbl_exam = blob ( space ( li_lenexam ) )

MemoryCopy ( lstr_exam, lbl_exam, li_lenexam )

将lbl_exam copy 到 lsr_exam

MemoryCopy ( lbl_exam, lstr_exam, li_lenexam )

如只 从结构copy到blob,还可用blobedit，更简单一些。记住这个API文档上找不到的RtlMoveMemory，非常非常有用。它可用来取pb变量的指针。

Banner from WebUnion Chinese Network


PB调用外部程序及判断其完成的方法 Panya整理

关键：API函数FindWindowA和IsWindow

在PB中常常需要运行一些外部的程序或命令，并等待其执行完成后，才接下来运行剩余的代码。我们可以有两种方法：

先定义全局外部函数：

Function long ShellExecuteA (long hwnd, string lpOperation ,String lpFile, String lpParameters, String lpDirectory, Long nShowCmd) Library "shell32.dll"

Function long FindWindowA (String lpClassName , String lpWindowName ) Library "user32.dll"

Function boolean IsWindow (Long hwnd ) Library "user32.dll"

第一种方式用Run() 函数，可在窗口上建立按扭，clicked事件中包含如下Script:

ulong ll_handle

int li_loop

SetPointer(HourGlass!)

//最小化执行xxx.bat

run("xxx.bat", Minimized!)

//循环到窗口打开，根据程序执行打开所需的时间设定li_loop的循环次数，可预留长一些。

for li_loop= 1 to 10000

ll_handle = FindWindowA("tty","xxx")

yield() //函数作用详见“ PB技巧”中《Pb中Yield()函数的使用》

if ll_handle <> 0 then

exit

end if

next

//一直循环到窗口关闭

Do While isWindow(ll_handle)

Yield()

Loop

//应用执行完成

messagebox(‘ok’, ‘执行完成!’)

这种方法的缺点是不能隐藏外部应用程序窗口，只能最小化。

第二种方式用API函数，可以隐藏应用程序的窗口，但是调用bat批处理命令时需要先建立一个PIF文件指定执行完成后关闭窗口，否则窗口不会自行关闭。可在窗口上建立按扭，clicked事件中包含如下Script:

uint lu_return

ulong ll_handle

int li_loop

string ls_Path

SetPointer(HourGlass!)

lu_return = ShellExecutea(handle(parent), "open", "xxx.pif", "", ls_path, 0)

//最后一个参数改为 4，可以显示执行情况

if lu_return > 32 then

for li_loop= 1 to 10000

ll_handle = FindWindowA("tty","xxx")

yield()

if ll_handle <> 0 then

exit

end if

next

//一直循环到窗口关闭

Do While isWindow(lu_handle)

Yield()

Loop

//应用执行完成

MessageBox("ok", "执行完成!")

Else

//error

messagebox("错误", "调用外部应用程序不成功，请检查应用程序路径！")

end if


PB中打印预览的实现

北京燕化集团化工二厂动力处 刘荣芳

---- 作为目前比较流行的数据库前端开发工具，PowerBuilder显著的一个特点就是其数据窗口(DataWindow)技术，利用此技术可生成各种复杂的报表，但在打印输出时为了打印出中国式特殊格式要求的报表时，需要编程实现，打印效果需要反复调整并打印输出，既效率低下，又浪费资源，如何让用户在打印报表前能预览报表打印效果呢 ？

---- 1、 在原窗口内实现预览

---- 最简单并且最常用的方法是在窗口内添加命令按钮“预览”，在其clicked事件中写入如下脚本：

if this.text="预览" then
dw_1.object.datawindow.print.preview = "yes"
this.text="取消"
else
this.text="预览"
dw_1.object.datawindow.print.preview = "no"
end if

---- 该方法适用于打印报表的格式与数据窗口的显示格式相同。

---- 2、 在预览窗口w_preview中实现预览

---- 在PB5.0带的例子Code Examples中有一窗口w_preview,用它可完成数据窗口的预览，它的技术要点是：将数据窗口的语法作为参数传递给w_preview，再在w_preview窗口中用dw.create()函数生成此数据窗口，其中数据用describe()函数取得，再用imporstring()函数转入。依据中国人的习惯，将w_preview内英文提示全部改为中文。采用此窗口进行预览的好处是：预览的比例可随意调整，便于观察整体效果。在Code Examples中调用w_preview的过程非常复杂，它先调用一个自定义函数，然后在函数中调用w_preview，下面介绍一种比较简单的调用方法并采用另一种获取预览数据的方法。

---- ? 在命令按钮“预览”的clicked事件中写入如下脚本：(原脚本全部删除)

string ls_dwname//定义变量，
用于存放数据窗口控件名
if isvalid(w_preview)
then close(w_preview)
ls_dwname = dw_1.dataobject//取数据窗口控件名
//拷贝数据窗口内的数据到剪贴板，
然后打开预览窗口w_preview
dw_1.saveas("",clipboard!,false)
openwithparm(w_preview,ls_dwname)

? 在w_preview的open事件中写入脚本如下：
//说明：用于单数据窗口的打印预览
//调用规则：open(w_preview,
)
//参数stringparm为字符型变量，
存放数据窗口控制名
//返回值： 无
//****************************
***********************************
//例如：
// string ls_dwname
// ls_dwname = dw_1.dataobject
// dw_1.saveas("",clipboad!,false)
// openwithparm(w_preview,ls_dwname)
//**********************************
*****************************
//获得数据窗口控件名
string ls_dwname
ls_dwname = message.stringparm
dw_1.dataobject = ls_dwname
dw_1.settransobject(sqlca)
dw_1.importclipboard() //导入预览数据
dw_1.object.datawindow.print.preview
= "yes"//置预览方式为true

---- 这种方法比较简单，充分利用了PB5.0现有资源，而且容易理解。但如果打印是通过复合式窗口（composite dw）来实现的，其打印效果的预览采用以上方法就不行，数据无法传递，只能采用最基本的方法实现，平时复合式窗口是不可见的，预览时将其可见即可，脚本如下：(dw_7为复合式数据窗口)

if this.text="预览" then
SetPointer(Hourglass!)
dw_7.object.datawindow.print.preview = "yes"
dw_7.visible = true
this.text="取消"
else
this.text="预览"
dw_7.visible = false
end if

---- 以上方法在P200兼容机、Windows98平台上、PowerBuilder5.0上实现，完全可行。


 PB内置OEACLE数据库接口的使用方法

马长明
    PowerBuilder(PB)和Oracle分别是前端开发工具和RD
BMS的主流产品。PB提供了两种与Oracle连接的接口：Pow
erSoft内置的数据库接口(Native Database Interface)
和ODBC接口。本文介绍使用PB60内置Oracle接口的方法
，包括数据描述文件的设置、存储过程的调用和存储过程
作为数据窗口数据源的操作方法等内容，使用的RDBMS的Or
acle 73。

    PowerBuilder与Oracle的连接

    假定已安装Oracle客户端应用程序。可用Sqlplus或T
nsping等是否能连接到Oracle数据库，确定在SQLNET配
置文件中使用的数据库别名(Database Alias，又称服务
器名，Server Name)。如有疑问，可在Oracle客户端程序
目录下tnsname.ora文件中找到。另外保证搜索路径已包括
SQLNET应用程序的安装目录(如C:\ORAWIN95\BIN)。

    进入PB的Database Profiles画笔，可看到所有已安
装的数据库接口(PB60缺省安装的是Oracle 73版的接
口，如使用低版本的Oracle，需在安装时指定)，选择“O7
3 Oracle 73”，点击“New”按钮，进入Database Pro
file Setup对话框。在“Connection”页输入下列信息：

    Profile Name：为该Database Profile起一个有意义
的名称；

    Server:@TNS:ServerName，其中ServerName为上述数
据库别名(服务器名)，如@TNS:ORA73；

    Login ID：Oracle数据库中实际的用户名，由于PB初
次连接到数据库时要自动建立五个系统表(PowerBuilder
Catalog Table:PBCATTBL,PBCATCOL,PBCATEDT,PBCATFMT,
PBCATVLD,存储表的扩展属性)，因此第一个连接到Oracle
的用户必须具有建表、给PUBLIC角色授权等权限。例如可
用SYSTEM用户进行第一次连接，此后的连接对用户则无特
殊要求；

    Password：该用户的口令。

    设置上述内容后，即可连上Oracle。为优化数据库连
接，还可设置下列选项：

    Prompt for Database Information：连接时是否提
示用户输入用户名和口令；

    Generate Trace：启动DB跟踪工具，跟踪连接；

    Thread Safe：开发需要多线程环境支持的分布式应
用时，选择该项。缺省为未选，适用于非分布应用；

    PBDBMS：与存储过程调用方式有关的参数。Oracle为
72或更高版本时，清除该选项，此时带IN OUT参数的存
储过程可作为数据窗口数据源。72版本以下，选择该项
，调用PBDBMS.Put－Line建立存储过程的SQL语句，缺省
是选中；

    Commit on Disconnect：断开连接时，指定提交或回
退未提交的事务；

    Case Sensitive：连接的Oracle服务器是否区分大小
写。注意该项选中时，所有主键、包含主键的表名、外键
须全为大写字符。

    PowerBuilder Catalog Tables Owner：指定拥有PB
系统表的用户，缺省为“SYSTEM”。如果要使用多种显示
格式或有效性规则，可以在不同的用户下建立几套系统表
；

    Table Criteria：指定满足哪些条件的表、视图和同
义词可在“Select Tables”对话框中显示出来。例如DEV
用户下销售子系统的表都以SALE开头，则可以用SALE％、
DEV、“TABLE”、“VIEW”指定只显示DEV用户以SALE开
头的表和视图；

    Asynchronous：选择该项，可在一个复杂的SQL语句
执行过程中，返回第一行结果前，切换到其他操作；

    Number of Seconds to Wait：若上一项选中，还可
进一步通过该项指定检索数据时，等待数据库响应的时间
；

    Retrieve Blocking Factor：决定数据窗口对象一次
可从数据库取出的记录数；

    Number of SQL Staments Cached：PB可将数据窗口
对象产生的SQL语句和嵌入式SQL语句保存在SQL语句缓冲区
，该参数指定缓冲区为PB保留的SQL语句数目。该数值可
由下式计算：SQLCache=服务器OPEN—CURSORS数－5(保留
的游标数)－本连接预期使用的最大游标数；

    Disable Bind：指定是否将输入变量与SQL语句绑定
，此参数影响PB为数据窗口对象生成INSERT语句的方式；

    Static Bind：数据窗口对象检索数据前是否检测SEL
ECT语句的合法性；

    在Syntax页，还可指定日期、时间的格式等。在Prev
iew页可查看Database Profile设置对应的PowerScript写
法。

    Oracle存储过程的使用

    归纳起来PB60中调用Oracle存储过程有以下四种方
法。

    方法一：以关键字RPCFUNC声明存储过程；

    方法二：以DECLARE PROCEDURE语句声明存储过程；

    方法三：以动态SQL语句实现；

    方法四：调用函数PBDBMS.Put－Line。

    一般情况下，方法一能得到较好的运行效率和更完善
的功能。因此推荐使用方法一。但是某些类型的存储过程
只能使用其他方法。以下重点介绍方法一和方法二。

    两种方法比较起来，有以下几点主要区别：

    1)方法一适用于具有IN、OUT和IN OUT参数的存储过
程，而方法二仅支持IN和OUT参数，不支持IN OUT参数；

    2)方法一对参数的数目无限制，方法二最多支持255
个参数；

    3)方法一不支持记录(Recorder)的传递，方法二可传
递和接收记录。

    方法一操作步骤：

    1)在用户对象画笔中创建一个Class－Standard类型
的，从Transaction继承的用户对象。

    2)在此用户对象中，声明Local External Functions
，将存储过程以RPCFUNC关键字声明为外部函数。在Decla
re Local External Functions对话框中按Procedures按
钮选择要调用的后台存储过程，或直接输入类似subroutin
e TEST(long id,ref string name)RPCFUNC ALIAS FOR
“DEVTEST”的语句。例如中DEVTEST的参数为(id IN
NUMBER,name IN OUT VARCHAR2)。

    3)保存该用户对象。

    4)在Application画笔中，进入应用属性对话框，在V
ariable Types页，将上Transaction用户对象设置为缺省
的全局变量SQLCA。

    5)脚本中，编码调用相应的外部函数。形式：SQLCA
函数名(参数)。如可用“SQLCATEST(ln—id,ls—name
);”调用例子中定义的DEVTEST存储过程。其中ln—id,
ls—name为调用参数。

    方法一注意事项：

    1)由于PB中String类型的数据长度是动态分配的，因
此对Ref String类型的参数，每次调用前需要申请足够的
空间。例如上例从输入的id值检索name，后台声明的NAME
数据类型为VARCHAR2(30)，每次调用SQLCATEST前需要
用ls—name=SPACE(30)置ls—name为足够长度的空串。

    2)类似地当传递的参数为数组(后台称PL/SQL Table)
时，声明参数为相应类型的动态数组，并在每次调用前设
置数组上界为足够大的值。例如：DEV用户的DEPT包中声
明了TYPE tbl—name IS TABLE OF VARCHAR2(30) INDEX B
Y BINARY—INTEGER；存储过程EMP—OF—DEPT(dept—id
IN NUMBER,name OUT DEPT.tbl—name)返回部门编写为de
pt—id的部门的所有员工，置name数组中。假定所有部门
的员工数目不超过100，则可用以下语句声明和调用DEV
EMP—OF—DEPT：

    Declare External Functions对话框中：

    Subroutine EMP—OF—DEPT(long dept—id,ref str
ing name[]) RPCFUNC ALIAS FOR ″DEV.EMP—OF—DEPT
″

    PowerScript脚本中：

    long ln=dept—id

    string lsa—name[]

    ……

    lsa—name[100]=″ //置动态数组lsa—name上界为
100

    ln—dept—id=2

    SQLCA.EMP—OF—DEPT(ln—dept—id,lsa—name) /
/检索部门2的所有员工的名字

    方法二使用方法：

    在脚本中加入如下编码(以DEVTEST为例)：

    long ln—id

    string ls—name

    ln—id=100

    DECLARE test PROCEDURE FOR DEV.TEST(:ln—id);
//test有一个IN参数，一个OUT参数

    EXECUTE test;

    FETCH test INTO:ls—name;

    方法三使用方法：

    根据输入输出参数的不同，PB中的动态SQL语句有四
种格式：1)无输入输出参数；2)只有输入参数，无输出参
数；3)有确定数目输入输出参数；4)输出结果列的数目不
定。其中第1)、第2)中格式的动态SQL语句也可用来调用简
单的存储过程。

    1)没有输入输出参数的存储过程。例如DEVDEMO1存
储过程没有输入输出参数，可用下列语句调用：

    sqlstring=″EXECUTE DEMO1()″

    EXECUTE IMMEDIATE:sqlstring;

    2)只有输入参数的存储过程。例如DEV.DEMO2(name I
N VARCHAR2,birthday IN DATE)

    PREPARE SQLSA FROM ″EXECUTE DEMO2(?，?);″

    EXECUTE SQLSA USING:ls—name,:ldt—birthday;

    如果是72以下版本的Oracle，可用方法四PBDMBS.P
ut—Line()函数调用建立存储过程。此时需选上Database
Profile中的PBDBMS复选框。

    存储过程作为数据窗口的数据源

    以一个简单数据窗口(显示服务器的系统时间)为例说
明怎样创建存储过程作为数据源的数据窗口。

    1)在某个包(如PACK—TYPE)中定义日期类型的Record
er自定义数据类型datarec；

    2)创建返回系统时间的存储过程：

    PROCEDURE get—sysdata(sysdt IN OUT PACK－TYPE
.datarec)IS

    BEGIN

    open sysdt for select sysdate from dual;

    END;

    3)新建数据窗口，数据源选择Stored Procedure；

    4)在随后的Select Stored Procedure对话框中选择g
et—sysdate即可。

    如果作为数据源的存储过程有IN参数，则这些参数就
是数据窗口的检索参数(Retrieve Argument)。


PowerBuilder下实现多用户安全访问

张锟

    PowerBuilder（以下简称PB）是一个非常优秀的数据库前端开发工具，在C/S结构中它处于客户端。在PB中，所有关于数据库方面的操作都交给DBMS完成，在一般情况下不用考虑多个用户同时访问数据库的问题。 　　但DBMS提供的多用户支持仅仅限于SQL语句级，即保证两条SQL语句不会同时对一条记录进行操作。
    比如，有SQL语句A和B：
     A：Update target
     Set col1=col1＋1
     Where keycol='key';
    B: Update target
     Set col1=col1＋2
     Where keycol='key';
    其中，keycol是表target的关键字，col1是待修改的字段。
    显然，A语句和B语句都是可以再细分的操作，具体可以细分如下：
    A1: 查找keycol值为'key'的记录
    A2: 读取col1字段的值，并计算结果
    A3: 将结果写回数据库
    B1－B3同A1－A3一样，只是B2在具体计算上有所不同。
    如果DBMS无法保证A语句和B语句的互斥，就有可能发生写丢失。比如col1的值为2，如果执行次序为（A1，A2，A3，B1，B2，B3）或（B1，B2，B3，A1，A2，A3），即A、B语句互斥，结果应为5。但如果执行次序为(A1,A2,B1,B2,B3,A3),结果col1的值就成了3，B3进行的写操作就丢失了。所以DBMS在设计时，充分考虑了SQL语句级的并发性。 　　但是,如果上面的A和B不是单条SQL语句，而是SQL语句序列，那DBMS就无法保证并发性的安全了。尤其是考虑到PB可以在脚本（Script）中混排SQL语句和PB语句，这种安全性就更加无法保障。 　　比如语句序列C是由SQL语句和PB语句混排而成，同时有多个实例运行，让我们看一看在这种情况下，如何保证并发的安全性。为了方便起见，在例子中的PB语句是用伪语言写的。 　　语句序列C 为：
    C1：select col1 into :value
     from target where keycol='key'; //将col1的值赋给变量value
    C2：chg=value>100 //chg为逻辑变量
    C3：if chg
     A //执行SQL语句A
     else
     B //执行SQL语句B
     end if
    其中，A、B指的是前面的SQL语句A和B。
    如果两台计算机上同时运行序列C，就有可能发生写丢失。因此，语句序列一级的并发安全性必须由程序来保证。 　　上述问题如何解决呢?在写记录时，检查该记录是否被修改过，如果被修改过，就放弃写操作。
    以上面的语句序列C为例，只要在C1执行完毕后，将读到的记录值存下来，等到C3执行的时候，在A和B中加入相应的where条件，比较现在的记录值是否与原来一致，就能够判断出在C1和C3之间该记录是否被修改过。 　　假设表target只有keycol和col1两列，那么只用修改A和B，就能将序列C改为具有并发安全性的序列。
    A和B的修改如下：
    A：Update target
     Set col1=col1＋1
     Where keycol='key'
     And col1=:value;
    B: Update target
     Set col1=col1＋2
     Where keycol='key'
     And col1=:value;
    其中,变量value是前面在C1运行的时候中记录下来的。
    如果表中还有其他序列，就要修改语句C1，将其他序列同时记录下来，并在A和B的条件中加入相应的判断条件。 　　在使用这种办法的时候要注意，应该用一条Select语句来记录表中的记录值，而不要分散到多条语句中去。同样，在写记录时，应该在Update语句中加入判断条件，而不要先判断，再写入。否则，仍然会存在并发性问题。


在MicroHelp Bar上显示时间

下面示例了在MDI的Microhelp棒的右边显示时间。它利用一个包含有一个单行
编辑控件sle_time的Pop-Up类型窗口w_popup，用SLE显示时间。

//定义MDI Frame的实例变量
INTEGER OX,OY,OH,OW // OLD X,Y AND HEIGHT, WIDTH
W_POPUP LPOP // LOCAL POPUP WHICH TRAIPES ...

//w_MDI_frame的Open事件
integer wx,wy,wh,ww // workspace extents
ox=this.x
oy=this.y
oh=this.height
ow=this.width

wx=this.workspacex ( )
wy=this.workspacey ( )
wh=this.workspaceheight ( )
ww=this.workspacewidth ( )

open(lpop)
move(lpop,wx+ww - lpop.width - 15,wy+wh - lpop.height - 22)
show(lpop)

//w_MDI_frame的Resize事件
integer wx,wy,wh,ww

wx=this.workspacex ( )
wy=this.workspacey ( )
wh=this.workspaceheight ( )
ww=this.workspacewidth ( )

if ( handle (lpop) > 0 ) then
move(lpop,wx+ww - lpop.width - 15,wy+wh - lpop.height - 22)
end if

SetFocus(this)

//w_MDI_frame的用户事件pbm_move
integer wx,wy,wh,ww

wx=this.workspacex ( )
wy=this.workspacey ( )
wh=this.workspaceheight ( )
ww=this.workspacewidth ( )

if ( handle (lpop) > 0 ) then
move(lpop,wx+ww - lpop.width - 15,wy+wh - lpop.height - 22)
end if

SetFocus(this)

//w_popup的Open事件
sle_time.weight = 300
Timer(5)
triggerevent(this,timer!)

//w_popup的Timer事件
Time t
t=now()
sle_time.hide ( ) //hide the sle

sle_time.text=space(len(sle_time.text))
sle_time.text=string(hour(t))+":"+string(Minute(t))+":"+string(Second(t))

sle_time.show ( ) //re-show the sle


PowerBuilder与外界的交互方式

天顺公司软件部 陈俊明编写

一.概述

一般来说，如果系统是一个纯粹的C/S模式的MIS应用系统，则与外部程序的交互比较少，PB本身提供的功能就足够实现所有的功能；但有的时候，当对系统的界面，性能要求比较严，或是在系统中还有很多其它的功能时，比如说办公自动化系统，此时需要比较多的与外界（或是外部程序）通迅，比如要发送EMAIL等等。

就PowerBuilder来说，它是一个开放式的开发平台，提供了多种手段来扩展它的功能，归纳起来，主要有：

  • WIN API调用
  • DDE（动态数据交换）
  • OLE（动态数据链接）
  • OCX（自定义控件）

下面我们一一介绍各种方法

二.WIN API调用

在一个系统中，有的功能用PB是实现不了或是不好实现的，比如对数据的压缩，而在WIN API中却有相应的函数调用或是我们用其它语言实现起来比较方便，此时我们就要用到API的调用。

在讲解之前，我们来看一下DDL文件的结构。在DDL中，所有的函数可分为输出和非输出两大类，输出的函数可以在外部调用，而非输出的函数则只能在DDL内部调用(WIN95中也可调用)。一般的，一个DDL包括以下几个部份：

  • 名为LibEntry的汇编语言模块
  • 名为LibMain()的非输出启动模块
  • Exported Programmer-Defined DLL函数(输出函数）
  • 可选的Nonexported Programmer-Defined DLL函数（非输出函数）

当系统装载或卸载DDL时，都要运行一次LibMain()和WEP()函数。LibMain()执行初始化工作，而WEP()执行所要求的清理工作。

DDL创建的方法有很多种，当然您可直接用WIN API函数和汇编语言编写，但一般不这样，象在Visual C++中就提供了建立DLL的功能，还有很多其它方法，在这就不一一说明了。

在创建了DLL后，怎样才能在PB中访问其中的函数呢？您需要做以下几步工作：

  • 将DLL文件拷贝到自已的DOS路径下或是Windows的路径下
  • 在调用以前声明您所要调用的函数：FUNCTION <函数说明> Library <DDL文件名>
  • 在需要的时候调用

在PB中，对外部函数的声明有两种情况，语法如下：

语法一：有返回值的函数

{ access } FUNCTION returndatatype name ( { { REF } datatype1 arg1, ..., { REF } datatypen argn } ) LIBRARY "libname" ALIAS FOR "extname"
语法二：无返回值的函数

{ access } SUBROUTINE name ( { { REF } datatype1 arg1, ..., { REF } datatypen argn} ) LIBRARY "libname" ALIAS FOR "extname"

参数的传递：

通过值传递的语法： ParameterDataType Parameter
通过引用传递的语法： REF ParameterDataType Parameter

实例：

取得用户名：
FUNCTION BOOLEAN GetUserNameA(REF STRING uName,REF ULONG sLength) &
LIBRARY "advapi32.DLL"
STRING UserName,sLength
sLength = 255
UserName = SPACE(255) //分配空间
GetUserNameA(UserName,sLength)

动态触发键盘的键值(本例为TAB)：
#DEFINE TABKeyCode 7
SUBROUTINE Keybd_Event(INT bVk,INT bScan,INT dwFlags,INT dwExtraInfo) &
LIBRARY "user32.dll"
Keybd_Event(TABKeyCode,0,0,0) //按下TAB
Keybd_Event(TABKeyCode,0,2,0) //放开TAB

三.DDE(动态数据交换)

DDE其实质是一种Windows应用程序之间的交换数据的机制，它是通过其享内存来实现的。通常它通过以下方式来实现：
1.DDE客户应用程序与DDE服务器应用程序建立会话
2.DDE客户应用程序向DDE服务器应用程序提出请求
3.DDE服务器应用程序向DDE客户应用程序提供数据和服务

它的工作方式有三种:
1.冷连接:只有当客户应用程序请求时,服务器应用程序才发送数据
2.温连接:建立了会话后,如果服务器应用程序的数据更新了,将发送一个消息给客户应用程序
3.热连接:如果服务器应用程序的数据更新了,将自动将数据发送给客户应用程序

PowerBuilder中的对应事件:
HotLinkAlarm: 在DDE服务器应用发送了数据时,应用程序接收到后触发
RemoteExec: 当一个DDE客户发送了一条命令时触发
RemoteHotLinkStart: 当DDE客户开始一个热连接时触发
RemoteHotLinkStop: 当DDE客户结束一个热连接时触发
RemoteRequest:: 当DDE客户请求数据时触发
RemoteSend: 当DDE客户已发送了数据时触发
PowerBuilder中的函数:
CloseChannel: 关闭由OpenChannel打开的与DDE服务器应用的通道
ExecRemote: 请求DDE服务器运行指令
GetDataDDE: 从热连接的DDE服务器应用中得到数据并放到定义好的串中
GetDataDDEOrigin: 从已建立的热连接中确定数据来源
GetRemote: 向服务器请求数据
OpenChannel: 打开一个通道
RespondRemote: 通知DDE服务器应用,所收到的数据是否可接收
SetRemote: 请求DDE服务器应用设置一些特定项
StartHotLink: 与DDE服务器应用建立热连接
StopHotLink: 结束与DDE服务器应用的热连接
GetCommandDDE: 得到由DDE客户发送的指令
GetCommandDDEOrigin: 得到DDE客户指令的源
StartDataDDE: 发送特定数据到DDE客户应用程序
StartServerDDE: 开始启动服务器应用
StopServerDDE: 停止服务器应用

实现步骤如下:
1.建立连接
2.发送请求或是处理请求
3.发送数据或是接收数据
4.结束连接

四.OLE和OCX(对象连接与嵌入)

OLE与OCX的应用主要是源于将多个应用信息源组合到一起的目标，在PowerBuilder的实际应用中,OCX的应用比较多，因为它能更好的与PowerBuilder的应用程序相结合，从而实现PowerBuilder不好实现或是很难实现的功能。实际上，PowerBuilder本身以及Visual Basic等等其它程序提供了很多处现特定功能的OCX控件。通过它们，我们可以非常容易的实现一些PowerBuilder本身不能实现的功能。

在PowerBuilder中，如果要调用OCX控件的话，首先得将OCX控件在系统中注册才能使用。在PowerBuilder中使用OCX控件非常的容易，只需要将OCS控件加入到程序中，然后直接调用其功能即可。其功能请参考各OCX控件的相关资料。


 如何在PB中切换中英文输入法数

关键：API函数ImmSimulateHotKey和ImmIsIME

函数声明：
function boolean ImmSimulateHotKey (ULong hWnd, ULong dwHotKeyID) library "IMM32.dll"
function ulong GetKeyboardLayout(ulong dwLayout) LIBRARY "user32.dll"
function boolean ImmIsIME(uLong hklKeyboardLayout) library "IMM32.DLL"

脚本如下：
constant int IME_THotKey_IME_NonIME_Toggle=112
ulong hklCurrent
ulong hnd

//切换到英文输入法
hklCurrent=GetKeyboardLayout(0)
if ImmIsIME(hklCurrent) then
hnd=Handle(parent)
ImmSimulateHotKey(hnd,IME_THotKey_IME_NonIME_Toggle)
end if

//切换到中文输入法
hklCurrent=GetKeyboardLayout(0)
if not ImmIsIME(hklCurrent) then
hnd=Handle(parent)
ImmSimulateHotKey(hnd,IME_THotKey_IME_NonIME_Toggle)
end if


Delphi4.0的Winsocket编程 作者：蒋立冬

    也许你曾经叹服过微软公司的IE浏览器等网络应用软件功能之强大,技术之先进，也许你目前还没有能力开发这样的一个复杂的大型系统，不过不用着急,这里可以给你一个练习的机会，就是学会用Delphi的Winsocket来编写基于TCP/IP的应用程序，这里假设你已经对Delphi开发工具已经初步熟悉了。 　　我们可以想象，如果从最原始开始来编写一段实现与远程计算机通信的程序，则必须对相关的网络协议、系统的底层知识以及网络软硬件技术有一个全面的熟悉和掌握，然而这在短时间内对大多数人来讲，都不是一件容易的事。 　　幸运的是Delphi的网络组件库中为我们提供了关于实现网络通信的组件，它们封装了复杂的具体实现的细节。其中ClientSocket和ServerSocket组件使我们能够很方便地编写出自己的网络通信和资源共享程序。 　　Delphi 4.0中编写Winsocket程序的步骤分为： 　　1）Winsocket组件属性设置； 　　2）建立与远程计算机的连接； 　　3）计算机间的数据传输； 　　下面具体就这三个步骤做详细说明。 　　一．Winsocket属性的设置 　　Delphi 4.0中将Winsocket细分为两种组件：ClientSocket和ServerSocket。它们分别作为客户端和服务器端的组件。即在客户端程序中采用ClientSocket组件，而在服务器端则采用ServerSocket组件。通过这两种组件之间的通信，再加上辅助的应用程序代码，就可以实现一个简单的通信程序。当然在客户端程序中再引入ServerSocket的话，那么客户端程序就可以充当服务器了，可以对其他的客户端程序的请求进行响应。 　　如果正在编写服务器端程序，就必须设置ServerSocket组件的Port属性。之所以要设置此参数，是因为在同一台计算机上可能运行着多个服务器程序，而它们可能总在不停地接受来自于远程客户端程序的连接请求。如果你正试图发送一份电子邮件，而系统却将你的请求连接到了另外一个服务器程序上了，则你的邮件就肯定无法正常发送出去了。所以必须给服务器程序上的ServerSocket组件设置一个合适的端口号Port。也可以设置Service属性，它指示了ServerSocket所提供的服务类型，比如：FTP、HTTP等等。然后设置Active属性为True。 　　如果正在编写客户端程序，则设置ClientServer组件的属性就多一些。Port属性应设置成和服务器端的Port属性值一致。另外Host的属性必须正确设置，它是一个只读属性，在设计时不可用。它指示了客户程序所要连接的远程服务器的主机名。也可以设置Address属性，也就是远程主机的IP地址。如果这两个属性都设置了的话，那么系统将采用Host属性，而Address属性设置的IP地址就变成无效了。 　　二．建立与远程计算机的连接 　　要在远程计算机系统之间进行数据传输，首先必须在通信的两台主机之间建立连接。 　　服务器端的ServerSocket组件调用Open方法初始化Socket连接，同时也就设置了Active属性为True，将ServerSocket组件设置成侦听模式，随时侦测是否有连接请求。 　　如果服务器接受了客户程序的连接请求，则触发OnAccept事件，如下代码就是处理接受连接后服务器程序所要做的工作。 　　procedure Myform..ServerSocketAccept(Sender: TObject，Socket: TCustomWinSocket); 　　begin 　　IsServer := True; 　　end; 　　在客户端程序中，ClientSocket组件则设置Port、Host等必须的属性，然后设置Active属性为True，提出连接请求。 　　三．计算机之间的数据传输 　　一旦服务器端接受了客户机方面的连接请求，客户机就可以发送数据。这时，在客户机和服务器之间就拥有了一个Socket，通过此Socket双方实现通信。所以Socket属性很重要，它又拥有很多的方法，用其中的几个简单的方法，就可以实现数据的发送和接收。 　　客户机端用如下形式：ClientSocket1.socket.sendtext('string you want to send'); 　　在服务器端采用如下形式：ServerSocket1.socket.recievetext( str: string);此函数返回接受到的字符串的长度，将字符串存储在变量str中。 　　上述是数据传输的最简单的例子，你还可以采用Socket属性所提供的其他方法来实现复杂的数据传输。 　　虽然Delphi 4.0中使用Winsocket组件十分方便，但是要真正熟练运用它来实现网络应用程序的开发，则必须仔细阅读关于它的帮助文件和示例程序。在不断的学习中比较这种方法的优点，掌握其编程特点，进而掌握Delphi中所提供的其它网络组件，例如：NMPOP3、NMUDP、NMFTP等的编程技巧。到那时，你就会有能力参与真正意义上的网络应用程序的开发，而不是停留在拿来别人的网络应用软件，设置一番、会熟练操作的水平上。


 PB：关于触发器的应用 TQ (12/07/1999)
    昨天看《精通MS SQL SERVER 7.0》，一不小心看到一个屏蔽/恢复触发器功能的命令，以前看MS SQL SERVER在线帮助及手册的时候一直没注意（嘿嘿，也怪那些书和手册写得不好^_^）。在PB中调用方式如下： EXECUTE IMMEDIATE "ALTER TABLE TableName ENABLE TRIGGER"; EXECUTE IMMEDIATE "ALTER TABLE TableName DISABLE TRIGGER"; 红色字体TableName需要改正您自己的表名 通常使用触发器以下有几个用途： 1.保证数据完整性(例如删除一张基础数据表的同时，需要检查是否有其他相关业务发生表用到此数据，如果用到则不允许删除) 2.保持合计值的更新(例如一张入库单由多条明细记录组成，表头(入库单)需要有合计数量和金额，这样在表体(明细记录)的Table增加触发器来合计值并更新表头Table。 3.检查数据值的合法性。 　　虽然有很多好外，但有时带来的麻烦是，例如需要通过程序批量恢复部分表中丢失的数据（不是手工一条条输入）或删除某些数据，如果触发器存在会造成数据不一致或数据无法导入(或删除)情况。需要先屏蔽触发器，等工作完成后再恢复状态。 特别说明的是：如果屏蔽触发器会造成该表的触发器暂时失效，在一个运行在网络上的多用户MIS系统来说要特别小心，因为这样很有可能造成其他工作站所用系统的数据没有完整按事先的设计来保存数据，所以屏蔽触发器的同时应该采取相应手段来防止其他用户同时使用系统。例如可以在数据库中增加表来设置标志位，进入相关模块的读取此标志。 例如：可增加一张Table: 表1 模块名 使用计数 是否禁用 订单输入 3 0 发货单输入 2 0 表结构说明：表1只是说明用法，所以字段用中文名，各位实际使用时需要相应改动。“使用计数”表示有多少人正在使用此模块，可以在进入此模板的Window open事件中增加代码来累加此值，并在退出模板时扣除此计数。（没有考虑到非法退出系统或断电情况，可以用winstock来实现或简单的允许系统的管理员对此表进行修正来实现，随便说一下，系统的管理员也可以用PC Anywhere 9.0对整个网络的每台计算机查看，来看看每台电脑上运行的系统) “是否禁用”表示当前此模块是否可以使用，在相应open相应模板的Window之前先读取此字段来判断，如果禁用则提示用户系统正在进行备份/恢复工作（或其他），不允许进入操作。 下面代码是演示在希望暂时屏蔽触发器的时候进行的操作代码模板： Integer li_state Select 状态 into :li_state From 表1 Where 模块名 = "订单输入" If li_state > 0 Then MessageBox("提示", "因为现在订单输入模块还有" + String(li_state) + "人正在使用,你不能屏蔽触发器！") Return End If Update 表1 Set 是否禁用 = 1 Where 模块名 = "订单输入"; Commit; SQLCA.Autocommit = TRUE EXECUTE IMMEDIATE "ALTER TABLE 订单Table ENABLE TRIGGER"; SQLCA.Autocommit = FALSE ...... 进行相应处理代码 ...... SQLCA.Autocommit = TRUE EXECUTE IMMEDIATE "ALTER TABLE 订单Table DISABLE TRIGGER"; SQLCA.Autocommit = FALSE Update 表1 Set 是否禁用 = 0 Where 模块名 = "订单输入"; ....... Commit: 其他说明： 使用TRUNCATE TABLE语句可以直接删除某张表的所有数据，无论当前系统设置如何，都不会激发触发器，但如果删除将破坏表之间的引用完整性（建立了外部关键字），则TRUNCATE TABLE也同样会失效。另外，和DELETE命令不同之处，DELETE可以用Where语句来选择删除部分记录，但TRUNCATE不行。TRUNCATE也不进行日志记录，所以速度要比Delete快 Sybase Sql Anywhere启动加上参数 -gf 可以关闭所有Table触发器(没有关闭单张table的命令) 本文来自陶清网站


《 在PowerBuilder中更改窗口继承 》
（柯建勋 http://kjx.126.com)

     PowerBuilder是Sybase公司的独资子公司PowerSoft推出的应用于客户机／服务器体系结构下的应用程序开发工具，也是一种面向对象的图形化交互式开发工具。PowerBuilder 以其简洁高效的集成开发环境、强大的数据窗口技术、几乎无所不能的数据库访问能力和友好的用户界面，越来越受到编程人员的青睐。
    PowerBuilder采用真正的面向对象技术，而“继承”是面向对象的一大特征，充分利用这一技术，可以在很大程度上减少我们编程的工作量，加速开发进程。
    窗口的继承在PowerBuilder中最为常见，在开发过程中，我们经常会遇到更改窗口继承的情况。此时，新建并继承一个新窗口,重做一遍是常见的办法，但某些时候我们可用Export 和Import方便地实现窗口继承的更改。现以更改窗口w_mmaintain_yw_input的继承窗口（父窗口）为例进行说明。
    一、 使用Export
    在Library Painter 中选中窗口w_mmaintain_yw_input
    然后在主菜单中选择 Entry →再选Export ，PB将此窗口导入一文件w_mmaintain_yw_input.srw中，我们对此文件进行编缉，可看到PB将此窗口中所有对象的定义以及我们编写的所有脚本全部导入此文件。
    其中此窗口的定义描述有如下语句：
    global type w_mmaintain_yw_input from w_mmaintain_enter_template
    表明窗口w_mmaintain_yw_input 是从窗口w_mmaintain_enter_template继承而来的。
    若描述如下：
    global type w_mmaintain_yw_input from Window
    表明窗口w_mmaintain_yw_input没有使用继承，而是直接新建的。
    如果我们要将窗口w_mmaintain_yw_input 的继承窗口（父窗口）更改为w_mmaintain_input_template，则我们可将上述语句改为：
    global type w_mmaintain_yw_input from w_mmaintain_input_template
    并搜索w_mmaintain_enter_template，将其全部更改为w_mmaintain_input_template
    编缉完成后存盘。
    二、 使用Import
    在主菜单中选择 Entry →再选Import，
    选择我们刚才所编缉的文件w_mmaintain_yw_input.srw导入，即完成窗口w_mmaintain_yw_input继承的修改。
    在用上述方法更改窗口继承时要注意：
    1.更改前应做好备份。
    2.如果改继承前父窗口与改继承后父窗口内控件有区别，如上例，有一个Command Button “保存”在窗口w_mmaintain_en_ter_template中名字为cb_update，而在窗口w_mmaintain_input_template中名字为cb_save，则我们需要搜索文件w_mmaintain_yw_input.srw，将其中所有cb_update 改为cb_save，否则导入操作将出错而不能完成。
    3.如果更改继承前父窗口与更改继承后父窗口内控件的区别过大，也可用上述方法完成，但比较繁琐，建议仍用最直接的方法——重建。


向窗口传递多个参数

用 OpenWithParm 打开窗口并向窗口传递参数时，PowerBuilder 只允许传递一个参数，例如： OpenWithParm(w_abc,
"hello")

如何传递多个参数呢？很简单，创建一个结构，将你希望传递的参数都定义到这个结构中去。

例如：首先定义结构 str_parms

然后在 script中声明结构变量：

str_parms l_str_parms

给结构变量赋值：

l_str_parms.name = "Smith"
l_str_parms.tel = "1234567"

OpenWithParm(w_abc, l_str_parms)

在窗口 w_abc的open事件中输入代码如下：

str_parms l_str_parms

l_str_parms = Message.PowerObjectParm

这个方法同样适用于CloseWithReturn.


数据管道

本例主要描述了数据管道的用法，我们首先看看数据管道的一些基础知识：

数据管道主要可以完成以下几个功能：

数据库之间数据的传送
修改表的结构，象主键标,是否可为NULL等

我们在Pipeline画板中创建一个管道对象时，必须说明以下这些参数：

Source Connection | 源数据库
Destination Connection | 目的数据库
Data Source | 源表
Table | 目的表
Option | 管道操作

其中管道操作方式有：

Create: 添加表（表不能存在〕
Replace: 删除再添加表（表可以存在
Refresh: 删除再插入行
Append: 插入行
Update: 更新/插入行

管道对象属性:

RowsInError Long 管道中发现的错误的行数
RowsRead Long 管道已读取行数
RowsWritten Long 写入数据库的行数
DataObject String 管道对象名
Syntax String 建立管道对象的语法字符串

管道对象函数:

Cancel Integer 中止管道执行
ClassName String 返回用户对象的名称
GetParent PowerObject 返回父对象的名称
PostEvent Boolean 在用户对象的消息队列末增加一个事件
Repair Integer 修正目的数据库
Start Integer 执行管道
TriggerEvent Integer 送一个事件到用户对象并执行事件脚本
TypeOf Object 返回用户对象的类型

要在程序中使用管道，必须要在Pipeline画板中创建一个管道对象，一个管道类型的
标准用户类对象和包含DataWindow控件的窗口。 PowerBuilder检测到任何管道错误后
将使用标准的dataWindow显示这个错误, datewindow 每个无效行显示全部的列，并显
示一条错误消息。在本例中，创建了3个管道对象：

p_emp_master_create,
p_emp_transaction_create,
p_emp_master_transaction_update.

p_emp_create为employee表建一个拷贝，作为本例的Master Table，
p_emp_transaction_create建立本例的Transaction Table,
p_emp_master_transaction_update根据所选部门和Transaction Table的内容修改Master Table相应记录，它的语法为：
SELECT "emp_pipe_transaction"."emp_id",
"emp_pipe_transaction"."emp_fname",
"emp_pipe_transaction"."emp_lname",
"emp_pipe_transaction"."dept_id",
"emp_pipe_transaction"."bene_health_ins",
"emp_pipe_transaction"."bene_life_ins",
"emp_pipe_transaction"."bene_day_care"
FROM "emp_pipe_transaction"
WHERE ( emp_pipe_transaction.dept_id = :department_id )
其中：:department_id即为在数据窗口dw_dept中所选部门的id号。

管道类型的用户对象有2个：p_pipe_wmeter和p_pipe_wometer,它们都是从pipeline继承来
的，都包含有3个静态文本类型的实例变量：st_read,st_written,st_errors。 p_pipe_wmeter
在它的pipemeter事件中对它们赋值，在窗口的Open事件中把它们与窗口上的3个静态
文本控件联系起来，动态显示当前数据管道的进度。

总结：

要实现数据管道要完成以下几步：

1. 创建Pipeline类型的标准不可视用户对象p_pipe_meter
2. 在Pipeline中创建一数据管道(作为第5步的DataObject)
3. 创建一窗口（带一数据窗口控件PipeError和命令按钮〕
4. 在OPEN事件中创建一p_pipe_meter类型的iu_PipeLine实例变量
5. 设置iu_PipeLine的DataObject特性
6. 连接源数据库和目的数据库
7. 启动数据管道（按钮Clicked事件〕
8. 动态修改进度（数据管道的PipeMeter事件)
9. Cancel事件
10.管道修复
11.提示完成（PipeEnd事件〕
12.断开数据库，Destory用户对象。


 PowerBuild 的高级菜单设计技术
武警学院计算机中心 程连孚
作者地址：河北省廊坊市武警学院计算机中心
邮编： 065000
E_mail：chlf@hotmail.com

    做为事件驱动的系统，菜单所起的作用是明显的。一个好的菜单，能给程序起到锦上添花的作用。PowerBuild 5.0(以下简称PB5)在这方面给用户提供了很大的灵活性，使用户能根据自已的爱好和需求设计出各种不同风格的菜单。下面就谈谈如何在PB5中实现两种别致的菜单：动态菜单和声音菜单。
一、动态菜单
如果一个程序的选项复杂，那么菜单项也会相应地增多。在主界面上设置过多的菜单项会影响界面的简洁，有时也会使用户无所适从。如果将菜单设置成动态菜变化的菜单，在一个功能下只显示相应的菜单，那么用户使用起来会效果更好。
菜单在PB5中是窗口的一个属性，我们可在通过改变这种属性来改变窗口菜单的显示。我们假设在已存在一个库test.pbl，该库中有一个标准的File菜单(m_file)，如下所示：
图1 例程界面

窗口上有一个按钮"Change to Another Applicatiuon"，按下后，程序将启动另一个应用模块，该模块将用自己的菜单来替代现有的菜单。我们可以通过下面的方法来实现这一功能。
1、在菜单模板下，创建一个菜单，设为 m_test，它与前面的file菜单有完 全不同的设置
2、利用"Change to Another Application"按钮的脚本来改变菜单的显示。
单现"Script"按钮，在其中添加如下的语句：
Parent.ChangeMenu(m_test)
3、运行程序，即可得到结果。 需要说明地是，改变菜单项必须通过ChangeMenu( )函数，而不能通过标准的"."来无成，因为在PB5中，菜单对窗口来说是保护（protected）属性，该属性只能通过界面函数来访问。另外，如果在菜单项中添加脚本来动态改变菜单，则脚本中应该使用语名：
ParentWindow.ChangeMenu(m_test)
二、为菜单添加声音
在多媒体演示产品中，我们经常会遇到这样的情形，当鼠标单击某一菜单或按钮时，音箱会发出某种音效（如果有这种设备的话），这使得软件更加生动。在 PB中我们也可以很容易地实现这一点。
PB中没有直接调用多媒体功能的函数，要想为一个程序加入多媒体功能，必须通过调用Windows API函数"SndPlaySound()"或"PlaySound()"来完成。
假设我们己有有了一个库"test.pbl"，其菜单也如前所示，具体实现步骤如下 :
1、在"Declare"菜单中选择"Local External Functions…"，弹出的对话框中填入如下的声明语句：
Function Boolean sndPlaySound(String s_file,&
UINT u_flags) Library "WINMM.dll" Function UINT LoadLibrary(String as_library) "kernel32.dll"
Subroutine FreeLibrary(UINT Hinstance)Library "kernel32.dll"
实际上PB中的菜单也是动态的，"Declare"必须在编辑菜单时才出现
2、为每个菜单项选择相应的声音文件" .WAV"
3、为菜单项添加脚本，驱动声音文件，以"new"菜单为例：
sndPlaySound ("new.wav",0)
sndPlaySound函数有两个参数，前面的一个为声音文件名，后面的一个为播放方式，主要用到有如下几个：
0－－同步播放
1－－异步播放
2－－找不到声音文件即返回
4－－播放声音文件在内存中的映象
声音文件最好是放在当前目录下，否则系统将按如下的顺序进行查找：当前目录，Windows目录，Windows系统目录，PATH中列出的目录，网络映射目录。
完成上述步骤后，运行程序即可听到菜单的声音了。


 事务处理

一 事务处理介绍

事务是这样一种机制，它确保多个SQL语句被当作单个工作单
元来处理。事务具有以下的作用：
* 一致性：同时进行的查询和更新彼此不会发生冲突，其他
用户不会看到发生了变化但尚未提交的数据。
* 可恢复性：一旦系统故障，数据库会自动地完全恢复未完
成的事务。

二 事务与一致性

事务是完整性的单位，一个事务的执行是把数据库从一个一
致的状态转换成另一个一致的状态。因此，如果事务孤立执行时
是正确的，但如果多个事务并发交错地执行，就可能相互干扰，
造成数据库状态的不一致。在多用户环境中，数据库必须避免同
时进行的查询和更新发生冲突。这一点是很重要的，如果正在被
处理的数据能够在该处理正在运行时被另一用户的修改所改变，
那么该处理结果是不明确的。
不加控制的并发存取会产生以下几种错误：
1 丢失修改（lost updates）
当多个事务并发修改一个数据时，不加控制会得出错误的结
果，一个修改会覆盖掉另一个修改。
2 读的不可重复性
当多个事务按某种时间顺序存取若干数据时，如果对并发存
取不加控制，也会产生错误。
3 脏读（DIRDY DATA），读的不一致性
4 光标带来的当前值的混乱
事务在执行过程中它在某个表上的当前查找位置是由光标表
示的。光标指向当前正处理的记录。当处理完该条记录后，则指
向下一条记录。在多个事务并发执行时，某一事务的修改可能产
生负作用，使与这些光标有关的事务出错。
5 未释放修改造成连锁退出
一个事务在进行修改操作的过程中可能会发生故障，这时需
要将已做的修改回退（Rollback）。如果在已进行过或已发现错
误尚未复原之前允许其它事务读已做过修改（脏读），则会导致
连锁退出。
6 一事务在对一表更新时，另外的事务却修改或删除此表的
定义。
数据库会为每个事务自动地设置适当级别的锁定。对于前面
讲述的问题：脏读、未释放修改造成的连锁退出、一事务在对一
表更新时另外的事务却修改或删除此表的定义，数据库都会自动
解决。而另外的三个问题则需要在编程过程中人为地定义事务或
加锁来解决。

三 事务和恢复

数据库本身肩负着管理事务的责任。事务是最小的逻辑工作
单元，在这个工作单元中，对数据库的所有更新工作，要么必须
全部成功，要么必须全部失败（回退）。只要应用程序指定了某
段程序为一个事务并做了相应的处理（提交或回退），数据库系
统会自动维护事务本身的特性。

四 ORACLE数据库的事务定义

ORACLE事务从COMMIT、ROLLBACK、连接到数据库或开始第一
条可执行的SQL语句时开始，到一条COMMIT、ROLLBACK语句或退出
数据库时结束。如果在一个事务中包含DDL语句，则在DDL语句的
前后都会隐含地执行COMMIT语句，从而开始或结束一个事务。
如果一个事务由于某些故障或者由于用户改变主意而必须在
提交前取消它，则数据库被恢复到这些语句和过程执行之前的状
态。
利用ROLLBACK语句可以在COMMIT命令前随时撤消或回退一个
事务。可以回退整个事务，也可以会退部分事务，但是不能回退
一个已经被提交的事务。回退部分事务的ROLLBACK命令为：
ROLLBACK to savepoint 存储点名
存储点是用户放入事务中的标记，用来表示一个可被回退的
位置。存储点通过在事务中放入一个SAVEPOINT命令而被插入。该
命令的语法是：
SAVEPOINT 存储点名
如果在ROLLBACK语句中没有给出存储点名，则整个事务被回
退。

五 SYBASE数据库的事务定义

SYBASE通过使用BEGIN TRANsaction和COMMIT TRANsaction命令指
示SQL将任意数目的语句作为一个单元来处理。ROLLBACK TRANsaction
命令则允许用户恢复到事务的开始，或恢复到事务内部已经被用SAVE
TRANsaction命令定义的存储点上。
BEGIN TRANsaction和COMMIT TRANsaction能够包含任意数目的SQL
语句和存储过程，方法很简单：
BEGIN TRANsaction [事务名称]
COMMIT TRANsaction
如果一个事务由于某些故障或者由于用户改变主意而必须在提交
前取消它，则数据库被恢复到这些语句和过程执行之前的状态。
利用ROLLBACK TRANsaction命令可以在COMMIT TRANsaction命令
前随时回退一个事务。可以回退整个事务，也可以回退部分事务，但
是不能回退一个已经被提交的事务。ROLLBACK TRANsaction命令为：
ROLLBACK TRANsaction [事务名|存储点名]
存储点名是用户放入事务中的标记，用来表示一个可以被回退的
位置。存储点名通过在事务中放入一个SAVE TRANsaction命令而被插
入。该命令的句法是：
SAVE TRANsaction 存储点名
如果在ROLLBACK TRANsaction中没有给出存储点名或事务名，则
事务被回退到批处理中的第一个BEGIN TRANsaction语句处。


 视图
发信人: STW (蓝快), 信区: DataBase
标 题: 数据库技术专题 (二) -- 视图(1)
发信站: 广州蓝天站 (Fri Jan 30 01:00:13 1998), 转信 一 视图的概念

视图是原始数据库数据的一种变换，是查看表中数据的另外一种
方式。可以将视图看成是一个移动的窗口，通过它可以看到感兴趣的
数据。
视图是从一个或多个实际表中获得的，这些表的数据存放在数据
库中。那些用于产生视图的表叫做该视图的基表。一个视图也可以从
另一个视图中产生。
视图的定义存在数据库中，与此定义相关的数据并没有再存一份
于数据库中。通过视图看到的数据存放在基表中。
视图看上去非常象数据库的物理表，对它的操作同任何其它的表
一样。当通过视图修改数据时，实际上是在改变基表中的数据；相反
地，基表数据的改变也会自动反映在由基表产生的视图中。由于逻辑
上的原因，有些视图可以修改对应的基表，有些则不能（仅仅能查询）。

二 视图的作用

视图有以下的作用：

* 简单性。看到的就是需要的。视图不仅可以简化用户对数据的理
解，也可以简化他们的操作。那些被经常使用的查询可以被定义为视图，
从而使得用户不必为以后的操作每次指定全部的条件。
* 安全性。通过视图用户只能查询和修改他们所能见到的数据。数
据库中的其它数据则既看不见也取不到。数据库授权命令可以使每个用
户对数据库的检索限制到特定的数据库对象上，但不能授权到数据库特
定行和特定的列上。通过视图，用户可以被限制在数据的不同子集上：
使用权限可被限制在基表的行的子集上。
使用权限可被限制在基表的列的子集上。
使用权限可被限制在基表的行和列的子集上。
使用权限可被限制在多个基表的连接所限定的行上。
使用权限可被限制在基表中的数据的统计汇总上。
使用权限可被限制在另一视图的一个子集上，或是一些视图和基表
合并后的子集上。

* 逻辑数据独立性。视图可帮助用户屏蔽真实表结构变化带来的影响。

三 视图的安全性

视图的安全性可以防止未授权用户查看特定的行或列，是用户只能
看到表中特定行的方法如下：
1 在表中增加一个标志用户名的列；
2 建立视图，是用户只能看到标有自己用户名的行；
3 把视图授权给其他用户。

四 逻辑数据独立性

视图可以使应用程序和数据库表在一定程度上独立。如果没有视图，
应用一定是建立在表上的。有了视图之后，程序可以建立在视图之上，
从而程序与数据库表被视图分割开来。视图可以在以下几个方面使程序
与数据独立：
1 如果应用建立在数据库表上，当数据库表发生变化时，可以在表
上建立视图，通过视图屏蔽表的变化，从而应用程序可以不动。
2 如果应用建立在数据库表上，当应用发生变化时，可以在表上建
立视图，通过视图屏蔽应用的变化，从而使数据库表不动。
3 如果应用建立在视图上，当数据库表发生变化时，可以在表上修
改视图，通过视图屏蔽表的变化，从而应用程序可以不动。
4 如果应用建立在视图上，当应用发生变化时，可以在表上修改视
图，通过视图屏蔽应用的变化，从而数据库可以不动。


 存储过程一 存储过程介绍

存储过程是由流控制和SQL语句书写的过程，这个过程经编译和优化
后存储在数据库服务器中，使用时只要调用即可。在ORACLE中，若干个
有联系的过程可以组合在一起构成程序包。
使用存储过程有以下的优点：
* 存储过程的能力大大增强了SQL语言的功能和灵活性。存储过程可
以用流控制语句编写，有很强的灵活性，可以完成复杂的判断和较复杂的
运算。
* 可保证数据的安全性和完整性。
# 通过存储过程可以使没有权限的用户在控制之下间接地存取数据
库，从而保证数据的安全。
# 通过存储过程可以使相关的动作在一起发生，从而可以维护数据
库的完整性。
* 再运行存储过程前，数据库已对其进行了语法和句法分析，并给出
了优化执行方案。这种已经编译好的过程可极大地改善SQL语句的性能。
由于执行SQL语句的大部分工作已经完成，所以存储过程能以极快的速度执
行。
* 可以降低网络的通信量。
* 使体现企业规则的运算程序放入数据库服务器中，以便：
# 集中控制。
# 当企业规则发生变化时在服务器中改变存储过程即可，无须修改
任何应用程序。企业规则的特点是要经常变化，如果把体现企业规则的运
算程序放入应用程序中，则当企业规则发生变化时，就需要修改应用程序
工作量非常之大（修改、发行和安装应用程序）。如果把体现企业规则的
运算放入存储过程中，则当企业规则发生变化时，只要修改存储过程就可
以了，应用程序无须任何变化。
不同数据库存储过程的写法不一，在后面的讲座中将分别介绍ORACLE
和SYBASE存储过程的用法。

二 ORACLE 的存储过程

ORACLE 创建存储过程的语法为：
create [or replace] procedure 过程名
参数1 [in|out|in out] 数据类型
[,参数2 [in|out|in out] 数据类型]...
{is|as} pl/sql 语句
下面举例说明ORACLE数据库存储过程的写法和用法。
可以建立一个存储过程，每当用户修改数据库的重要数据时，即把
用户的用户名、日期和操作类型记录下来：
create procedure update_log is
begin
insert into update_log_tab(use_name,update_date,operation)
values(user,sysdate,'update'
end;
可以在恰当的位置调用这个存储过程来记录用户对表的修改。例如下面在
表sal_comm上建立一个修改触发器，每当用户修改此表后，用户的名称、修改
时间和操作即被记录在了表update_log_tab中：
create trigger audit_update
after update on sal_comm
for each row
begin
update_log
end

三 Sybase的存储过程

尽管Sybase存储过程的功能和写法与ORACLE类似，但他们之间还是
有一定的差别。下面讲述SYBASE的存储过程。
SYBASE可以用CREATE PROCedure命令生成存储过程：
CREATE PROCedure 存储过程名 [；number]
[[(] @parameter_name datatype [=default] [OUTput]
[, @parameter_name datatype [=default] [OUTput]]...[)]]
[WITH RECOMPILE]
AS SQL_statements
下面是一个查询作者编码、名称和生日的存储过程：
create proc p_auths @author_code varchar(10)
as
select author_code, name, birthdate
from auths
where author_code=@author_code
下面执行过程p_auths:
p_auths @author_code=A00001
在CREATE PROC语句中，可以为参数赋缺省值，该值可以是任何常量。
当用户不提供参数值时，该值便作为参数值提供给过程。


 触发器一 触发器介绍

触发器是一种特殊的存储过程，它在插入，删除或修改特定表中
的数据时触发执行，它比数据库本身标准的功能有更精细和更复杂的
数据控制能力。数据库触发器有以下的作用：
* 安全性。可以基于数据库的值使用户具有操作数据库的某种权利。
# 可以基于时间限制用户的操作，例如不允许下班后和节假日
修改数据库数据。
# 可以基于数据库中的数据限制用户的操作，例如不允许股票
的价格的升幅一次超过10%。
* 审计。可以跟踪用户对数据库的操作。
# 审计用户操作数据库的语句。
# 把用户对数据库的更新写入审计表。
* 实现复杂的数据完整性规则。
# 实现非标准的数据完整性检查和约束。触发器可产生比规则
更为复杂的限制。与规则不同，触发器可以引用列或数据库对
象。例如，触发器可回退任何企图吃进超过自己保证金的期货。
# 提供可变的缺省值。
* 实现复杂的非标准的数据库相关完整性规则。触发器可以对数
据库中相关的表进行连环更新。例如，在auths表author_code列上的
删除触发器可导致相应删除在其它表中的与之匹配的行。
# 在修改或删除时级联修改或删除其它表中的与之匹配的行。
# 在修改或删除时把其它表中的与之匹配的行设成NULL值。
# 在修改或删除时把其它表中的与之匹配的行级联设成缺省值。
# 触发器能够拒绝或回退那些破坏相关完整性的变化，取消试
图进行数据更新的事务。当插入一个与其主健不匹配的外部键
时，这种触发器会起作用。例如，可以在books.author_code
列上生成一个插入触发器，如果新值与auths.author_code列
中的某值不匹配时，插入被回退。
* 同步实时地复制表中的数据。
* 自动计算数据值，如果数据的值达到了一定的要求，则进行特
定的处理。例如，如果公司的帐号上的资金低于5万元则立即给财务人
员发送警告数据。
ORACLE与SYBASE数据库的触发器有一定的区别，下面将分别讲述
这两种数据库触发器的作用和写法。

二 ORACLE 触发器

ORACLE产生数据库触发器的语法为：
create [or replace] trigger 触发器名 触发时间 触发事件
on 表名
[for each row]
pl/sql 语句
其中：
触发器名：触发器对象的名称。由于触发器是数据库自动执行
的，因此该名称只是一个名称，没有实质的用途。
触发时间：指明触发器何时执行，该值可取：
before---表示在数据库动作之前触发器执行；
after---表示在数据库动作之后出发器执行。
触发事件：指明哪些数据库动作会触发此触发器：
insert：数据库插入会触发此触发器；
update：数据库修改会触发此触发器；
delete：数据库删除会触发此触发器。
表 名：数据库触发器所在的表。
for each row：对表的每一行触发器执行一次。如果没有这一
选项，则只对整个表执行一次。
举例：下面的触发器在更新表auths之前触发，目的是不允许在
周末修改表：
create trigger auth_secure
before insert or update or delete //对整表更新前触发
on auths
begin
if(to_char(sysdate,'DY')='SUN'
RAISE_APPLICATION_ERROR(-20600,'不能在周末修改表auths');
end if;
end

三 SYBASE数据库触发器

SYBASE数据库触发器的作用与ORACLE非常类似，仅有较小的差异。
SYBASE产生触发器的语法为：
CREATE TRIGGER 触发器名
ON 表名
FOR INSERT，UPDATE，DELETE
AS
SQL_statement |
FOR INSERT，UPDATE
AS
IF UPDATE(column_name) [AND|OR UPDATE(column_name)]...
SQL_statements
上面FOR子句用来指定在触发器上的哪些数据更新命令可激活该
触发器。IF UPDATE子句检查对指定列的操作类型，在IF UPDATE子句
中可指定多个列。
与ORACLE不同，对于每条SQL语句，触发器只执行一次。触发器
在数据更新语句完成以后立即执行。触发器和启动它的语句被当作一
个事务处理，事务可以在触发器中回退。
下面举例说明SYBASE触发器的写法。
create trigger forinsert_books
on books
for insert
as
if(select count(*) from auths,inserted
where auths.author_code=insert.author_code)!=@@rowcount
begin
rollback transaction
print "books 表中 author_code 列的值在auths 表中不存在。"
end


 PB中实现图片按钮的mousemove事件
四川省绵阳市
李万昌
---- PB中除窗口(window)和富文本(richtextedit)具有mousemove事件外，
其他控件都不接收ousemove事件。在某些程序中，可能会对按钮进行动态的
图片切换，如：鼠标移到按钮上时，切换按钮的图片以实现动感。

---- 创建一个用户自定义对象(User Object),取名uo_picbutton,其上放一
个Picture控件(p_1)。选中p_1,定义用户自定义事件(user events),在
event name中输入mousemove, 由下面的Paste Event ID中选取pbm_mousemove
放入Event ID栏。自此，p_1的事件栏中增加了事件mousemove,在其中编程：

---- p_1.picturename="c:\图片_over.bmp" //鼠标移动到p_1上时要显示
的图片

---- 关闭user object，新建一个窗口，添加刚才建成的user object(uo_1).
在form的open事件中编程：

---- uo_1.p_1.picturename="c:\图片_up.bmp" //初始图片

---- 在form的mousemove事件中编码：

---- uo_1.p_1.picturename="c:\图片_up.bmp" //恢复原来的图片

---- 这样，运行时一旦鼠标移动到该控件上就会变换图片，从而产生动
态效果。只要在uo_picbutton的mousemove事件中增加两三句代码，可以
使其成为通用的图片按钮；添加user event事件，并拷贝下面列出的事件
ID(event id),可以生成更多的原本不能接收的事件。

海洋之星文库-PowerBuilder资料:http://hxzhang.yeah.net
收集整理自 世界计算机日报 转载请勿删除此行。
返回主页


在PB6.5中利用Win9X的OCX控件美化程序界面
广东省韶钢集团财务部
赖晓敏
---- 用过Power Builder编程的人都会有这种感觉：PB在操作数据库数据方面
提供了简捷、独特的方法，是同类开发工具所无法比拟的。但是在程序界面设
计方面却美中不足。一个好的程序员总是想方设计美化自己的程序，不但要求
自己的程序能解决用户的需求和提供简便的操作，而且希望用户在使用界面优
秀的程序的过程中充分体现美的感受。这是笔者在多年开发中所追求的目标。
在PB中充分利用OCX控件编程，往往会得到事半功倍的功效，最近在互联网上
收集了很多利用OCX编程的技巧，但是在其他开发工具中能使用的控件在PB中
能用的却非常的少，花费了大量的精力和上网机时收获却并不大，本人在查
阅了有关书籍和网友的指点后，把有关PB程序界面美化的几点经验经过整理
和各位网友分享：

---- 一、 利用Windows 9x中的OCX控件制作状态条的方法：

---- 在PB中新建一窗口，在窗口画板中插入OLE，在“Insert Object”窗
口中选择“Insert Control”找到“Microsoft Statusbar Conrol,
Version5.0(sp2)，这样就可在窗口中插入一个状态条控件，此控件包含在
C:\Windows\System\COMCTL32.OCX文件中。在窗口和控件的Script 中只要
控制控件的显示位置和设定控件上的显示窗格，以及各窗格中显示的内容。
此控件预设了很多内容给用户选择，主要包括长短格式日期、系统时间、
大小写锁定键状态、插入锁定键状态、小键盘数字锁定键状态和自定义文
本等，例如要在程序中改变第一个窗格的显示内容方法如下：

//open for w_3 returns long
oh=this.height //oh,ow定义为全局变量
ow=this.width
statusbar_1.object.Panels.Item(1).Text = "广东省韶钢财务结算中心"

//resize for w_3 returns long
oh=newheight
ow=newwidth
statusbar_1.TriggerEvent(constructor!)

动态改变控件的显示位置：
//constructor for statusbar_1 returns long
this.x=0
this.width = ow – 2
this.y=oh - this.height

---- 二、 利用Windows 9x中的OCX控件制作月历选择的方法：

---- 此控件为“Microsoft Date and Time Picker Control Version 6.0”,
包含在C:\Windows\System\MSCOMCT2.OCX文件中。

---- 在Script 中主要设定控件当于显示值和被选择的日期，方法如下：

设定当天日期
//constructor for ole_1 returns long
this.object.value=today()
获得被选日期
//change for ole_1 returns long
sle_1.text=string (date(this.object.value))

---- 三、 制作动态按钮或动态图片的思路：

---- 用过Office 97的人都知道它的菜单具有动态感应功能，只要鼠标
光标指向某个快捷按钮时，此按钮即动态更换显示风格，在Pb6.5的菜单
中也具有这种功能，现在很多优秀的程序都通过这种原理在程序的主窗
口中放置一幅导航图，用户通过点击导航图即可直接进入各模块，界面
友善而简捷，深受用户欢迎。但是Pb6.5中的按钮对象或图片对象却没
有直接提供这种功能，要实现此功能用户必须通过自己编程实现或直
接利用第三方提供的OCX动态按钮控件实现，但是笔者试过很多控件在
PB6.5中都不能正常使用。于是在网上通过网友指点迷津，通过以下方
法使图片或按钮具备了动态感应功能，制作出漂亮的程序导航图：

先用绘图工具将你的程序简捷流程图(导航图)画好并保存为一个BMP文件；

将导航图中各个需要动态感应的部分剪切出来并保存为名个小的BMP文件；

在主窗口中放置完整的导航图，然后将各个小的BMP文件放置到完整导航
图中相应位置上方，默认图片的Border风格为False,这样从表面上基本
看不出原来完整导航图与叠加小图片后的分别；

为完整导航图和各个小图片增加用户自定义mousemove事件，在PB中对
应事件号Pbm_mousemove，各个小图片的Script 如下：
//mousemove for p_2 returns long
if this.border = false then
this.border = true
this.borderstyle = StyleRaised!

---- /*如果对各个小图片的多种显示风格对应的图片进行特效处理，
如颜色渐变、字体阴影、增强立体效果等，更能增强界面的美观效果,
只须动态改变感应后显示的图片即可。*/

//this.picturename="d:\author\toolbox3.bmp"
… //编写程序恢复其他小图片的默认显示风格
end if

---- 在各个小图片的Clicked事件中：

//clicked for p_2 returns long
this.borderstyle =StyleLowered!

---- 在完全的导航图和主窗口的mousemove事件中的Script如下：

//mousemove for w_3 or p_main returns long
if p_2.border = true then
p_2.picturename="d:\author\toolbox4.bmp" //恢复初始图片
p_2.border = FALSE
… //恢复其他小图片初始设置状态
end if

---- 至此，三个美化程序界面的方法就讲完了，希望能对各位网友有所帮助。

海洋之星文库-PowerBuilder资料:http://hxzhang.yeah.net
收集整理自 世界计算机日报 转载请勿删除此行。
返回主页


 数据窗口间的数据传送方法

本例主要介绍了在2个数据窗口间拷贝数据的几种方法，并在速度上进行了比较
  • 用RowsCopy函数
PowerBuilder提供了一组在同一数据窗口内或2个数据窗口间传送数据
的函数，但只有在接收和源DataWindow的列完全匹配时，才能对行
操作，这些函数有RowsCopy,RowsMove,RowsDiscard(本例只使用了
Rowscopy)，它们的语法如下： dwcontrol.RowsCopy ( startrow, endrow, copybuffer, targetdw, beforerow,
targetbuffer )dwcontrol.RowsMove ( startrow, endrow, movebuffer, targetdw, beforerow,
targetbuffer)dwcontrol.RowsDiscard ( startrow, endrow, buffer ) 我们不难理解它们的含义。例如dw_source.RowsCopy(1, il_Rows, Primary!, dw_dest, 1, Primary!)是
把dw_source数据窗口的数据拷贝到dw_dest数据窗口。
我们只要注意：如果是在2个不同数据窗口 上Copy或Move，目的数
据窗口上的行的状态为NewModified!但如果是在同一数据窗口的不同
缓冲区进行Move，PowerBuilder保存数据行的来源以及它们被改变的
状态，例如：如果从Primary!缓冲区把一些没有修改过的行Move
到delete!缓冲区，它们的状态标志为Delete!,然后如果又Move回Primary!缓冲区，则它们的状态又为NotModified! 当使用RwsMove时，它不会
象InsertRow函数一样，目的数据窗口内的DDDW进行自动检索，而必
须明确地对目的 数据窗口内的DDDW检索。 RowsCopy函数主要用于：
1.根据已有数据，生成新的数据行
2.通过拷贝选定数据行到一个临时数据窗口，然后打印这个临时数据窗口来打印所选行 RowsMove函数主要用于：
1.直接从Primary缓冲区中移出几行到delete缓冲区，而不必每次删除一行
2.从delete缓冲区移动数据行到Primary缓冲区来实现Undo功能
  • 用GetItem...系列函数与SetItem函数
GetItem系列函数用来获取数据窗口控件的指定缓冲区中相应数据类
型的数据，所获取的数据可以是原始缓冲区的，也可以是Primary,
delete或filter缓冲区的当前值，该系列的函数包括有：
GetItemDate
GetItemDateTime
GetItemNumber
GetItemDecimal
GetItemString
它们的语法为dwcontrol.GetItem...( row, column {,燿wbuffer, originalvalue})
其中OriginalValue取值为TRUE和FALSE，为TRUE时，取原始值，
FALSE时，取当前值。
column：可以是列名，也可以是列号 SetItem函数用来设置指定行列的数据 从上面的分析，我们就可以看出，使用这两个函数很不直观，取数
据时，我们必须根据列的类型来使用不同的 函数，这与面向对象编
程有点不符。不过现在可以直接使用点方法来访问数据，语法为：

dwcontrol.Object.Data.Buffer.WhichValue [RowRange]
dwcontrol.Object.Column.Buffer.WhichValue [RowRange]
其中RowRange可以是一个范围或Selected 例如：要取dw_1第2行的“salary”列的值，可以是这样：
dw_1.Object.salary.primary.original [ 2 ]或
dw_1.GetItemNumber(2,"salary",Primary!,True)

又如在本例的cb_getitem按钮脚本的
li_Data = dw_source.GetItemNumber(ll_Cnt, "line_id")
dw_dest.SetItem(ll_Cnt, "line_id", li_Data)
我们可以改用：
li_data=dw_source.object.line_id[ll_cnt]
dw_dest.object.line_id.[ll_cnt]=li_data 在本例的“结构数组”和“直接赋值”其实就是这种方法。
  • 用SaveAs和剪切板
用剪切板来传递数据，不管它额速度和占用内存的情况如何，我都认
为不是一个好主意，当然SaveAs的存在也 有它的理由，但我认为，
这应该不在DataWindow间传送数据，SaveAs有它的长处，可以用各种
格式来保存数据 ，对DataWindow它的语法为：
dwcontrol.SaveAs ( { filename, saveastype, colheading } )
其中saveastype可以为：
Clipboard! | Save to the clipboard
CSV! | Comma-separated values
dBASE2! | dBASE-II format
dBASE3! | dBASE-III format
DIF! | Data Interchange Format
Excel! | Microsoft Excel format
HTMLTable! | Text with HTML formatting that approximates the DataWindow layout
PSReport! | Powersoft Report (PSR) format
SQLInsert! | SQL syntax
SYLK! | Microsoft Multiplan format
Text! | (Default) Tab-separated columns with a return at the end of each row
WKS! | Lotus 1-2-3 format
WK1! | Lotus 1-2-3 format
WMF! | Windows Metafile format
下面是本例各种方法的运行时间统计
   SetRedraw(False) | SetRedraw(True)
GetItem/SetItem | 2.417 | 4.504
剪切板 | 0.851 | 0.961
结构数组 | 0.193 | 0.22
直接引用 | 0.069 | 0.082
用RowsCopy | 0.041 | 0.151


允许从空的非字符字段跳离
http://techinfo.sybase.com

下面这段DataWindow的ItemError事件脚本，将允许非字符字段为空并移到下一列。
它对所有的DataWindow有效。

string s

s = this.dwDescribe(this.GetColumnName()+".coltype")
//s = this.dwDescribe("#"+String(this.GetColumn())+".coltype")

CHOOSE CASE s
CASE "number"
IF Trim(this.GetText()) = "" THEN
int null_num
SetNull(null_num)
this.SetItem(this.GetRow(),this.GetColumn(),null_num)
this.SetActionCode(3)
END IF
CASE "date"
IF Trim(this.GetText()) = "" THEN
date null_date
SetNull(null_date)
this.SetItem(this.GetRow(),this.GetColumn(),null_date)
this.SetActionCode(3)
END IF
CASE "time"
IF Trim(this.GetText()) = "" THEN
time null_time
SetNull(null_time)
this.SetItem(this.GetRow(),this.GetColumn(),null_time)
this.SetActionCode(3)
END IF
CASE "datetime"
IF Trim(this.GetText()) = "" THEN
date null_datetime
SetNull(null_datetime)
this.SetItem(this.GetRow(),this.GetColumn(),null_datetime)
this.SetActionCode(3)
END IF
END CHOOSE


 在一个DataWindow中修改多个Table

摘自http://techinfo.sybase.com

下面的脚本示例了在一个DataWindow中修改它所对应的多个表，

int rc
string err

DataWindow建立了两个表Department和Employee的Join之上，它的Select语句为：

SELECT department.dept_id, department.dept_name,
employee.emp_id, employee.emp_fname,
employee.emp_lname FROM department, employee

在开始设置为对Department表可修改。所以，可以首先修改Department表，设置Reset标志，
以便对表的修改不改变数据窗口的行列状态(假设使用的是SetTransObject，以便对数据库的
修改不会自动提交)

rc = dw_1.Update(true,false)

//如果对Department表的修改成功，下一步就要修改另一个表

IF rc = 1 THEN

//首先，关掉对Department表的修改

dw_1.Modify(department_dept_name.Update = No)
dw_1.Modify(department_dept_id.Update = No)
dw_1.Modify(department_dept_id.Key = No)

//使Employee表成为新的可修改表

dw_1.dwModify(DataWindow.Table.UpdateTable = ~employee~)

dw_1.Modify(employee_emp_id.Update = Yes)
dw_1.Modify(employee_emp_fname.Update = Yes)
dw_1.Modify(employee_emp_lname.Update = Yes)
dw_1.Modify(employee_emp_id.Key = Yes)

然后修改Employee表

rc = dw_1.Update()
IF rc = 1 THEN
COMMIT USING SQLCA;
ELSE

MessageBox(Update of employee table failed, Rolling back changes to & department and employee)
ROLLBACK USING SQLCA;
END IF

如果还要修改别的表，重设修改标志

dw_1.Modify(department_dept_name.Update = Yes)
dw_1.Modify(department_dept_id.Update = Yes)
dw_1.Modify(department_dept_id.Key = Yes)
dw_1.Modify(DataWindow.Table.UpdateTable = ~department~)

dw_1.Modify(employee_emp_id.Update = No)
dw_1.Modify(employee_emp_fname.Update = No)
dw_1.Modify(employee_emp_lname.Update = No)
dw_1.Modify(employee_emp_id.Key = No)

ELSE
MessageBox(Update of department table failed, Rolling back changes to department)
ROLLBACK USING SQLCA;
END IF


 用InstallShield制作专业Windows安装软件[1999-06-01]

苏贵洋

    InstallShield可以说是软件安装的最好制作工具。其简单的操作、完全可视化的界面、方便的调试功能、多平台（Windows3.1, Windows95, WindowsNT3.5, Windows NT4.0 ）语言（中文、各种西文）的支持使它几乎成为了安装制作的标准。难怪微软在它的拳头产品Visual C++4.X中开始,就以捆绑的形式包括了InstallShield3。而今，随着日月的推移，InstallShield更是羽翼丰满，又陆续推出了InstallShield Professional 5.X(包括East Edition,West Edition)和InstallShield 6.0，功能更为强大，操作更为简便。对于Shareware软件的制作者，包装上一个漂亮的安装程序，实在是非常必要。本文简要探讨一下用InstallShield制作安装软件的方法，希望对感兴趣的朋友有所帮助。

    在InstallShield的各种最新版本中，其专门制作的East Edition，可应用于汉语(GB、BIG5)、英语、印度尼西亚语、日语、韩语、俄语、土耳其语、泰国语等多语种。本文便以InstallShield East Edition 5.1为探讨对象进行讨论。InstallShield的其它语种版本的操作和East Edition几乎完全相同。

    进入InstallShield East Edition 5.1后便是两个“向导”面对你。

    习惯用VB的朋友可以用VB5 Project Wizard,它需要你的系统中安装了VB5。

    更为常用的是Project Wizard,它可以定制更为普便适用的安装程序。进入Project Wizard后如图1：在第一栏中写入你的程序的名字，第二栏中写入公司的名字，在第三栏中选择开发程序使用的根据，第四栏中选择程序的种类，最后一栏中填入程序的可执行文件的完整路径和名字。单击下一步，进入选择定制对话框：安装自己的喜好选择希望安装过程进行时出现的对话框，按Priview键可以预览一下相应对话框的效果。接下来是选择要安装目标平台。也即程序将被运行在什么平台之上。InstallShield共提供了四种平台供选择（Win31, win95, WinNT3.5, Windows NT4.0 ）。然后是多语种支持,也就是安装时可以提供的语言支持。再向下依次是安装类型选择、指定构成成份、指定文件组，最后是所有选项的概括。完成选择后便可得到你自己定做的Project，进入InstallShield的可视化编程环境。

    下面让我们一步一步地完成我们的制作。

    整个InstallShield的可视化程度很高。支持鼠标右键的操作，在各个位置的鼠标右键可激活各种功能；编译、调试方便，简直是VC＋＋这种大型程序的简写本，却又比VC＋＋方便了许多。

    一、在左面的选项中选择Scripts。

    这是全部制作的核心部分，也是我们所最应集中精力攻克的小小关口。当然不更改Scripts中的源程序也能制作一个标准的安装程序,但如果能够控制Scripts的编程,整个安装程序的制作水准就可以达到一个新的高度,这也许是您与普通制作者区别的开始吧。其实InstallShield的Scripts非常类似于C和C＋＋的程序编写，相信对大多数程序员来说是非常容易掌握的。而对于没有什么C和C＋＋的基础的朋友来讲，看完这篇文章后，相信你也能很好地去制作自己的Scripts，因为它具有相当程度的重复性，更容易被“依葫芦画瓢”。

    Scripts的布局同C语言非常类似。首先是头文件的包含，紧接着是字符串的定义、函数原形的说明、全局变量的说明。你可以做的是在此加上你的函数说明、全局变量的说明。

    Scripts中遵循C的规则，同样有局部变量和定义。遵循C＋＋的注释规则，可以用// 注释内容来进行单行注释，也可以用/* 注释内容 */来注释一块语句。所有的关键字、宏定义都以不同颜色的字体标出，非常直观。Scripts 的编程遵循结构化编程的思想，提供了大量类似Windows API的函数库供调用，每个宏定义都有详细的说明，每个函数除了详细地说明，解释每个变量的意义外，还都附有相应的例子，参考这些说明和例子，可以让新手在非常短的时间内上路。对用VC＋＋开发过Windows程序的朋友来讲，简直就是一种6＝2×3的转变而已，可以非常轻松地掌握。

    在程序的最前段，是program…………………endprogram，它相当于C中的main() {……}主函数。后面是一个个子函数体的程序，排列顺序是按调用顺序排列。全部函数都非常简单易懂，并且一般不需太大改动。下面将几个常用、经常改动的函数加以说明。

    （1） function SetupFolders()

    function SetupFolders()

    NUMBER nResult;

    STRING szFolderDir;

    begin

    szProgramFolder = FOLDER_PROGRAMS^

    SHELL_OBJECT_FOLDER; //建立程序组或快捷方式的位置

szItemName = "眼部图象多功能处理系统"; //应用程序名称

szProgram = TARGETDIR^'eye95.exe'; //可执行文件名

szParam = "";

    LongPathToQuote (szProgram, TRUE);//合并路径

    LongPathToShortPath (szParam);//转为短路径,专为Win31和WinNT3.5而用

    szCommandLine = szProgram + " " + szParam; //生成命令行

    szWorkingDir = TARGETDIR;//工作路径

    szIconPath = "";

    nIcon = 0;

    szShortCutKey = "";

    nFlag = REPLACE;

    AddFolderIcon (szProgramFolder, szItemName,

    szCommandLine,

    szWorkingDir, szIconPath, nIcon,

    szShortCutKey, nFlag);

    szProgramFolder = FOLDER_DESKTOP;

    LongPathToQuote(szCommandLine, TRUE);

if(SprintfBox( OK|CANCEL,"请选择：","%s","你想在桌面上创建快捷方式吗?")==6)

    then

    AddFolderIcon(szProgramFolder,szItemName,szCommand

    Line,

    szWorkingDir,szIconPath,nIcon,szShortCutKey,

    nFlag);

endif;

szProgramFolder = FOLDER_PROGRAMS^ SHELL_OBJECT_FOLDER;

szItemName="readme";

szProgram = WINDIR ^ "notepad.exe";

szParam = TARGETDIR^"readme.txt";

LongPathToQuote (szProgram, TRUE);

LongPathToShortPath (szParam);

szCommandLine = szProgram + " " + szParam;

AddFolderIcon(szProgramFolder,szItemName,

    szCommandLine ,

    szWorkingDir, szIconPath, 0,

    szShortCutKey,nFlag );

    nResult = CreateShellObjects( "" );

    return nResult;

    end;

    其实这段程序说白了是对AddFolderIcon的一个应用而已。应用AddFolderIcon来在开始选单中添加程序组、程序项或在桌面桌面添加快捷方式等等。所以首先读一下AddFolderIcon的说明非常必要。它共有八个入口参数。第一个参数szProgramFolder是确定要在Win95中添加选单的位置（路径），例如

    "C:\\WINDOWS\\STARTMENU\\PROGRAMS\\ACCESSORIES\\GAMES"。

    而在Windows95和WinNT4.0中，还可以用系统的宏定义：FOLDER_DESKTOP(添加在桌面上)、FOLDER_DESKTOP(添加在开始选单中)、FOLDER_PROGRAMS(添加在开始－〉程序选单中)等。第二个参数为szItemName，即添加的图标的名称（应用程序的名称）。第三个参数szCommandLine相对繁琐一些。它为了指定可执行程序和它的命令行，或者在子目录下的全路径（仅适用于Windows95&winNT4.0）。szWorkingDir为工作路径，可为空：“”，或为TARGETDIR（目标路径）。szIconPath为图标路径，一般设为空：“”。nIcon为图标序数，即若应用程序有若干个图标，想显示那个，依次为0，1，2……。

    szShortCutKey用来定义快捷键。nFlag用来指定图标的外观。如果在路径中包含长路径名，则必需用‘’或“”来包含，然后用LongPathToQuote函数来合并。当用于16位的Win31和WinNT3.5时，还应再调用一下LongPathToShortPath进行转换。如此便可创建程序组、程序项或快捷方式了。太麻烦了？没关系，如果您认为这一段操作实在麻烦，您完全可以略过，到Resource中去完成，详情请往后看。

    （2）function SetupScreen()

    begin

    Enable( FULLWINDOWMODE );

    Enable( INDVFILESTATUS );

    SetTitle( "眼科多功能处理系统安装程序", 28,YELLOW );//修改字体和颜色

    SetTitle( "眼科多功能处理系统", 0, BACKGROUNDCAPTION ); // 标题行的字体和颜色

    Enable( BACKGROUND );

    Delay( 1 );

    end;

    这儿主要是SetTitle (szString, nPointSize, nColor)函数的应用。

    三个参数分别代表了所写字符串的内容、字体大小、文字颜色。

    （3）function DialogShowSdWelcome()

    NUMBER nResult;

    STRING szTitle, szMsg;

    begin

    szTitle = "欢迎";

    szMsg = "欢迎您光临 眼科多功能处理系统 安装程序！";

    nResult = SdWelcome( szTitle, szMsg );

    return nResult;

    end;

    在这个函数里写入您想制作程序的标题。

    （4）function SetupRegistry()

    NUMBER nResult;

STRING szKey, szNumName, szNumValue, svNumValue;

    NUMBER nType, nSize;

    begin

    szKey="Robert\\RobertSoftware\\Spirit\\Version";//自己的主键

    szNumName = "TestValue";//键名

    szNumValue = "12345";//键值

    nType = REGDB_NUMBER;//类型

    nSize = -1;//指定大小

    RegDBSetKeyValueEx(szKey, szNumName, nType, szNumValue,nSize) ;//创建

    nResult = CreateRegistrySet( "" );

    return nResult;

    end;

    通过这种方法修改注册表，再辅以一些算法，可以投入很小但起到很好的加密程序的作用，您不妨一试！ ?

    （5）function DialogShowSdFinishReboot()

    NUMBER nResult, nDefOptions;

    STRING szTitle, szMsg1, szMsg2, szOption1, szOption2;

    NUMBER bOpt1, bOpt2;begin

    if (!BATCH_INSTALL) then

     bOpt1 = FALSE;//不选

     bOpt2 = FALSE;//不选

     szMsg1 = "%P 安装结束！\n";//%P代表安装程序名字。

szMsg2 = "按结束键结束 %p 安装。";//提示信息

szOption1 = "我想看自述文件。";

     szOption2 = "我想现在就运行程序。";

     nResult = SdFinish( szTitle, szMsg1, szMsg2,

    szOption1, szOption2, bOpt1, bOpt2 );

if(bOpt1) then

     LaunchAppAndWait(WINDIR^"Notepad.exe",TARGETDIR^"readme.txt",WAIT);//如果选中，调用Windows的笔记本载入自述文件。

    endif; if(bOpt2) then

     LaunchApp(TARGETDIR^'eye95.exe',"");//如选中，立刻执行应用程序

    endif;

     return 0;

    endif;

    nDefOptions = SYS_BOOTMACHINE;

    szTitle = "";

    szMsg1 = "";

    szMsg2 = "";

    nResult = SdFinishReboot( szTitle, szMsg1,

    nDefOptions, szMsg2, 0 );//由此控制决定是否重新启动计算机。

    return nResult;

    end;

    这一段程序便是安装到最后，可以提示用户是否看自述文件、是否立刻启动程序运行、是否重新启动计算机。

    Scripts最需要改动的地方可能就是这五个地方。其它则根据制作者自己的需要来做相应修改。

    二、修改File Groups

    双击相应的Examples Files、Help Files、Program DLLs、Program Executable Files、Shared Dlls，在相应的Links中，单击右键，可以插入一组文件、一个文件夹或一个文件。而在Examples Files、Help Files、Program DLLs、Program Executable Files、Shared Dlls中分别加入文件是为了在程序安装时的选项安装。更为简便的方法是在Tools中启动Launch Explorer，然后把所需的文件或文件夹拖拉到右面空白处即可。

    三、修改Components

    单击Program Files、Example Files、Help Files或Shared Dlls，在右面的表格中双击Include File Groups，在弹出的属性对话框里单击Add…，加入相应的文件组。

    四、修改Setup Types

    单击Compact、Typical或Custom，在右面的表格中选择各自包括的组成成份。

    注：有了二、三、四项的操作，才能完成安装时的“典型安装、简洁安装和定制安装”的选择。

    五、修改Setup Files

    在Splash Screen-〉Language Independent中你会发现一个Setup.bmp文件，这个是系统提供给你的安装开始时的闪烁画面。你可以用你的封面替代这个setup.bmp。在Language Independent-〉Operating System Independent中，你会发现license.txt和infolist.txt。你可以将你的应用程序的说明、版权信息写入相应文件。

    六、修改Resource

    在Register Entires中，你可以添加你的键值。对Register Entires单击鼠标右键，单击New Entry,便可在右面的Register Set中加入自己的键值。

    在Shell Objects中，有Explorer Shell和Program Manager Shell两个分类，分别是为了windows95、WinNT4.0和win31、winNT3.5设计的。在Explorer Shell中，又有Desktop、Start Menu、Programs、Startup的层次之分。你可以在任一层单击右键，插入您所需要加的快捷方式或文件夹。

    单击快捷方式或文件夹，双击右面的表格选项，依次填入空白项，那么在您理想的目的地的文件夹或快捷方式便建好了。不要着急去按下Run Setup工具按钮，否则您会发现您运行后得到的并不完全是您所设计的！这是因为您少了：

    七、运行Media Wizard

    在Media栏，单击Media Build Wizard，按照提示一步一步执行，直至结束（而实际上您仅需连续按“下一步”按钮）。您完全可以再此基础上加上更多的更精彩的小花招，如：加密、安装拷贝时的不同的背景变换、制成一个自解包的安装文件、做一个带时间限制的试用版安装等等，InstallShield都可以满足您的需要。但这些小花招,基本都需要您对Scripts的熟练掌握,这些都是用简洁方式所不能控制的。

    好了，您可以Run Setup了。怎么样？够不够专业？酷不酷？可不要把嘴乐歪了！


用PB5设计串口通讯程序
石家庄铁道学院计算中心
王学军

---- PowerBuilder5.0是一种面向对象的、具有可视图形界面的、快速
的交互式开发工具，它通过不同数据库采用各自的专用接口或通过ODBC
接口，同时支持多种关系数据库系统、支持多文档界面（MDI）、对象嵌
入与链接（OLE）、动态数据交换（DDE）。利用其独特的数据窗口对象，
无须编写SQL语句，便可直接对数据库进行查询、修改、插入、删除、浏
览、打印，以多种文件格式打开和存储数据，因此深受广大用户的欢迎。
在实际应用过程中经常遇到PowerBuilder与其它设备通过串口进行通讯
的程序设计如IC卡应用系统中需通过串口与IC卡读写器进行通讯；在控制
系统中，需通过串口与下位机进行通讯。下面介绍PowerBuilder5.0中常
用的两种串口通讯设计方法。

一、 调用Windows SDK函数进行串口通讯
---- 1、Windows SDK函数介绍
OpenComm
int OpenComm(LpszDevControl,
CbInQueue,cbOutQueue)
LPCSTR lpszdevcontro
l 设备控制信息的地址
UINT CbInQueue
接受队列的大小（以字节为单位）
UINT cbOutQueue
发送队列的大小
返回：如调用成功，返回值标示此打开的设备否则其返回值小于0
·CloseComm
Int CloseComm(idComDev)
Int idComDey 要关闭的设备
返回值：调用成功返回0，否则返回负值
·WriteComm
int WriteComm(idComDev,lpvBuf,cbWrite)
int idComDev 通讯设备标示符
const void FAR * lpvBuf 数据缓存区地址
int ,cbWrite 要写的长度
返回值：如果成功返回所写的字节数，否则返回值小于0
·ReadComm
Int ReadComm(idComDev,lpvBuf,cbRead)
int idComDev 通讯设备标示符
const void FAR * lpvBuf 数据缓存区地址
int cbRead 要读的字节数
返回值：如果成功返回所读的字节数，否则返回值小于0
·FlushComm
Int FluseComm(idComDev,fnQueue)
Int idComDev 通讯设备标示符
Int fnQueue 要刷新的队列
返回值：成功时返回0，否则返回为负

---- 2、示例：
---- 图一(略)为一人事管理系统中的查询窗口。如果想与被查询人拨打
电话，可输入该人姓名，在输入过程中，数据窗口会显示满足要求的所
有人姓名，双击该行即将该人电话号码显示于单行编辑窗内，然后按
“拨号”按钮即可。

---- 程序设计如下：

---- 在窗口设计菜单中选取：Declare菜单?Declare External Function 输入：

Function int OpenComm(String Comm,
Uint Inqueue,Uint Outqueue)Library
“USER.EXE”
Function int CloseComm(int lpt)Library
“USER.EXE”
Function int WriteComm(int lpt,String
buf,int Size) Library “USER.EXE”
Function int FlushComm(int lpt,
int no_que)Library “USER.EXE”

“拨号”按钮的”Clicked” 事件编程如下：
int ret
String port_2,ph_code
port_2=”Com2”
//取单行编辑框内容，并用atdt拨号
ph_code=”atdt”+sle_code+”~r~n”
//打开串口
ret=OpenComm(port_2,128,128)
//拨号开始
FlushComm(ret,0)
FlushComm(ret,1)
WriteComm(ret,ph_code,len(ph_code))
WriteComm(ret,”~r~n”,2)
CloseComm(ret)

二、 利用Microsoft公司提供的通讯控件
---- 利用Microsoft公司提供的通讯控件(Mscomm.vbx,用于16位通讯
程序、Mscomm32.ocx用于开发32位通讯程序)可方便实现串口通讯
---- 1、 常用属性

CommPort:设置或返回串行端口号，缺省为1。

Setting:设置或返回串行端的波特率、奇偶校验位、 数据位数、停止位。
缺省值为"9600，N，8，1"。

PortOpen：设置它为True则打开端口，设置它为False则关闭端口。

InBufferSize:设置或返回接收缓冲区的大小，缺省为1024 字 节。

InBufferCount:返回接收缓冲区内的等代读取的字节个数，设属性为0
清除接收缓冲区。

OutBufferSize:设置或返回发送缓冲区，缺省为512字节。

OutBufferCounter:返回发送缓冲区内等待发送的字符数，可用0来清空缓冲区。

Output:向发送缓冲区传送数据

RThreshold: 该属性确定当接收缓冲区内字节个数达或超过该值后就产生代码为
ComEventReceive的OnComm事件，设置位0，则不产生OnComm事件。

CommEvent:有通讯错误或事件发生时产生OnComm事件，CommEvent事件的值可
以用来确定引发OnComm事件的具体的错误或事件常用的有以下几种（更详细的
资料可参考MSComm控件帮助文件）
ComEventbreak：接受到中断信号
ComEventFrame：硬件检测到帧错误。
ComEventRxOver：接收缓冲区溢出。
ComEventTxFull：发送缓冲区溢出。
ComEventReceive：接受到规定的字符数。

InputLen:设置或返回接收缓冲区内用Input读入的个数。若取0，则读
取整个缓冲区的内容。

Input:返回并删除接受缓冲区的数据。
---- 2、 示例：
---- 下面举一接收程序示例：制作一通讯接收窗口，选择菜单
Controls→OLE→Insert Control→Microsoft Communications Control，

---- 在窗口中出现通信控件图标单击鼠标右键，选择OCX Properties设置
常用属性，也可在程序中进行设置

窗口的Open事件程序如下
ole_1.object.CommPort=2
ole_1.object.Settings="9600,N,8,1"
ole_1.object.PortOpen=ture
ole_1.object.Rthreshold=10
ole_1.object..Inputlen=0
.
.
通信控件的oncomm事件的程序如下
choose case ole_1.object.CommEvent
case comEventBreak //接收到中断信号
case comEventCDTO //处理CD超时
.
.
.
case comEvReceive
sle_1.text=ole_1.object.Input
.
.
end choose

---- 以上是一个基本的例子，在实际应用过程中应根据不同的情况进
行不同的设计，如波特率、帧格式等。

---- 总之，利用以上方法可以方便的在PowerBuilder中进行串口通讯，并
对接收的数据利用PowerBuilder进行数据处理。

海洋之星文库-PowerBuilder资料:http://hxzhang.yeah.net
收集整理自 世界计算机日报 转载请勿删除此行。


 PowerBuilder 5.0 InstallBuilder 使用技巧 }作者：WJK
Install Builder    使用心得
我觉得Install Builder实在是非常不好用,而且有许多问题,或许这是为什麽在PowerBuilder
   6.0里,以InstallShield 5.0 取代 Install Builder的原因。
鉴於有许多人需要使用Install    Builder,因此提供小弟以前使用上的一些心得。
首先,小弟提供一个Install    Builder的 sn_cg.cfg 档,先在Install    Builder打开此cfg档(32 bit应用程式)

在Install Builder里,分为两个主要部分,一个是有关应用程式本身的设定,也就是您现在在画
   面上所看到的base Compoment,另一个是PowerBuilder提供的资源,例如:资料库介面,shall
   dynamic link library等,此部份称为Reusable Compoment
Base Compoment:

设定此部份时,你必须先了解,您的应用程式本身使用的资源有哪些,因此产生新的cfg後,你
   必须先设定你的compoment
component descrption |
default destination path | 预定安装的路径
add to path | 指定哪些执行档设定registry key叁考对话盒的HELP
Files | 此component要安装的档案
INI settings | 此component要设定的ini
Registry Enters | 此component要设定的registry key
Extensions | 当要安装的档案已经存在时的应对方式
Addtional Options | 设定此componet於安装过程是否有描述,可供选择等
Billboards | 安装时所显示的背景
其中最重要的是Registry    Enters的设定(for 95,NT),INI(for w31)
通常需要设定的有两项,一是搜寻路径,一是ODBC资料源
应用程式搜寻路径请叁考1-3行,有3个执行档的设定
ODBC资料源设定请叁考4-11行的设定,有2个SQL    anywhere资料源的设定,
如果您的工作平台已有此ODBC资料源的设定,您可由选单EDIT-ODBC    Entrys-From
   Registry从您的平台上复制,如果您不是使用SQL anywhere,那麽这些Regstry key会有不同。
Reusable Compoment:

设定此部份,你可从选单Actions-Default    Reusable Component,此时会打开一有许多已定义好
   的Component的视窗,小弟建议不要更改这些Component的设定,除非你已经很了解Install
   Builder,否则...
如果你已经变动过,那你只有重装Install    Builder才可还回原设定,或COPY没有变动过
   的instbldr.ini。
此部份分为4个主要的Component
 reuse1    PowerBuilder RunTime shall dynamic link library
这个Component对於所有的PB5.0应用程式是必要的,其路径必须在应用用程式搜寻路径里,
   如果user在安装过程中选短档名则会以Default Destination Path的路径安装,如以选长档名,则
   以Additional Option中Long Directory Path的路径安装,因此在应用程式搜寻路径中的设定方
   式要很小心,如果您的应用程式在执行时出现启动错误,或找不到dll,那就可能您的应用程
   式搜寻路径没包含此路径了,您可执行作业系统的regedit来检查您的Registry key的设定是
   否正确。还有一种方式就是把这些share dll copy到您的应用程式路径,或作业系统路径也可
   ,但是小弟觉得这是一个不好的做法
 reuse2    SQL Anywhere(32bit)
此部份是SQL anywhere资料库系统,    在此 , 小弟提出一个地方值得注意,那就是在 Files 中各
   位可以发觉并没有包含dbeng50.exe这个资料库引擎 , 而是以 rtdsk50.exe 来当成资料库引擎 ,
   这是因为智慧财产权 , 所以您必须注意一个陷阱, 如果您的开发平台上 , 您所设定的ODBC
   是以dbeng50.exe来启动资料库, 那麽您在产生此安装程式时 , 必须要修改上面的例子
   在Registry Entry其中一行,dbeng50 改成rtdsk50, 至於 dbeng50 与    rtdsk50 差异 , 请叁考 PB 的
   使用手册。
否则,您就必须加入dbeng50.exe(小心智慧财产权)
 resue3    ODBC Connectivity Drivers
此部份是ODBC介面驱动程式,设定PB所提供的ODBC    driver,如果您的应用程式使用ODBC
   资料源,您必须确定是否已安装了连接的介面驱动程,以及正确的registry key,INI设定
 resue4 Native    Database Drivers
┅┅┅┅┅
以上是小弟觉得的必须注意的几个地方,希望对各位有所帮助


PowerBuilder与外界的交互方式

天顺公司软件部 陈俊明编写

一.概述

一般来说，如果系统是一个纯粹的C/S模式的MIS应用系统，则与外部程序的交互比较少，PB本身提供的功能就足够实现所有的功能；但有的时候，当对系统的界面，性能要求比较严，或是在系统中还有很多其它的功能时，比如说办公自动化系统，此时需要比较多的与外界（或是外部程序）通迅，比如要发送EMAIL等等。

就PowerBuilder来说，它是一个开放式的开发平台，提供了多种手段来扩展它的功能，归纳起来，主要有：

  • WIN API调用
  • DDE（动态数据交换）
  • OLE（动态数据链接）
  • OCX（自定义控件）

下面我们一一介绍各种方法

二.WIN API调用

在一个系统中，有的功能用PB是实现不了或是不好实现的，比如对数据的压缩，而在WIN API中却有相应的函数调用或是我们用其它语言实现起来比较方便，此时我们就要用到API的调用。

在讲解之前，我们来看一下DDL文件的结构。在DDL中，所有的函数可分为输出和非输出两大类，输出的函数可以在外部调用，而非输出的函数则只能在DDL内部调用(WIN95中也可调用)。一般的，一个DDL包括以下几个部份：

  • 名为LibEntry的汇编语言模块
  • 名为LibMain()的非输出启动模块
  • Exported Programmer-Defined DLL函数(输出函数）
  • 可选的Nonexported Programmer-Defined DLL函数（非输出函数）

当系统装载或卸载DDL时，都要运行一次LibMain()和WEP()函数。LibMain()执行初始化工作，而WEP()执行所要求的清理工作。

DDL创建的方法有很多种，当然您可直接用WIN API函数和汇编语言编写，但一般不这样，象在Visual C++中就提供了建立DLL的功能，还有很多其它方法，在这就不一一说明了。

在创建了DLL后，怎样才能在PB中访问其中的函数呢？您需要做以下几步工作：

  • 将DLL文件拷贝到自已的DOS路径下或是Windows的路径下
  • 在调用以前声明您所要调用的函数：FUNCTION <函数说明> Library <DDL文件名>
  • 在需要的时候调用

在PB中，对外部函数的声明有两种情况，语法如下：

语法一：有返回值的函数

{ access } FUNCTION returndatatype name ( { { REF } datatype1 arg1, ..., { REF } datatypen argn } ) LIBRARY "libname" ALIAS FOR "extname"
语法二：无返回值的函数

{ access } SUBROUTINE name ( { { REF } datatype1 arg1, ..., { REF } datatypen argn} ) LIBRARY "libname" ALIAS FOR "extname"

参数的传递：

通过值传递的语法： ParameterDataType Parameter
通过引用传递的语法： REF ParameterDataType Parameter

实例：

取得用户名：
FUNCTION BOOLEAN GetUserNameA(REF STRING uName,REF ULONG sLength) &
LIBRARY "advapi32.DLL"
STRING UserName,sLength
sLength = 255
UserName = SPACE(255) //分配空间
GetUserNameA(UserName,sLength)

动态触发键盘的键值(本例为TAB)：
#DEFINE TABKeyCode 7
SUBROUTINE Keybd_Event(INT bVk,INT bScan,INT dwFlags,INT dwExtraInfo) &
LIBRARY "user32.dll"
Keybd_Event(TABKeyCode,0,0,0) //按下TAB
Keybd_Event(TABKeyCode,0,2,0) //放开TAB

三.DDE(动态数据交换)

DDE其实质是一种Windows应用程序之间的交换数据的机制，它是通过其享内存来实现的。通常它通过以下方式来实现：
1.DDE客户应用程序与DDE服务器应用程序建立会话
2.DDE客户应用程序向DDE服务器应用程序提出请求
3.DDE服务器应用程序向DDE客户应用程序提供数据和服务

它的工作方式有三种:
1.冷连接:只有当客户应用程序请求时,服务器应用程序才发送数据
2.温连接:建立了会话后,如果服务器应用程序的数据更新了,将发送一个消息给客户应用程序
3.热连接:如果服务器应用程序的数据更新了,将自动将数据发送给客户应用程序

PowerBuilder中的对应事件:
HotLinkAlarm: 在DDE服务器应用发送了数据时,应用程序接收到后触发
RemoteExec: 当一个DDE客户发送了一条命令时触发
RemoteHotLinkStart: 当DDE客户开始一个热连接时触发
RemoteHotLinkStop: 当DDE客户结束一个热连接时触发
RemoteRequest:: 当DDE客户请求数据时触发
RemoteSend: 当DDE客户已发送了数据时触发
PowerBuilder中的函数:
CloseChannel: 关闭由OpenChannel打开的与DDE服务器应用的通道
ExecRemote: 请求DDE服务器运行指令
GetDataDDE: 从热连接的DDE服务器应用中得到数据并放到定义好的串中
GetDataDDEOrigin: 从已建立的热连接中确定数据来源
GetRemote: 向服务器请求数据
OpenChannel: 打开一个通道
RespondRemote: 通知DDE服务器应用,所收到的数据是否可接收
SetRemote: 请求DDE服务器应用设置一些特定项
StartHotLink: 与DDE服务器应用建立热连接
StopHotLink: 结束与DDE服务器应用的热连接
GetCommandDDE: 得到由DDE客户发送的指令
GetCommandDDEOrigin: 得到DDE客户指令的源
StartDataDDE: 发送特定数据到DDE客户应用程序
StartServerDDE: 开始启动服务器应用
StopServerDDE: 停止服务器应用

实现步骤如下:
1.建立连接
2.发送请求或是处理请求
3.发送数据或是接收数据
4.结束连接

四.OLE和OCX(对象连接与嵌入)

OLE与OCX的应用主要是源于将多个应用信息源组合到一起的目标，在PowerBuilder的实际应用中,OCX的应用比较多，因为它能更好的与PowerBuilder的应用程序相结合，从而实现PowerBuilder不好实现或是很难实现的功能。实际上，PowerBuilder本身以及Visual Basic等等其它程序提供了很多处现特定功能的OCX控件。通过它们，我们可以非常容易的实现一些PowerBuilder本身不能实现的功能。

在PowerBuilder中，如果要调用OCX控件的话，首先得将OCX控件在系统中注册才能使用。在PowerBuilder中使用OCX控件非常的容易，只需要将OCS控件加入到程序中，然后直接调用其功能即可。其功能请参考各OCX控件的相关资料。


与Excel通讯
Author: Adam Kierce

如果要传数据到Excel，可以用OLE2.0，用它实现非常简单。OLE Automatic让
你可以直接控制整个Excel应用，在Excel中可以做的，在PB中都可做到。
下面是一些主要步骤：

首先，建立一个与Excel通讯的Object。实现如下：

OLEObject ExcelServer
integer ExcelOK

ExcelServer = CREATE OLEObject
ExcelOK = ExcelServer.ConnectToNewObject( "excel.application" )

...(应该检查返回值，以确保已成功地连接到了Excel)

现在，就可以使用(OLEObject) ExcelServer来直接访问Excel对象(包括所有的Property
和Mothod)，注意在'ExcelServer.'后面接的是Excel的内部对象。如果你需要一个
property或method，但不知它的名字，可以查看Excel VBA帮助文件。
下面是一些例子：

ExcelServer.Workbooks.Open("FullFileName")
ExcelServer.ActiveWorkBook.Sheets("SheetName").Select
ExcelServer.ActiveSheet.Range("FiscalYear").value = "My value"
ExcelServer.ActiveSheet.Cells(1,1).value = 7.23
ExcelServer.Application.Run("MacroName")
ExcelServer.Application.Visible = TRUE
ExcelServer.DisconnectObject()

也可以从Excel提取信息，假设你想知道当前活动Sheet的A1单元(Cell)的值，
你可以使用如下代码：

ANY MyValue

MyValue = ExcelServer.ActiveSheet.Cells(1,1).value

CHOOSE CASE ClassName(MyValue)

CASE 'string'
//assign to string

//other data types here
......
END CASE

注意ANY变量的用法。返回的Excel对象的method/property可能是各种各样的数据
类型，所以除非你绝对确定返回类型(例如一个Cell对象的ColumnWidth属性一般都
返回的是integer变量)，你一般都要使用ANY变量，然后再用ClassName函数来确定
对应的值。


 关于PB应用程序的2000年问题 广东核电合营有限公司 步建华

---- 1． PB应用程序有2000问题 ---- PB是我国近年来开发C/S应用程序使用最多的工具之一，包括相当多的大型应用系统， 因此，清理2000年问题PB应用程序是重点。用PB开发的应用程序有没有2000年问题首先要 看PB本身有没有2000年问题？按Sybase提供的资料，PB5.0.04，PB6.0没有2000年问题 （certified,compliant），换言之，PB5.0及以前的版本有2000年问题。前几年用PB5.0开 发的应用程序必须进行升级或用其它程序替代。 可以使用PB5.0的升级版PB5.0.04，或用PB6.0 及以上的版本升级原有的软件。 ---- 那么，用PB5.0.04，PB6.0 升级或开发的应用程序就没有2000年问题了？ 回答同样是否。问题不在PB本身，而在开发人员， 因为PB有个隐含的内部世纪日期规则和有个长短日期的表示方法，而以往的应用程序日期的年 份一般都是取2位。如果使用2位年就很容易出错，可以造成2000年问题。 ---- 本文根据Sybase的资料分析和讨论PB应用程序由于2位年可能造成程序逻辑和日期显示的 2000年问题。 ---- 2. 2位年是造成PB应用程序2000年问题的主要原因 ---- 由于思维的习惯，无能是开发人员还是用户都喜欢用2位年份，并且PB 的缺省年份是2位，开发人员使用2位年份便理所当然。为了识别这2位年份属于 哪个世纪，PB设计了一个隐含的世纪识别规则。可是开发人员在使用2位年份的 时候常不能全部顾及区分世纪，不能正确运用PB的隐含世纪规则，于是造成PB应 用程序的2000年问题。PB的隐含世纪规则是： 当2位年份是50-99时，PB认为是19xx年，当2位年份是0-49时，PB认为是20xx年。 ---- 3．可能出现2000年问题的方面 ---- 可以从5个角度考察PB应用程序可能出现2000年问题的方面。 ---- 缺省的2位年日期格式问题 ---- PB日期数据类型是4位年，包括数据表内缺省定义的日期数据类型。但是PB在各种场合都 使用缺省的2位年日期格式，包括Window控件，DataWindow的列，计算列及图形等。为了避 免2000年问题的发生，我们不应直接使用缺省日期格式。开发人员应当手工全部将2位年调整 为4位年。下面分别说明： ---- Window控件的缺省日期格式年份是2位（mm/dd/yy），例如EditMask, 其Mask 若选Type为date, 它的属性便是00/00/00。为保证4位年份，必须手工将Mask的日期 置为4位年份， 例如：yyyy/mm/dd，而为 0000/00/00。 ---- DataWindow的列和计算列的日期采用的是short date格式（mm/dd/yy）。 在DateWindow日期列的format中，缺省日期格式是[general]，[general]的格式是对应 这一列的数据类型的。必须手工将其修改为4位的long date格式， 即yyyy/mm/dd, 可以用test来测试更改是否成功。 ---- 在有图形的DataWindow中，日期的缺省格式也是[general]，也就是00/00/00形式。 如果取X轴为日期轴，最为简单的方法是， 在data 页的category 栏中用string()函数转换，如下： string( bonus_date,"yyyy/mm/dd") ---- 其中bonus_date是数据表的日期字段，可以使X轴的日期刻度为4位年。 这个方法比较简单有效，因为PB在图形部分对日期format的更改操作不理想。 ---- 日期字符串函数问题 ---- date()是开发人员最常用的日期字符串函数， Date（date_string） ---- 其中，date_string 是日期字符串。在使用date_string字符串时一定要清楚这个字符串 日期是落在哪个世纪。如果是使用是2位年份表示日期，PB会根据其内部隐含世纪规则自动地 判断是那个世纪。例如，如果开发人员心目中某个员工的出生日期是“1949/12/31”，那么 应该写成： Date（“1949/12/31”） ---- 如果写成： Date(“49/12/31”) ---- 那么PB认为是“2049/12/31”，相差100年，两个不同世纪。 ---- Date()函数另外一个用途时用来判断某个日期字符串是否有效，如果是无效日期， PB设计的Date()函数返回值是“1900-01-01”。“1900-01-01”是个真实日期，不注意 正确使用这个返回值可以出现程序的逻辑错误。例如尽管"1999/2/29"这个日期不存在， 但是下述程序的反馈是“ok”： if isdate(string(date("1999/02/29"))) then messagebox("response","ok") else messagebox("response","not ok") end if ---- 因此要特别注意无效日期的返回判断。 ---- 显示日期的string()函数问题 ---- Sting() 函数是一个很有用的显示Window, DataWindow 或Rreport上的日期的函数。 但是String() 函数与日期连用时要注意使用第二个说明日期格式的参数。第二个参数未 说明的话，PB则使用 short date 格式， 也就是年份是2位。例如下面的用法年份将是2位： String(today()) ---- 因此应当设定String()的第二个参数，例如作如下说明： String(Today(), "yyyy/mm/dd") ---- 世纪表达问题 ---- PB提供了若干计算日期的函数，如year,relativedate,等。 由于我们处于两个世纪之交的时期，世纪表达就变得突出。世纪的表达 用代码比较安全，可以避免世纪错误，例如要表达当前世纪用下述方法较好： Left(String(Year(Today())), 2) ---- 不要用“19”来表示当前的世纪。因为当时钟进入21世纪时， 当前世纪是“20”了。不规范的世纪表达方法也是出现2000年问题的原因。 ---- 日期数据的录入问题 ---- 在DataWindow中如果有容许用户输入2位年份的话，那么出现2000年问题的 可能性很大。如果开发人员用GetItemDate（）函数提取一个2位年的输入日期， PB即使用它隐含世纪判断规则，将输入日期转换为相应世纪的4位年份日期。 例如用户输入的员工出生日期是45/10/01，则GetItemDate（）函数会将其转 换为2045/10/01/，而不是1945/10/01。 ---- GetText（）函数从DataWindow 返回的是日期字符串，形式同用户的输入日期， 如45/10/01。当使用2位年的输入方法时，开发人员必须记住这2位年是那一个世纪。 如果再由此进行日期的计算或比较，哪个世纪必须明确，否则PB将用它的隐含世 纪规则转换日期。 ---- SingleLineEdit和EditMask控件也有类似的问题。 EditMask的format属性缺省是2位年。如果EditMask控件的mask属性用缺省的2位年份格式， 则返回的日期是2位年。EditMask控件有一个函数GetData（），如果mask属性是2位年， 那么其返回的日期也按PB自己的隐含世纪规则进行。下面是使用4位年日期的例子， 其中em_date 是EditMask，sle_date 是SingleLineEdit。 date d em_date.GetData(d) sle_date.Text = String(d, "yyyy/mm/dd") ---- 4. 关于short date 和long date ---- short date 和 long date源自Windows控制面板的区域设置，打开区域设置 可以看到日期的这两种设置。由于PB的缺省日期取自Windows的short date， 因此可以用手工在控制面板中将区域设置中的 short date改为yyyy/mm/dd， 但是PB中的缺省日期并不自动更改，例如，需要把EditMask的日期格 式需要更改为[date]International。也有人建议使用PB的RegistryGet() 和RegisterSet()函数更改操作Windows 的注册表 （HKEY_CURRENT_USER\Control Panel\International），重新定义short date， 这种方法不但操作复杂，而且不能替代每一处日期格式的检查，在网络应用 环境下难以管理，因此不值得提倡。 最安全和最可靠的方法是不要更动Windows的shortd date和 long date的设置，把重点放在与日期有关的域格式，日期的输入输出，函数等的运用上。 ---- 5． 结论 ---- 由上讨论可以看到，PB应用程序的2000年问题主要产生于使用2位年份， 包括用2位年份显示日期，容许用户输入2位年份，和用2位年份操作日期字符串。 PB的1900-01-01的无效日期返回值也是造成日期逻辑判断错误的重要原因。 因此如果开发人员遵循下述规则，则PB应用程序的2000年问题可以避免： 使用PB的扩充属性，所有显示和输入日期格式都设置为4位年 用String()函数操作日期时，要设第二个参数为4位年 在确定日期是否为有效日期时，注意1900-01-01


也 谈 用InstallShield 制 作 纯 中 文 化 的 安 装 程 序
dbzhou@163.net
---- 在 李 中 华 先 生 《 利 用InstallShield 5 英 文 版 开 发 纯 中
文 安 装 程 序 的 秘 密 》 一 文 中 谈 到 用InstallShield 5.0 制 作
中 文 版 的 安 装 程 序， 本 人 制 作 了 多 套 中 文 版 的 安 装 程
序， 也 谈 谈 用Install Shield 5.0 制 作 中 文 版 安 装 程 序。

---- Install Shield 允 许 定 制 对 话 框。 我 们 可 以 在Install Shield
项 目 中 加 入 自 己 定 制_Isuser.dll。_Isuser.dll 可 以 包 含 中
文 的 显 示 对 话 框。

---- 1。_Isuer.dll 的 获 得。

---- 在Install Shield 5.0 程 序 的 目 录 中， 有 一 个
.\custom dialog\VC++ 4 Project 目 录( 本 人 所 用 为VC 附 带
的Install Shield), 用VC 打 开 此 目 录 下 的 项 目 文 件， 对 资 源
编 辑。

---- a. 加 入 一 个 对 话 框， 指 明 其ID 号 为 想 要 改 变 的 对 话
框ID. 对 话 框ID 可 由

---- Install Shield 的tools 菜 单 下Dialog sampler 获 得。

---- 也 可 打 开Install Shield \Redistributable\Compressed Files\0009-English\

---- Intel 32 目 录 下_IsRes.dll,( 用Resuorce 方 式), 拷 贝 要 改 变
的 资 源 对 话 框 到 项 目 的 资 源 中， 改 其 语 言 为 中 文， 把
提 示 话 语 翻 成 中 文 即 可。

---- b. 对 要 改 变 的 对 话 框 都 操 作 一 遍。

---- c. 编 译， 生 成Release 版 的_Isuser.dll; 由 此， 获 得 了 自 己
的 提 示 对 话 框。 保 留， 可 作 为 今 后 制 作 安 装 程 序 使 用。

---- 2。 加 入_Isuer.dll 到InstallShield 项 目 中。

---- 在Install Shield 项 目 的Setup Files 项 中
English->operating system independent 中 点 击 鼠 标 右 键， 弹 出
菜 单 中 选 择Insert Files, 把 制 作 好 的_Isuser.dll 加 入 进 来。

---- 3。 生 成 安 装 程 序

---- 制 作 好 其 他 的 安 装 信 息， 选 择Build \Media Build Wizard ,
选 择 合 适 的 发 布 方 式， 生 成， 将 自 动 连 接_Isuser.dll， 安 装
程 序 的 编 译 将 把 定 制 的 信 息 加 入 到 安 装 程 序 中。

---- 中 文 化 的 安 装 程 序 即 制 成， 是 不 是 纯 中 文 的， 要
看_Isuser.dll 中 有 没 有 包 含 其 中 文 化 的 对 话 框 了。 一 般 地，
只 要 用 到 的 对 话 框 都" 汉 化" 了, 即 自 己 已 定 制 了 提 示，
就 是 纯 中 文 的 安 装 程 序 了。


PowerBuilder系统表
摘自Sybase China LTD.《PowerBuilder用户手册》

PowerBuilder系统表

PBCatTbl | 数据库中的表
PBCatCol | 数据库中的列
PBCatFmt | 显示格式
PBCatVld | 有效性规则
PBCatEdt | 编辑风格

PBCatTbl

pbt_tnam | 表名
PBt_tid | 表的SQL Server Object ID(仅用于SQL Server)
pbt_ownr | 表的属主
pbd_fhgt | 数据字体高度，以PowerBuilder中的单位表示
pbd_fwgt | 数据字体笔划粗细(400=Normal,700=Bold)
pbd_fitl | 字体是否位斜体(Y=YES，N=NO)
pbd_funl | 数据字体是否为加下划线(Y=YES，N=NO)
pbd_fchr | 数据字体字符ji(0=ANSI,2=Symbol,255=OEM)
pbd_fptc 数据字体的字符间距和系列，由2个常数相加得到
Pitch(0=Default,1=Fixed,2=Variable)
Family(0=无所谓,16=Roman,32=Swiss,48=Modern,64=Scrit,80=Decorative)
pbd_ffce | 数据字体的字形
pbh_fhgt | 标题字体高度，以PowerBuilder中的单位表示
pbh_ fwgt | 标题字体笔划粗细(400=Normal,700=Bold)
pbh_fitl | 标题字体是否位斜体(Y=YES，N=NO)
pbh_funl | 标题字体是否为加下划线(Y=YES，N=NO)
pbh_fchr | 标题字体字符集(0=ANSI,2=Symbol,255=OEM)
pbh_fptc 标题字体的字符间距和系列，由2个常数相加得到
Pitch(0=Default,1=Fixed,2=Varible)
Family(0=无所谓,16=Roman,32=Swiss,48=Modern,64=Scrit,80=Decorative)
pbh_ffce | 标题字体的字形
pbl_fhgt | 标签字体高度，以PowerBuilder中的单位表示
pbl_ fwgt | 标签字体笔划粗细(400=Normal,700=Bold)
pbl_fitl | 标签字体是否位斜体(Y=YES，N=NO)
pbl_funl | 标签字体是否为加下划线(Y=YES，N=NO)
pbl_fchr | 标签字体字符集(0=ANSI,2=Symbol,255=OEM)
pbl_fptc 标签字体的字符间距和系列，由2个数相加得到
Pitch(0=Default,1=Fixed,2=Varible)
Family(0=无所谓,16=Roman,32=Swiss,48=Modern,64=Scrit,80=Decorative)
pbl_ffce | 标签字体的字形
pbt_cmnt | 表的注释

PBCatCol

pbc_tnam | 表名
pbc_tid | 表的SQL Server Object ID
pbc_ownr | 表的属主
pbc_cnam | 列名
pbc_cid | SQL ServerColumn ID
pbc_labl | 标签
pbc_lpos | 标签位置(23=左边，24=右边)
pbc_hdr | 标题
pbc_hpos | 标题位置(23=左边，24=右边，25=居中)
pbc_jtfy | 对齐方式(23=左边，24=右边)
pbc_mask | 显示格式的名称
pbc_case | 大小写(26=Actual,27=UPPER,28=lower)
pbc_hght | 列高度
pbc_wdth | 列宽度
pbc_ptrn | 有效规则的名称
pbc_bmap | 位图/图片(Y=Yes,N=No)
pbc_init | 初始值
pbc_cmnt | 列的注释
pbc_tag | (保留)
PBCatFmt
pbf_name | 显示格式的名称
pbf_frmt | 显示格式
pbf_type | 格式应用于何种数据类型
pbf_cntr | 并发用法标志

PBCatVld

pbv_name | 有效性规则的名称
pbv_frmt | 有效性规则
pbv_type | 有效性规则作用的数据类型
pbv_cntr | 并发用法标志
pbv_msg | 有效性检验出错消息

PBCatEdt

pbe_name | 编辑风格的名称
pbe_edit | 格式串
pbe_type 编辑风格类型:
85=CheckBox,86=RadioButton,87=DropDownListBox
88=DropDownDataWindow,89=Edit,90=Edit Mask
pbe_cntr | 修改次数计数器
pbe_seqn | 对于PNCatEdt标中需要多行的编辑类型，指定行循序
pbe_flag | 编辑风格标志
pbe_work | 附加域


不要害怕PFC
作者：Dana P. Crenshaw

你是不是想用PFC，却对它知之不多，或者，你已开始使用，却遇到了困难？
如果是这样，那你就要看看该文了，这篇文章的目的正是为你介绍PFC，使你
了解它的功能，同时，也很好的概括了PFC的基本概念和怎样在你的应用程序
中来实现他。

可能，你ÎÊ自己的第一个问题是：为什么要用PFC？当然，如果你不用任何库
工具，你也能开发应用程序，然而，采用PFC可以促进你的开发工作，因为 PFC
为你提供对象和事件（这些都是别人花了不少时间来建立的〕，你也可能要花一
些时间来掌握它，不过，你会发现，这很值得。

PFC基础

开始用PFC，可能有点困难，但一旦你掌握了它，知道了它的功能，你会受益
匪浅，Powersoft开发的PFC库提供了非常丰富的对象来供你开发时使用。 为了正
确地使用PFC，有2个概念你必须要了解，第一个是：有2层：PFC祖先层和PFC
扩展层(子孙层) ，最重要的是你不应该修改祖先层上的任何对象( 前缀为PFC)，
这是因为Powersoft会修改这些以PFC为前缀的对象，你对它们所做 的任何修改都
将会被Powersoft以后的升级版本所付给复盖，你修改的应该在扩展层上，你可以
修改W_MASTER而不是PFC_W_MASTER

在每一层上都有PBL集，祖先层这些PBL是PFCAPSRV，PFCDWSRV，
PFCMAIN，PFCWNSRV，扩展层上对应的是PFEAPSRV，PFEDWSRV
PFEMAIN，PFEWNSRV，
PFCAPSRV(PFCAPSRV) 提供应用程序管理服务和全局对象，
PFCDWSRV(PFEDWSRV) 数据窗口服务，
PFCMAIN(PFEMAIN) 包括了应用程序开发的基本服务，
PFCWNSRV（PFEWNSRV〕窗口对象服务，
所以， 当你建立你自己的应用程序时，你应确信已包含了所有这些库

在开始之前的另一件事情是你应理解PFC的基础对象，这些对象包
括PFC_W_MASTER,PFC_U_DW,PFC_N_TR,PFC_N_CNT_APPMANAGER.
PFC_W_MASTER:所有窗口的祖先窗口
PFC_U_DW:祖先数据窗口对象
PFC_N_TR:祖先事务对象
PFC_N_CST_APPMANAGER:n_cst_manager和全局变量gnv_app的祖先Application
Manager
PFC还包括许多别的对象，强烈要求你熟悉上面提及的这4个对象，一旦你理解了
它们的功能，你就可以为你的业务开发用户对象了。

应用程序建立

把PFC加入到你的应用程序要求三步：

  • 第一步：

    第一步是要确信把上面提及的8个库加到了你的应用程序 激活Application
    的Properties窗口，选Library表，如果你的窗口不象,按Browse来选择并增
    加另外的库（当然也包括PFCTUTOR.PBL库〕。

  • 第二步：

第二步是在你的应用程序事件中加入脚本，这里主要的事情是定义一
个全局
变量'gnv_app',应用程序的OPEN事件可能有下面这几行：
gnv_app = CREATE n_cst_appmanager //creates the object
gnv_app.Event pfc_Open(commandline) //invokes the OPEN event for the object

CLOSE事件可能是这样:
gnv_app.Event pfc_Close() //closes
DESTROY gnv_app //destroys

你可通过选择'Declare","Global Variable...'然后输入n_cst_appmanager
gnv_app来定义
全局变量'gnv_app' 你也可以在ConnectionBegin, ConnectionEnd, Idle and
SystemError
事件中编码：
//ConnectionBegin script
ConnectPrivilege lcp_privilege lcp_privilege = &
gnv_app.Event pfc_ConnectionBegin(userid, password, connectstring)
Return lcp_privilege

//ConnectionEnd script
gnv_app.Event pfc_ConnectionEnd()

//Idle script gnv_app.Event pfc_Idle()

//SystemError script gnv_app.Event pfc_SystemError()

  • 第三步：

第三步是修改'n_cst_appmanager',以便使用你的应用程序的初始化文
件(.ini)， 图三图示了Application Manager的Constructor和PFC_Open事件

准备开始

现在你可能对使用PFC感到有点适应了. 假如你ÓÐPowerBuilder手册，建议你充
分利用PFC的教程. 事实上，你可能要看教程几次; 每次建立一个不同的应用程序.
没有比使用它去熟悉PFC 的更好的办法. Powersoft非常善意的为你提供了一个快
速建立PFC应用程序的方法. 在QckStart 子目录, 你可以看到5个快速开始
库QCKSTART.PBL, QCKAPSRV.PBL, QCKDWSRV.PBL, QCKMAIN.PBLºÍ
QCKWNSRV.PBL.


怎样实现一个列焦点指示器PFC数据窗口服务
Alexander Kjerulf

本文一步一步地介绍了怎样建立一个PFC数据窗口服务（Service)
(注：PFC把相关的处理类型隔离成若干组，它们就叫Service)。
这个service高亮度显示数据窗口的当前列

创建service

第一步：创建一个新的数据窗口服务。

建立一个新的nvo用户对象，它从pfedwsrv.pbl的n_cst_dwsrv继承而来，命名成
n_cst_dwsrv_col_focus_ind并保存。这就是我们要创建的数据窗口service.

第二步：把数据窗口service连接到祖先数据窗口

打开PFE祖先数据窗口u_dw。在这个对象中，为了实例化这个service，我
们要在 其中编写脚本。

  • 在u_dw中定义一个实例变量:
    n_cst_dwsrv_col_focus_ind inv_col_focus_ind

  • 在u_dw中定义一个用户对象函
    数of_set_col_focus_ind 在u_dw的destuctor事件中编写脚本：
    int of_set_col_focus_ind(Boolean ab_switch)

    //Check arguments
    If IsNull(ab_switch) Then
    Return -1
    End If

    IF ab_Switch THEN
    IF Not IsValid (inv_col_focus_ind) THEN
    inv_col_focus_ind = Create n_cst_dwsrv_col_focus_ind
    inv_col_focus_ind.dynamic of_SetRequestor ( this )
    Return 1
    END IF
    ELSE
    IF IsValid (inv_col_focus_ind) THEN
    Destroy inv_col_focus_ind
    Return 1
    END IF
    END IF

    Return 0

  • ÔÚu_dwµÄdestructorÊÂ¼þÖÐ±àÂë£ºthis.of_set_col_focus_ind( FALSE )

第三步：对n_cst_dwsrv_col_focus_ind编码

  • 在n_cst_dwsrv_col_focus_ind中第一一个实例变量
    protected String is_last_focus_col

  • 创建一个用户对象函数of_set_col_focus

    string header_name

    // Get header name
    header_name = as_col_name + is_defaultheadersuffix

    // Bold header of current column
    idw_requestor.Modify( header_name + ".Font.Weight = 700")
    IF Len( is_last_focus_col ) > 0 THEN
    // Remove bold from last column
    header_name = is_last_focus_col + is_defaultheadersuffix
    idw_requestor.Modify( header_name + ".Font.Weight = 400")
    END IF

    is_last_focus_col = as_col_name

    return 1

  • 创建一个用户事件ue_itemfocusChanged

    int res

    IF as_col_name <> is_last_focus_col THEN
    // Column focus has changed. Set new focus.
    res = of_set_col_focus( as_col_name )
    ELSE
    res = 1
    END IF

    return res

第四步：

现在要做的是当在数据窗口的itemfocuschanged事件发生时，应调用
n_cst_dwsrv_col_focus_ind中的ue_itemfocuschanged，这可通过在u_dw的
itemfocuschanged事件中编写如下脚本来实现：

IF IsValid( inv_col_focus_ind ) THEN
inv_col_focus_ind.Trigger Event ue_itemfocuschanged( dwo.name )
END IF

第五步：

在数据窗口控件(数据窗口必须为u_dw类型或它的一个子孙)的constructor事
件中编写如下代码：

this.of_set_col_focus_ind( TRUE )

它如何工作。

In the itemfocuschanged event of u_dw, we check if the service is active and if so call ue_itemfocuschanged on the service object. In this event we perform a call to the object function of_set_col_focus which then highlights the header belonging to the column in question. The name of the header is found as the name of the column concatenated with the default header suffix. If nothing else is specified this is "_t", so that a column "emp_no", will have a header text named "emp_no_t". This is also the default way that PB names headers. If you have named your headers differently, simply change the value of the default header suffix, by calling the of_setdefaultheadersuffix() function. For a detailed description of this, see the PFC documentation.

In this function it is also necessary to "switch off" the last column which was highlighted. The name of the previously highlighted column is stored in the is_last_focus_col variable.

Possible modifications

The way this object works is that the text in the header for the active column is made bold. There are other ways to indicate the current column, eg. changing the background colour or the border.

How exactly to do it, could be controlled by an instance variable defined like this:

protected int ii_focus_indicator

In the of_set_col_focus you could then have a CHOOSE CASE statement to decide how to highlight the active column, based on the value of ii_focus_indicator. To set the value of this variable, you should make a user object function on n_cst_dwsrv_col_focus_ind called of_setcolumnfocusindicator(). This would be analogous to SetRowFocusIndicator().

Another point: If you're worried about the performance implications of having code in the itemfocuschanged event of your dw ancestor object, then you might consider not placing any code there. Then you would have to place the same code in every dw where you actually use this service.

Summary

This article demonstrates how quickly new datawindow services can be created. I originally created this service in ½ an hour. It's that easy.

The question is: Why create a service when a few lines of code in the itemfocuschanged event will accomplish the same. The answer is: For the sake of code maintenance. This service can now be expanded and modified to changing needs, and subsequently plugged into any dw. If you start with having the code directly in the dw ancestor, you limit the maintainability and flexibility of the code.

Also, having the code in a seperate object makes for better encapsulation, and hence better OO practice.


PB 5中PFC的使用
中原石油勘探局计算中心系统集成部
刘少青

---- PFC是PowerBuilder高级开发工具集中的一个基本类库，提供了
进行一般应用开发时会经常用到的对象和函数代码。PowerBuilder
5.0的基本类库中的内容十分丰富，比以前版本中所提供的功能要增加
了许多，而且对象之间的继承和引用关系也比以前更为清晰合理。因此
编写一个完全建构在PFC类库之上的应用可大大提高软件质量，而且利
用基类的已有代码也会使得许多复杂的功能实现起来要轻松得多。

---- 本文将根据笔者使用PFC的经验，介绍如何创建PFC的应用。

---- 1、首先在C盘新建一个目录Test，启动PowerBuilder5.0，
创建一个新的应用，并将应用对象保存在Test.pbl中，将应用取名
为test，保存时不使用PB提供的快速模板。

---- 2、进入应用的“Properties”（属性）中，选中
“Libraries”标签，单击“Browes…”按钮，选中
C:\Pwrs\Pb5i32\Adk\pfc目录下的全部8个pbl文件：
pfcapsrv.plb、pfcdwarv.pbl、pfcmain.pbl、pfcwnsrv.pbl、
pfeapsrv.pbl、pfedwarv.pbl、pfemain.pbl、pfewnsrv.pbl，
单击“打开”按钮，再单击“Apply”按钮。然后选中“Variable
Types”标签，将“SQLCA:”中的“transaction”改为“n_tr”，
单击“OK”按钮保存对属性的修改。

---- 3、新建一个“User Object”（用户对象），单击
“Inherit…”（继承）按钮，在“Application Libraries”
中选中“PFEAPSRV.PBL”，在“User Objects”中选中
“n_cst_appmanager”，单击“OK”按钮，在其“pfc_open”
事件中写入以下登录数据库的程序（以SQL Server为例）：

//pfc_open for n_cst_test inherited
n_cst_appmananger returns (none)
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 Using SQLCA;
If SQLCA.sqlcode < > 0 Then
MessageBox("Cannot Connect to Database",
SQLCA.sqlerrtext,StopSign!)
Halt Close
Return
End If
Open(w_frame_test) //打开应用的主窗口（假设为w_frame_test）

---- 将该用户对象保存为n_cst_test。

---- 4、进入应用的“Script”中，选择“Declare/Global Variables…”菜单，
定义全局变量：

n_cst_appmanager gnv_app //该全局变量的名称必须为gnv_app

并在该应用的open事件和close事件中分别写入以下程序：

//open for test returns (none)
gnv_app = Create n_cst_test
gnv_app.Event pfc_open(commandline)

//close for test returns (none)
gnv_app.Event pfc_close()
Destroy n_cst_appmanager

---- 经过上述步骤后，我们所要的PFC应用的总体械架就形成了，
剩下的就是具体的编程工作了，本人在此不再赘述。

海洋之星文库-PowerBuilder资料:http://hxzhang.yeah.net
收集整理自 世界计算机日报 转载请勿删除此行。


PFC方法与老方法

作者：Alexander Kjerulf 96.12.8

摘要

当转换到用PFC开发应用程序时，许多开发方法也都必须改变，这篇
文章概述了用PFC开发PowerBuilder应用程序时的一些主要的变化，同
时也列出了一些主要要采用的方法。

TIPS

  • 窗口上的对象

    当把一个对象放到窗口上时，你应该确信你使用的是PFEMAIN.PBL上的标
    准可视用户对象或它的一个子孙，而不是在pfcmain.pbl中的，如果你使用了
    一个在pfcmain.pbl中的对象，你将切断其扩展层，并且使你在以后的
    对pfemain.pbl 中对象的所有改变无效。 要确保你总是使用的是可视用户对
    象而不是普通的控件，最简单的办法是从window Painter的painter bar中把控
    件按钮去掉。这你可以通过定制painter bar来实现，最后只留下一个user
    object 按钮。当然你也可以加一些各种类型的用户对象按钮。
  • 为菜单项的Clicked事件编程

    在以前，代码可能是这样：

    // Clicked script for menu item save
    ParentWindow.TriggerEvent("ue_save")

    然而，在PFC中，有消息循环，这是一个在对象之间传递消息的一个有创
    造力的方法，使用消息循环后，上面的代码将变成：

    // Clicked script for menu item save
    of_message("ue_save")

    注意： 这里没有指向父窗口，这是因为消息循环根据它自己的逻辑，把消
    息分配给不同的 对象，直至那个对象可以接收消息，对消息循环逻辑的详
    细描述请查看在线书上的PFC文档。
    当你在一个窗口上有2个数据窗口时，这种消息循环的优点十分明显，菜单
    可以包含许多与数据窗口直接相关的菜单项，例如InsertRow,DeleteRow,
    copyRow等等。当用户从菜单选择“InsertRow",他可能希望在当前活动的数
    据窗口上增加记录，如果用以前的方法(ParentWindow.TriggerEvent("ue_insert"))
    在窗口上触发用户事件ue_insert,在事件的脚本中就必须判断当
    前哪个数据窗口是活动的，然后在其上插入一行。
    消息循环就不是这样，任何消息首先送达当前活动控件，所以ue_insert自动
    的对应于当前活动窗口，并在其上增加一条记录，这正是用户所希望的，
    但是， 如果在窗口上有第三个控件，而这个控件又没有ue_insert事件，但
    控件是活动的，而用户又在菜单中选择了"Insert Row"结果会怎样呢？如果
    这个控件没有这个消息请求，那么这个消息将送达活动窗口或MDI应用程
    序的Sheet，在这里你可以编写脚本来处理这种情况，你也可以不作任何
    处理，在这种情况，仅仅在2个数据窗口之一为活动窗口时，从菜单选择
    "Insert Row"才有效。
  • 为应用程序事件编写脚本

    在OPEN事件中，我过去常常编码来从INI文件读DBMS连接参数，然后执
    行连接，在System Error";事件中，我也常编码来显示一个错误，在CLOSE事
    件中，我编码来做清理和中断数据库连接。 当我们用PFC时就应该避免
    这样，应用程序事件脚本应该仅仅包含一个CALL语句，来调用AppManager
    对象的一个相应事件

OLD way vs. PFC way

  • In a datawindow
OLD | PFC
dw_1.SetTransObject( SQLCA ) | dw_1.of_settransobject( SQLCA )
dw_1.Retrieve() dw_1.Trigger Event pfc_retrieve()

Remember to code the ue_retrieve event whenever you place a datawindow on a window. The code in the ancestor simply says return 1, which won't retrieve much data.

dw_1.Update() win_1.Trigger Event pfc_save()

触发窗口上的save事件，窗口上的所 有数据窗口将自动update
dw_1.InsertRow( before_row ) dw_1.SetRow( before_row )
dw_1.Trigger Event pfc_insertrow( )

如果要的插入的行是在当前行前，
则SetRow()可以省略
dw_1.InsertRow( 0 ) | dw_1.Trigger Event pfc_addrow()
dw_1.DeleteRow( row ) dw_1.SetRow( row )
dw_1.Trigger Event pfc_deleterow( )

如果要删除的行是当前行，则
SetRow() 可以省略
dw_1.ScrollNextPage() | dw_1.Trigger Event pfc_nextpage()
.dw_1.ScrollPriorPage() | dw_1.Trigger Event pfc_previouspage()
dw_1.Cut() | dw_1.Trigger Event pfc_cut()
dw_1.Copy() | dw_1.Trigger Event pfc_copy()
dw_1.Paste() | dw_1.Trigger Event pfc_paste()
dw_1.Clear() | dw_1.Trigger Event pfc_clear()
dw_1.Print() | dw_1.Trigger Event pfc_print()
  • In a menu:
OLD | PFC
ParentWindow.Trigger Event(ue_open) Used in the clicked event of the a menu to send a signal to the window that the user has made a choice in the menu. | of_sendmessage(ue_open) Use messaging to route the message to the active object/window/frame.

Database

OLD | PFC
CONNNECT; | sqlca.of_connect()
DISCONNNECT; | sqlca.of_disconnect()
COMMIT; | sqlca.of_commit()
ROLLBACK; | sqlca.of_rollback()
trans_2.DBMS = trans_1.DBMS

trans_2.Database = trans_1.Database

...

To copy one transaction to another.

trans_1.of_copyto( trans_2 )

Copies all properties of trans_1 to trans_2.

(Remember that trans_2 = trans_1 only makes trans_2 reference trans_1. It does NOT create a new transaction object)

sqlca.DBMS = ProfileString( "inifile", ... )

sqlca.Database = ProfileString( "inifile", ... )

...

To initialize the transaction object from an INI file.

sqlca.of_init( "inifile" "profile_name")

Or use:

sqlca.of_init( "registry_key")

to initialize from the registry.

  • General
OLD | PFC
Storing a reference to the frame window in a global variable gw_frame. | gnv_app.of_getframe() Returns a reference to the frame window
gw_frame.SetMicroHelp( "Reading data") Uses the hypothetical global variable mentioned above, which contains a reference to the frame window. | gnv_app.of_setmicrohelp( "Reading data.")


使用WEB.PB时系统的文件配置 天顺公司软件部 陈俊明编写
  • 在浏览器用户端：如果要用到plug-ins，则将plug-ins文件拷贝到浏览器安装路径中的plugins目录中，否则不用作任何操作。
  • WEB服务器端：将WEB.PB文件（CGI支持文件）拷贝到WEB服务器软件的安装路径中的CGI执行路径中；如果用到plug-ins，则相应在WEB服务器软件中设置PBD和PSR文件的MIME类型，如下：
    1.PBD设置为application/vnd.powerbuilder6
    2.PSR设置为application/datawindow
  • 将PowerBuilder的运行支持库安装到服务器上,并修改注册表：
    在注册表中加入主键到Hkey_Local_Machine\Software\Microsoft\Windows\ CurrentVersion\App Paths下，键名为应用程序的名称，键值为安装有PowerBuilder运行支持动态库的路径
  • 设置好PBWEB.INI文件：
    [<服务器应用信息名>]
    driver=WinSock
    application=<WinSock端口号 或是 在SERVICES文件中标识端口号的标识名>
    location=<PowerBuilder服务器程序所在机器的IP地址 或是 域名 或是在HOSTS文件中的标识名>
  • 如果上述application设置的是标识名，则应编写SERVICES文件,找到SERVICES文件，在文件中加入下述行：
    <端口标识名> <端口号>/TCP
    如果location用的是标识名，则应编写HOSTS文件，找到HOSTS文件名，并在文件中加入一行：
    <IP地址> <标识名>
说明:所有的’<’和’>’表示要输入的内容,实际中不用加’<’和’>’


在PB 应 用 中 收 发 电 子 邮 件
广 东 珠 海
卞 国 斌

---- 随 着Internet 进 入 千 家 万 户, 通 过Internet 收 发 电 子 邮 件 将
成 为 人 们 最 受 欢 迎 的 通 讯 手 段 之 一。 如 果 我 们 的 应 用 程 序
也 能 够 提 供 收 发 电 子 邮 件 的 功 能, 就 可 以 使 我 们 方 便 快 捷
地 与 他 人 交 换 和 共 享 应 用 的 数 据 和 信 息, 使 其 应 用 真 正 地
溶 合 于Internet 这 个 大 环 境。 下 面 我 介 绍 一 下 在PowerBuild 应 用
中 实 现 收 发 电 子 邮 件 的 方 法。

---- 在PowerBuild 应 用 中 实 现 收 发 电 子 邮 件, 主 要 是 通 过PowerBuild
系 统 提 供 的 一 套 用 于 邮 件 操 作 的 内 嵌 函 数。 这 些 函 数 实 现
其 应 用 到 微 软 公 司 的MAPI(Message Application Program Interface) 标
准 之 间 的 接 口。

---- 一、 作 为 举 例, 创 建 一 个MDI 风 格 的 窗 口w_mail。 在 这 个 窗
口 上, 放 置 一 个 含 接 收 邮 件、 发 送 邮 件 和 地 址 薄 三 个 带 图
标 的 菜 单 项 的 菜 单m_mail, 一 个 多 行 编 辑 器mle_1。 整 个 窗 口
的 外 观 如 下:

---- [ 图 略]

---- 二、 为 各 菜 单 项 编 制 事 件(Event) 处 理 程 序(Script)。 程 序
行 中// 后 的 为 说 明 文 字。

---- 1. 在 菜 单 项“ 接 收 邮 件” 的 单 击(Clicked) 事 件 下 输 入 下
面 程 序:

mailsession mses
//定义对象mses,以供在程序中引用
mailmessage mmsg
//定义结构mmsg,此结构用于描述邮件的内容
mailrecipient mrec
//定义结构mrec,它标识邮件的接收者和发送者
mailreturncode mreturn
//定义返回码,用于存放函数的返回值
int mnbr
//定义临时变量

//产生邮件会话对象mses
mses = create mailsession

//邮件登录。此处用户名,口令缺省
mreturn = mses.maillogon(mailnewsession!)
if mreturn < > mailreturnsuccess! Then
messagebox("邮件登录","邮件登录失败!")
return
end if

//获得接收到尚未读过的邮件ID号。
它是放在mses中的一个字符串数组
mreturn = mses.mailgetmessages(true)
if mreturn < > mailreturnsuccess! then
messagebox("邮件获取","邮件获取失败或无新邮件!")
return
end if

//获得接收到尚未读过的邮件数目
mnbr = upperbound(mses.messageid[])

//读取最后接收到的邮件内容。
它被放在mmsg的notetext的字符串字段中
mses.mailreadmessage(mses.messageid[mnbr],
mmsg,mailentiremessage!,true)

//把获得的邮件内容放到窗口w_mai1上的多行编辑器mle_1中
w_mai1.mle_1.text=mmsg.notetext

//退出登录
mses.maillogoff()

//取消邮件会话
destroy mses

---- 2. 在 菜 单 项“ 发 送 邮 件” 的 单 击(Clicked) 事 件 下 输
入 下 面 程 序:

mailsession mses
mailmessage mmsg
mailreturncode mreturn

//产生邮件会话对象mses
mses = create mailsession

//邮件登录
mreturn = mses.maillogon(mailnewsession!)
if mreturn < > mailreturnsuccess! then
messagebox("邮件登录","邮件登录失败!")
return
end if

//弹出邮件地址窗口,获取邮件接收者的地址。
地址及名字是放在mmsg中的一 个结构数组
mreturn = mses.mailaddress(mmsg)
if mreturn < > mailreturnsuccess! then
messagebox("邮件地址","邮件地址出错!")
return
end if

//把窗口w_mai1上的多行编辑器mle_1
中的内容放到mmsg的notetext字段中
mmsg.notetext = w_mai1.mle_1.text

//发送邮件
mreturn = mses.mailsend(mmsg)
if mreturn < > mailreturnsuccess! then
messagebox("邮件发送","邮件发送失败!")
return
else
messagebox("邮件发送","邮件发送成功!")
end if

//退出登录
mses.maillogoff()

//取消邮件会话
destroy mses

---- 3.在菜单项“地址簿”的单击(Clicked)事件下输入程序:

mailsession mses
mailmessage mmsg
mailreturncode mreturn

//产生邮件会话对象mses
mses = create mailsession

//邮件登录
mreturn = mses.maillogon(mailnewsession!)
if mreturn < > mailreturnsuccess! then
messagebox("邮件登录","邮件登录失败!")
return
end if

//弹出邮件地址窗口,获取邮件接收者的地址
mreturn = mses.mailaddress(mmsg)
if mreturn < > mailreturnsuccess! then
messagebox("邮件地址","邮件地址出错!")
return
end if

//退出登录
mses.maillogoff()

//取消邮件会话
destroy mses

---- 三、 执 行 程 序。 正 常 执 行 程 序 的 前 提 是, 在Powerbuild
应 用 所 在 的 机 器 上 已 装 有Microsoft Mail 客 户 软 件。

---- 1. 单 击“ 接 收 邮 件” 菜 单 项 或 工 具 图 标, 执 行 接 收
邮 件 处 理 程 序。 该 程 序 将 最 后 接 收 到 尚 未 读 过 的 邮 件
内 容 显 示 到 窗 口 的 多 行 编 辑 器 中。

如 果 把mreturn = mses.mailgetmessages(true) 改 为:
mreturn = mses.mailgetmessages()
便 可 获 得 全 部 邮 件( 包 含 已 读 过 的 邮 件)。

---- 2. 单 击“ 发 送 邮 件” 菜 单 项 或 工 具 图 标, 执 行 发 送
邮 件 处 理 程 序。 该 程 序 执 行 过 程 中, 弹 出 地 址 簿 窗 口,
在 你 选 择 好 接 收 者 后, 则 将 窗 口 的 多 行 编 辑 器 中 的 内
容 发 送 出 去。

---- 3. 单 击“ 地 址 簿” 菜 单 项 或 工 具 图 标, 执 行 地 址 簿
处 理 程 序。 该 程 序 弹 出 通 讯 簿 窗 口, 供 你 增 加、 编 辑 或
删 除 邮 件 地 址 属 性。

回主页


使用PowerBuilder进行Internet编程时
CGI环境变量的解说
天顺公司软件部 陈俊明

我在Internet编程时发现，在PBWEB.INI文件中有下列各项：
[Web.PB]
ErrorMessage=
ISAPIKeywords=
CGIKeywords=
NSAPIKeywords= 开始时，我不知道这些项是什么意思，后来，我将这些项从PBWEB.INI文件中删除，在执行PB中的例子时，出现下列错误：取得环境参数时返回值为空。加上以后，一切正常。于是我想，可能这些设置与CGI的环境参数有关。经过进一步的测试，结果正是这样。现在我把这些设置的意义列出来，以供和我一样的朋友参考： 一.ErrorMessage 此设置表示在找不到相应的对象时发送给浏览器的信息。例如：ErrorMessage = ‘我们的服务器没有找到您所要的页面，<a href=”MailTO:chenjm@163.net”>请与管理员联系</a>’。此时，如果您的页面中调用了服务器应用中不存在的例程，则会返回以下信息：我们的服务器没有找到您所要的页面，请与管理员联系。点击兰色位置会发送EMAIL给chenjm@163.net 二.ISAPIKeywords和 CGIKeywords以及 NSAPIKeywords为三种类型服务器上的环境参数名。其中：

  • SERVER_SOFTWARE：

    WEB服务器所用的软件，如：WEBSITE

  • SERVER_NAME： WEB服务器的域名，如：chenjm.tianshun.com

  • GATEWAY_INTERFACE： 网关接口，如：CGI/1.3（DOS）

  • SERVER_PROTOCOL： 协议，如：HTTP/1.0

  • SERVER_PORT： WEB服务器的端口号，如：80

  • REQUEST_METHOD： 表示从什么事件调用的，FORM中为POST，LINK为GET

  • HTTP_ACCEPT： 我不明白，如：D:\WEBSITE\CGI-TEMP\98WS.ACC

  • PATH_INFO： 我不明白，如：/WEBPBS/uo_internet/getinfo

  • PATH_TRANSLATED： 我不明白，如：D:\WebSite\htdocs\WEBPBS\uo_internet\getinfo

  • SCRIPT_NAME： 调用的CGI程序，如：/cgi-bin/pbcgi60.exe

  • QUERY_STRING： 我不明白

  • REMOTE_HOST： 我不明白，可能是浏览器方的主机名（通过WINS）

  • REMOTE_ADDR： 浏览器方的IP地址

  • REMOTE_USER： 我不明白

  • CONTENT_TYPE： 我不明白

  • CONTENT_LENGTH： 我不明白

  • HTTP_USER_AGENT： 可能是浏览器方的浏览器类型及操作系统类型，如：Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)

三.怎样取得这些参数

  • 在不可视对象的方法定义时，加入和上述名称相同的类型为STRING的参数即可取得其值。

  • 在使用前，请先判断此值是否为空，或是为空串。

说明：上述参数中有很多我还不知道是什么意思，如有那位朋友知道的话请写信告诉我，谢谢。


PowerBuilder 5.0对SQL Server 6.0数据库的访问

胜利石油管理局钻井工艺研究院计算机所 李 玉 (257017)

胜利石油管理局钻井地质公司 李 健

山东东营外经委 卢文纲

摘要 本文介绍用PowerBuilder5.0与SQL Server 6.0构建客户/服务器方案时，使用专用接口DB-Library实现对SQL SERVER 6.0数据库的访问，并给出了连接步骤和配置方法。

关键词 客户/服务器 通信接口 DB-Library

在构建客户/服务器计算方案时，前端客户机和后端服务器均有大量不同种类的软件供用户选择。本文对如下环境中的客户机与服务器使用专用接口通信进行介绍，并给出连接步骤及配置方法。

·服务器端：Windows NT 3.51 Advanced Server，Mcrosoft SQL Sever 6.0
·客户端 ：Windows 95，PowerBuilder 5.0
·协议 ：Microsoft TCP/IP

一、PowerBuilder与SQL Server
PowerBuilder(以下简称PB)是客户/服务器(C/S)结构中，Windows环境下客户端的专业开发工具，由其开发出的应用程序可以和服务器端的数据库（如Sybase，ORACLE，SQL Server等）协同工作，构成C/S工作方式，顺应了分布计算的发展潮流。

SQL Server是Microsoft公司BACKOFFICE战略的重要组成部分，它被设计成一种用于分布式客户/服务器计算的高性能的DBMS。作为后台的数据库服务器，SQL Server运行在Windows NT上，与NT良好的集成，使之能充分利用这一先进的操作系统提供的各项功能。例如，它可以利用NT的多线程特性来提高自己的性能等等。
在采用PB与SQL Server构建C/S方案时，前端的PB程序使用API（应用程序接口）与后台的SQL Server进行通信。目前，PB5.0与SQL Server 6.0通信可使用ODBC和DB-Library两种接口。

二、专用的数据库接口软件

由于ODBC(Open Database Connectivity)是通用的数据库接口，每次只能返回一个结果集（即使是调用含有多条命令的存储过程），且调用层次多，因而通过这种接口操作数据库的效率低。专用的数据库接口软件，顾名思义，是专为某种数据库设计的软件接口，PB5.0提供的到SQL Server数据库的接口软件DB-Library即属于该类接口软件。DB-Library允许PB应用程序存取SQL Server并与之相互作用，它提供了完整丰富的API函数。这种接口的特点是调用直接，速度快，并充分发挥了SQL Server数据库的特色。PB到SQL Server的调用层次如下图所示:

Powerbuilder 5.0

Microsoft SQL Server 6.0 数据库服务器
专用接口（PB提供）

Microsoft SQL Server Microsoft SQL Server
Net-Library Net-Library

网络 网络

三、PB5.0通过专用接口连到SQL Server 6.0的步骤及配置
1、安装NT网络软件（因为SQL Server必须运行在NT环境上），
在客户端（Windows 95）支持Microsoft TCP/IP协议并调通。
2、安装配置SQL Server 6.0
·服务器端: 安装SQL Server 6.0服务器软件，对TCP/IP协议来说，
缺省端口号为1433。
·客户端:
1）安装SQL Server 6.0客户端软件；
2）用SQL Client Configuration Utility进行配置
·启动SQL Client Configuration Utility。它包括DB-Library、Net
Library和Advanced三项，对于DB-Library和Advanced两项使
用缺省值即可；
·配置Net Library：用鼠标单击Net Library，在“Default
Network”下拉框中，选取“TCP/IP sockets”；
·单击“Done”按钮结束。
3）用客户端的ISQL-w连到SQL Server服务器进行测试。使SQL
Server6.0数据库自身能够正常运行。
3、安装配置PB5.0
1） PB5.0本身的安装；
2） PB5.0到SQL Server 6.0专用接口软件安装（在PowerBuilder
Deployment Kit中）；
3）配置到数据库的连接参数
假设:使用MS TCP/IP协议，数据库服务器的名字是jsjsmain，
SQL Server的用户名为sa，口令为空，注册到数据库pubs。
(1)启动PB5.0，点中“Database笔画”(若出现“Select Table”
窗口，则点“Cancel”取消之)；
(2)单击主菜单的“File”项中的“Connect”进入其子菜单；
(3)单击子菜单中的“Setup”项，进入“Database Profiles”
窗口。
(4)单击“New”进入“Database Profile Setup”窗口，进行如
下参数设置：

(5)单击“OK”按钮，完成配置。
至此，用户便可利用专用接口实现对SQL Server的访问。


播放声音文件

在项目快要完成的前几天给你的软件加上一点声音，对提高应用程序界面将是一件十分有宜的事情！

在PowerBuilder应用程序中加入声音文件是十分容易的事情：只需要增加一个外部函数调用即可，播放wav声音文件的其中之一的函数是:

FUNCTION INT SndPlaySound( &
'*.wav fn(STRING)', modeofplay(INT)) &
library "mmsystem.dll"

在Win32系统下，需要使用另外一个DLL函数调用:

Function boolean sndPlaySoundA( &
string SoundName, uint Flags ) Library "WINMM.DLL"
Function uint waveOutGetNumDevs() Library "WINMM.DLL"

使用以下的代码来播放声音文件:

uint lui_NumDevs

lui_NumDevs = WaveOutGetNumDevs()
IF lui_NewDevs > 0 THEN
sndPlaySound( "filename.wav", modeofplay(INT) )
END IF

其中: modeofplay
0 - 同步播放
1 - 异步播放
2 - 如果找不到声音文件，不使用缺省的声音文件
3 - 第1项与第2项
8 - 循环播放直到下一个sndPlaysound
10- 不停止播放

返回值:
0 出错（通常是文件没有找到）
1 成功

上面的文件可以很好的播放声音，在Windows NT与Windows 95你还可能使用OLE来播放声音:

ole_snd.InsertFile("snd.wav")
ole_snd.Activate(InPlace!)


在EXE文件中注册OCX部件

你是否遇到过这样的问题：一个带有OCX部件的应用程序在开发环境与你的计算机上运行得很好，但当你发行你的程序并安装到用户的机器上时，OCX不能正常工作了？

这个问题现OCX的工作方式有关：每个OCX部件在其constructor事件中存取系统注册表中它自已的信息。这由OCX容器通过调用在每个OCX控件中的DLLRegisterServer函数来实现。

问题是PowerBuilder并没有调用这个函数，即使你运行REGSRV程序, OCX也不能注册它自己。为了解决这个问题， 在你需要使用OCX部件的对象你需要在其constructor事件中调用 DLLRegesterServer函数。

在容器对象中定义一个局部的外部函数
Function long DllRegisterServer() Library "ocxname.OCX"
在constructor事件中编写如相的代码

LONG ll_RC
ll_RC = DllRegisterServer()


 让应用程序只运行一次

创建一个非可视化用户对象n_cst_win32sdk，该对象有以下的局部函数与外函数:外部函数:
FUNCTION boolean ShowWindow( ulong winhandle,& int wincommand ) Library "user32" FUNCTION boolean BringWindowToTop( ulong HWND )& Library "user32" FUNCTION long FindWindowA( ulong Winhandle, & string wintitle ) Library "user32" 局部函数 :
Of_FindWindowByTitle( string as_wintitle ) Return FindWindowA( 0, as_wintitle ) Of_ShowWindow( unsignedlong al_winhandle ) Return ShowWindow( al_winhandle, 5 ) Of_BringToTop( unsignedlong al_winhandle ) Return BringWindowToTop( al_winhandle ) 2. 创建一个带有close按钮的测试窗口w_tes，窗口标题为First Window3. 在应用程序的Open事件中增加如下代码:n_cst_win32sdk lnv_sdk
long ll_winhandle

If Not IsValid( lnv_sdk ) Then
lnv_sdk = Create n_cst_win32sdk
End if
ll_winhandle = &
lnv_sdk.of_FindWindowByTitle( "First Window" )
If ll_winhandle > 0 Then
lnv_sdk.of_BringToTop( ll_winhandle )
lnv_sdk.of_ShowWindow( ll_winhandle )
destroy lnv_sdk
Return
End If
Open( w_sdktest )注释：如果找到w_test窗口，表示应用程序已经执行，直接将它带到屏幕的最上层即可。


 高亮显示分组条

PowerBuilder提供了一种使用函数的方法来高亮度显示数据窗口中细节栏，但有时你需要高亮度显示分组条及细节栏，这时你需要增加一个long类型的实例变量（假设为Il_LastRow)，并且在数据窗口中增加一个额外的列（假设为highlight)，并在rowfoucs事件增加以下代码：

SetItem( il_LastRow, 'highlight', 0 )
SetItem( currentrow, 'highlight', 1 )
il_LastRow = CurrentRow

然后在细节栏中增加一个矩形框，并将下列表达式加入到背景色之中:if( sum( highlight FOR GROUP 1 ) > 0, 65535, 16777215)


查看DataStore中的内容与变化

当你在调试一个带有datastore的过程时，如果知道datastore中的内容以及它在整个处理过程中是如何变化的是非常有用的。

一个好的方法是在测试程序时是写一个自动实例化的非可视用户对象，该用户对象带有一个函数，在函数中你可能打开一个带有数据窗口控件的窗口，然后将DataStore中的内容映射到窗口中的数据窗口控件之中，即数据窗口与DataStore共享数据，这样你就可以查看DataStore中的内容及变化！


 使可编辑的数据窗口只读

有时，你可能需要将一个用于编程的数据窗口用作显示目的，可以有以下几种方法来实现这一要求：

1。建立两个数据窗口，一个用于编辑，一个用于显示

2。将数据窗口中的全部对象的taborder设置为0.

3。也可以将数据窗口中的列的protect属性设为On或Off,但你不能使用protect属性进行编辑操作

4。数据窗口有一个将datawindow设为只读的属性(readonly)，使用它最方便。


 引用其他行的数据

在数据窗口的表达式画板中如果能引用其他行的数据将对编程有很大的帮助！当你使用列名建立表达式时，使用的是当前行的数据，PB提供了强大的相对行引用功能来执行表达式计算功能，也就是说，你可能使用相对引用的方法来引用其他行的数据。

例如，在客户与订单列表中，你可能需要检查客户名称是否改变并用一个图标来表明它是否被改变，你不需要在第一张订单前建立一个分组条来显示客户名称，而且有时你不能使用分组条。

可以在列名后跟一个数据下标的方法与使用这种特性，只是这里你应该使用相对于当前行的相对下标，如下的例子：

bitmap( if( customer[ -1 ] = customer, '', 'smile.bmp' )


 PowerBuilder之我见 飞马

    我是1997年接触PowerBuilder（以下简称PB）的，对它可谓“一见钟情”。从刚开始接触PB，我就觉得它是数据库方面最优秀的开发工具之一。无论是它面向对象的特性，还是它特有的PowerScript语言和“数据窗口”，都让我感到心动。 　　我的感受：回想起最初学习PB的时候，给我留下印象最深的是，我发现自己常常会不由自主地转入到原先学习FoxPro或Visual FoxPro的思路中去，拿FoxPro或Visual FoxPro下编程的思维方式来考虑PB的问题，这使得我在很长的一段时间内无法理顺整个开发环境中各种画笔（PB中特有的概念）、部件、控件、事件的相互关系以及它们是如何相互配合,使我觉得太难了。我感觉到这样下去是肯定不行的，应该及时作出调整，重新寻找一个学习的切入点。 　　我仔细考虑了很久，决定从PB的程序示例Example Application入手。因为这个示例是PowerSoft公司自己的开发人员所编制的应用示例，其中从窗口设计到菜单设计、从控件脚本到应用脚本、从数据窗口到后台数据库的动态管理都非常直观地展现在我面前。在这个思路下，我很快就了解了用PB开发管理系统的流程，掌握了PB的PowerScript语言、各种对象、各种控件、数据窗口的基本应用，进而掌握了各种系统函数，最后终于熟练地运用它们完成了我第一个PB软件的开发。这个时候才感觉自己好像眼前迷雾忽然散去，一片清晰，真是“柳暗花明又一村”！ 　　上手指南：要真正掌握PB，就一定要用心学好“数据窗口”控件，它是PB中特有的控件，是PB的精髓所在。对数据库进行的各种操作绝大多数情况下它都能够非常完美地完成。只有真正学会和掌握“数据窗口”控件的各种应用，你才算得上一名PB高手！ 　　当然，在学习的过程中，参考有关PB的好书是少不了的，按照书上的章节一步一步、稳扎稳打地学习，这样往往能使你事半功倍。 　　学习提示：对于初学者而言，在学习PB的过程中，一定要寻找一个自己的最佳切入点，要用心学习“数据窗口”这个控件，要学会充分利用PB自带的帮助文件，遇到难题时，不要被它所吓住，要多问，多看看在线帮助文件或者到网上去寻找你所需要的各种信息和帮助。衷心祝愿各位学习PB的朋友能够“早成正果”，使自己达到一个更高的境界！ 　　 一、基本概念 　　1.Power Builder(以下简称PB)开发环境的组成 　　PB Painters（画板）：画板是给开发者提供完成窗口结构（如窗口、菜单、数据窗口）设计的工具。 　　PB Libraries（库）：PB开发的应用系统中，所有应用对象（包括应用、窗口、菜单、数据窗口、自定义函数等）均放在应用库（.pbl）中。 　　PB Database（数据库） 　　Toolbars（工具条）：每个画板上的图像按钮。 　　PB Deployment Kit（应用工具箱） 　　Online Help（在线帮助） 　　2.PowerScript语言 　　PowerScript是PB使用的程序设计语言，有点类似于VB与C的混合物，非常简洁高效。 它提供了一套完整的嵌入式SQL语句并提供了丰富的数据类型和众多的函数。 　　3.事件 　　事件是PB应用系统开发平台中一个非常重要的概念。在PB环境中，应用系统的运行是由事件来驱动的，例如你单击一个命令按钮，这个按钮的某个事件中的PowerScript脚本就会被执行。PB编程就是编写事件脚本。 　　二、基本要素 　　1.应用 　　PB把一个解决实际问题的应用程序叫做Application，即应用。应用对象是进入到应用库中其它对象的入口点，通过在Application级编写代码，可以展开一个程序框架。 　　2.窗口 　　窗口（Window）是PB中主要的用户界面，它构成了PB与用户间交互式的信息传递。窗口由事件、各种控件和它本身的属性组成。窗口中常用控件有：CommandButton（命令按钮）、DataWindow（数据窗口控件）、DropDownListBox（下拉列表框）、EditMask（编辑掩码控件）、Graph（统计图控件）、GroupBox（组框）、ListBox（列表框）、Tab（标签）、MultiLineEdit（多行编辑框）、OLEControl（OLE控件）、Picture（图片框）、SingleLineEdit（单行编辑框）、StaticText（静态文本框）、TreeView（树型视图）等。 　　3.数据窗口 　　数据窗口（DataWindow）是PB为解决用户数据同数据库中数据之间的转换问题而提供的一种比较完善的解决方案，是PB最强大的功能之一。对于不太熟悉SQL的开发人员来说，DataWindow可自动生成SQL语句，不需开发人员编程就可操纵数据。它可以直接管理数据库操作，而最终用户通过已建好的DataWindow即可获得所需的信息。 　　4.菜单 　　PB有很强的菜单功能，提供了下拉链连式菜单、按钮式菜单、图标按钮式菜单及结合式菜单。 　　5.用户对象 　　用户对象是拓展PB功能的最灵活对象之一。利用用户对象，我们既可以扩展系统原有对象的功能，增加新的使用方法，又能够创建出可重用的自定义部件，在一个或多个应用程序中反复使用，缩减开发和维护的时间，进一步提高应用程序的开发效率。同时，对速度敏感的部分可以用C＋＋开发，然后将其嵌入到PB应用程序中。 　　三、重要的技术点 　　1.PB与数据库的连接 　　PB支持各种常见的数据库，PB与数据库的连接建立在驱动程序之上。对于Sybase、Oracle、Informix这样的大型数据库管理系统，PB提供了旨在提高数据库访问效率的专用数据库接口。对小型数据库（如Xbase、Access、Excel）和支持ODBC接口的数据库(如IBM DB2)，PB提供了ODBC接口。 　　2.数据窗口 　　PB的成功在很大程度上归功于它拥有专利技术的数据窗口。学习过程中要注意数据窗口控件与数据窗口对象的区别。在数据窗口对象设计的中，有两个基本概念，一是数据源，指数据是选自哪个数据库的哪个（些）表中。PB提供了5种数据源：Quick Select、SQL Select、Query、External、Stored Procedure。二是数据的表现方式，即数据以何种方式呈现在用户前，有如下几大类：Freeform、Tabular、Grid、Label、Group、Graph、N－Up、Cross Tab、OLE2.0、RichText、Composite。 　　数据窗口控件也有一组属性、事件和函数，不过它们的数量比较庞大。 　　应用程序中运用数据窗口的一般方法都是相似的，基本步骤为： 　　a. 使用数据窗口画笔创建数据窗口对象； 　　b. 在窗口上放置数据窗口控件； 　　c. 通过属性设置或编码将数据窗口控件与数据窗口对象相关联； 　　d. 针对具体应用，编写数据窗口控件事件的事件处理程序，如显示数据、响应用户操作。 精彩网站 　　1.Sybase中国 http://www.sybase.com.cn 　　Sybase中国的官方网站，也就是PB的老家了。提供PB补丁程序、直连驱动程序下载、PB讨论区（BBS），有什么问题尽管在这里问吧，还提供BBS资料的下载，PB编程常见的问题在这都能找到答案。 　　2.PowerBuilder精品编程站点 http://pb7.nethome.com.cn 　　非常优秀的一个PB个人主页，有高质量的PB编程文章和程序范例，不愧叫PB精品编程站点。精彩栏目如下： 　　自给自足：站长回答的问题，以及制作的一些方便编程的工具，都给出了具体的例子且提供下载。 　　编程天地：绝对经典的栏目，此站的点睛之笔！提供大量实用PB例程、OCX控 件、PB资料的下载，使你的PB应用程序更加专业，Cool！ 　　PB论坛：PB的问题及解答，人气很旺，有问题、没问题的都去看看吧，相信会有收获的。 　　DataWindow：关于DataWindow的一些使用技巧。 　　数据库：数据库相关技巧和常见问题。 　　3. PowerBuilder程序员资源站点 http://www.pbdr.com 　　国外一家非常好的PB站点，PB技术文章、资源，包括数据窗口、数据库、API调用、PowerScript语言等等，内容丰富且提供下载。 好书推荐 　　入门级 　　《PowerBuilder 6.0开发人员指南》 　　《PowerBuilder 6.0用户参考手册》 　　出版：北京希望电脑公司 　　作者：张长富、李匀、严苏娅 　　关于PB的两本好工具书，其中《开发人员指南》内容详细，编排合理，每一章均配有实例练习。《用户参考手册》采用按对象介绍其属性、事件、函数的方法，编排清晰，并有大量示例，同时在书尾给出了全部函数的索引，方便读者查阅，适用于各级读者。 　　中级 　　《PowerBuilder6.0应用与开发》 　　出版：清华大学出版社 　　作者：吴洁明 　　一本相当不错的PB工具书，全面系统地介绍了PB6.0的应用与开发技巧。全书分章节讲述了对数据库的操作、窗口对象中各控件的使用方法、数据窗口的应用、用户对象和用户事件以及OLE的技术和应用。此书另一大特色就是有一章专门介绍了PB开发Internet应用和分布式应用的原理和方法。 　　《PowerBuilder6.0开发指南》 　　出版：电子工业出版社 　　以一个精心设计的完整实例贯穿全书，具体而清晰的介绍了PB6.0的应用开发方法，详尽描述了PB6.0的各种工具及其使用方法，并且深入介绍了PB6.0的高级开发应用技术。 　　高级 　　《PowerBuilder 6.0／6.5开发与参考手册》 　　出版：晓通数据库 　　一套关于PB应用开发的骨灰级工具书。全套共9本，包括：《PowerBuilder 6.0／6.5用户指南》（卷1、卷2）、《PowerBuilder 6.0／6.5函数、事件和属性精解》（卷1、卷2）、《PowerBuilder 6.0／6.5实用开发技巧》、《PowerBuilder 6.0／6.5开发范例》、《PowerBuilder 6.0／6.5 Internet／Intranet解决方案》、《PowerScript程序设计指南》、《PowerBuilder开发中的数据库设计》。 　　可以说这套书涉及PB编程的方方面面，包括PB应用开发中的重点、难点、开发技巧等等。其中，关于如何使用PowerDesign 6.0设计数据库书，是市面上少有的关于此软件的中文书籍，还有PB的Internet／Intranet解决方案，使熟悉PB的程序员可以快速地转到当前热点的Web应用开发上来。 （文/柯建勋）


 PB打印机选择列表的分析与实现
江苏省姜堰市第五中学 李中华
---- Word的打印对话框中提供了一个选择打印机的下拉列表，但是我们在PowerBuilder编程中常用的是PrintSetup函数打开的Printer Setup设置窗口，在该窗口中，我们可以在列表框中选择我们所要的打印机。然而很遗憾的是这个Printer Setup窗口是英文界面。对于我们开发的纯中文界面的应用程序，其中却有着这样一个很重要的然而却是英文界面的窗口，我想，大多数程序员都是不能容忍的。PowerBuilder的打印控制是最难控制的，在网上见到的许多疑难问题大都与打印有关，但无论如何，我们的中文应用程序还是应该向用户提供这样一个中文的打印机选择窗口。 ---- 做一个类Word的打印对话窗口并不复杂，PowerBuilder的example程序中有完整的做好的窗体，PFC中也有类似的内容。将上面的英文文本及提示改为中文就行了。但该打印窗口没有提供象word一样的打印机选择下拉列表框。我们必须自己做一个。 ---- 通过对Windows 9X系统进行分析，我们能够知道Windows的打印机的信息一般存放在注册表及Win.ini文件中。这些信息分别位于系统注册表的HKEY_CURRENT_CONFIG\System\CurrentControlSet\ Control\Print\Printers下及win.ini的[windows]及[Devices]段下。在注册表中每一个打印机在上面的目录下均有一个以打印机为名的主键条目，Win.ini的[Devices]也一样。在注册表上述目录下，有default字符串值指示当前的默认打印机；win.ini中的[windows]中device的值对也一样对应了当前的打印机。笔者通过下面几个简单的试验，判别出起决定作用的是win.ini文件而非注册表信息。 ---- 试验一：将注册表中上述目录的default的打印机值对改变为其它打印机，再运行printsetup函数，发现没有产生任何反应。而将win.ini文件中的[windows]下的devices的值对改变一下(哪怕胡乱输入几字符)，再运行printsetup，打印机选择列表中立即体现出了相应的变化。 ---- 试验二：在注册表上述目录下添加一个主键，运行printsetup，没有发现打印机列表框中增加打印机，而在win.ini文件的[Devices]下随便增加一个，则printer setup窗口中立即增加了所输入的打印机。 ---- 因此，应从win.ini文件入手编制打印机选择下拉列表框。 ---- 可是，PowerBuilder提供了RegistryKeys能从注册表中某一主键下子所有主键条目的列表，却没有提供任何函数从ini文件中取相似的信息。当然，我们可以自己编一个，将win.ini文件内容一行一行地读入某字符串，使之与“[Device]”进行匹配判断，若是再继续向下读至某一行头一个字符为“[”的行并经过复杂的字符串处理以取得打印机列表(注：纯PB开发下)。但我们知道，windows增加打印机有专门的程序，而且，该程序会自动地同时在注册表及win.ini文件中同时增加打印机。因此我们可以这样入手：从注册表中取得打印机的列表信息，而从win.ini文件中取得及设置当前打印机信息。这样一来，问题就简单得多了。 ---- 经过以上分析论证，下面就开始做了。根据一般做法，我们会将该列表框封装成一个标谁的用户对象，以后开发时，只需在某窗体中调用这一用户对象就行了。 ---- 步骤一：新建一可视（visual）的标准（standard）的用户对象，在Select Visual Standard Type对话框中选择DropDownListBox（下拉列表框），点击“OK”按钮进入设计画面。 ---- 步骤二：开始编程。 ---- １、定义两个实例变量 string is_windows_dirctory,is_winini_file 声明一API函数用以取windows目录 FUNCTION ulong GetWindowsDirectoryA(ref string wdir, ulong buf) LIBRARY "kernel32.dll" ---- ２、定义两个函数： 函数一：ouf_get_windows_dirctory() 用途：取windows目录。 参数：无。 返回值类型：string。 函数体： string ls_WindowsDirctory ulong lu_Buf lu_Buf = 144 ls_WindowsDirctory = Space(144) GetWindowsDirectoryA(ls_WindowsDirctory,lu_Buf) return ls_WindowsDirctory 函数二：ouf_get_printer() 用途：取打印机信息并增加至下拉列表框。 参数：无 返回类型：boolean 函数体： string ls_printer[] //定义一个字符串型数组 integer li_rtn,li_ArrayUp,li_step li_rtn = RegistryKeys("HKEY_LOCAL_MACHINE\System \CurrentControlSet\Control\Print\ Printers", ls_printer) //从注册表中取其列表 IF li_rtn = 1 THEN li_ArrayUp = UpperBound(ls_printer) //取列表数组上限 IF li_ArrayUp >= 1 THEN FOR li_step = 1 TO li_ArrayUp additem ( ls_Printer[li_step] ) //向下拉列表框中增加打印机 NEXT ELSE return false END IF ELSE return false END IF return true 3、在constructor事件下编写下列代码： boolean lb_PrinterExists string ls_printer,ls_tmp int li_point is_windows_dirctory = ouf_get_windows_dirctory ( ) is_winini_file = is_windows_dirctory + "\win.ini" lb_PrinterExists = ouf_get_printer ( ) //向下拉列中增加打印机 IF NOT lb_PrinterExists THEN messagebox ("提示！","尚未安装打印机！") ELSE ls_tmp = profilestring (is_winini_file, "windows","device","") //取当前打印机信息 //由于win.ini信息中该信息还包括有其它参数如端口， 因此必须进行截取处理。 ls_tmp = trim (ls_tmp) li_point = pos (ls_tmp,",",1) ls_printer = left (ls_tmp,li_point - 1) IF trim (ls_printer) <> "" AND NOT IsNull (ls_printer) THEN this.text = trim (ls_printer) //设置下拉列表框文本为当前打印机 ELSE selectitem ( 1 ) END IF END IF ---- ４、在selectionchanged事件中输入： string ls_printer ls_printer = profilestring (is_winini_file,"Devices", trim(this.text (index)),"") //从win.ini文件中取当 前打印机完整信息(含端口及其它) //在win.ini文件及注册表中更新当前打印机 setprofilestring (is_winini_file,"windows","device", trim(this.text (index)) + "," +ls_printer) RegistrySet( "HKEY_CURRENT_CONFIG\System \CurrentControlSet\Control\Print\Printers", "Default", trim(this.text)) ---- ５、最后保存该用户对象(如保存为nvo_ddlb_of_printer)即可。


 Powersoft资料库在系统开发中的应用
军械工程学院计算机室 谭月辉
---- 在PowerBuilder开发环境中，存在一个Powersoft资料库，它由五张表组成，记录了用户在开发过程中，创建或修改一张表属性的情况，如定义或修改表中各列的标签和列标题，有效性规则、显示格式 和编辑风格等。巧妙地使用这些表格，可减轻系统开发的工作量，提高开发效率，系统在运行过程中也易于维护。 ---- 这五张表及分别存贮的信息如下：PBCatCol每列的名称、标题、标签及位置信息 ；PBCatEdt列的编辑风格及定义；PBCatFmt列的显示风格及定义；PBCatVld列的有效性规则及定义。下面介绍的是利用PBCatCol和PBCatEdt表，用户无须建立代码表，可实现由代码值求显示值的功能。 ---- 在PBCatCol表中涉及的主要列及其含义如下： pbc_tnam:表名称 , pbc_cnam:表中某列的名称,pbc_edit:该列所对应的编辑风格;在PBCatEdl表中涉及的主要列及其含义如下： pbe_name:编辑风格名称,pbe_edit: 编辑风格可能的值包括代码、显示值或下拉式数据窗口的显示字段名、值字段名等，pbe_type：编辑风格的种类,如87表示下拉列表，88表示下拉式数据窗口等，pbe_seqn每个编辑风格的值的序号。不同的编辑风格在表中有不同的存贮格式。对下拉列表，第一个记录为开始的标志，而后各行依次存放下拉列表的各代码值、显示值；而下拉数据窗口，通常有三个记录，三个记录中的Pbe_edit列的值分别为数据窗口对象名称、代码字段名，显示值字段名。根据上述特性就可实现对任一给定表，已知某一列的代码值，求出其显示值。 ---- 该功能由一个函数完成，输入参数为：表名、列名、代码值、存储显示值的字符串变量。返回值为整型，等于0未转换，大于0转换成功。实现方法：首先利用表名、列名在PBCatCol表中找到该记录，取出pbc_edit列的值，它表示该列定义的编辑风格的名称，如果为空表示未定义编辑风格，无须进行转换；否则，到PBCatEdl表中查找该编辑风格，按Pbe_seqn顺序找到第一个记录，从该记录中取出pbe_type列值，以判断编辑风格的类型。如果该值等于”87”， 表示编辑风格为下拉列表，转换方法：从PBCatEdl表中按Pbe_seqn列的序号依次取出Pbe_edit列的值，Pbe_edit列按照先显示值后代码值的顺序依次存诸下拉列表的每个条目，一旦找到代码值即可确定显示值；等于”88”，表示编辑风格为下拉数据窗口，转换方法：从PBCatEdl表中按Pbe_seqn列的序号依次取出三个记录中的Pbe_edit列的值，它们分别为提供数据源的数据窗口对象名称、用于存储的列名称，用来显示的列名称，而后对该数据窗口对象进行查找即可得到和存储的代码值对应的显示值。 ---- 附程序清单(Powerbuilder 6.0运行通过) //返加值等于False:未转换,True:转换成功 //fielddisp变量为reference类型的字符串变量， 用于返加显示值。 string s1,s2,vpbe_type,val1,val2 long ll_found boolean findflag datastore ds,ds1 findflag=False //取出指定表、列的编辑风格 SELECT "pbcatcol"."pbc_edit" INTO :s1 FROM "pbcatcol" WHERE ( "pbcatcol"."pbc_tnam" = :tabname ) AND ( "pbcatcol"."pbc_cnam" = :fieldname ) ; IF s1="" THEN RETURN findflag //未指定编辑风格 END IF //取出编辑风格数据 DECLARE t1 CURSOR FOR SELECT "pbcatedt"."pbe_edit" , "pbcatedt"."pbe_type" FROM "pbcatedt" WHERE "pbcatedt"."pbe_name" =: s1 ORDER BY "pbcatedt"."pbe_seqn"; OPEN t1; FETCH t1 INTO :s2 ,:vpbe_type; CHOOSE CASE vpbe_type CASE "88" //建立数据存储，用于表示下拉式数据窗口对应的数据窗口对象 ds=CREATE datastore ds.dataobject=s2 ds.SetTransObject(sqlca) ds.Retrieve() //存取显示值、存储值的列名 FETCH t1 INTO :val1 ,:vpbe_type; FETCH t1 INTO :val2 ,:vpbe_type; CLOSE t1; //在数据窗口对象中进行查找 s2=val1+"="+"'"+fieldvar +"'" ll_found = ds.Find(s2,1,ds.RowCount()) IF(ll_found>0 ) THEN //取出给定代码值的显示内容 fielddisp=ds.GetItemString(ll_found,val2) findflag=true END IF DESTROY ds CASE "87" FETCH t1 INTO :s2 ,:vpbe_type; //从下拉列表取数据 DO WHILE (sqlca.sqlcode = 0 AND NOT Findflag) fielddisp=s2 //当前显示内容 FETCH t1 INTO :s2,:vpbe_type; //当前显示内容对应的代码 IF(sqlca.sqlcode <> 0) THEN EXIT END IF IF s2=fieldvar THEN //等于给定代码值 findflag=true CONTINUE END IF FETCH t1 INTO :s2,:vpbe_type ; LOOP END CHOOSE CLOSE t1; RETURN findflag


 Powerbuilder中游标的应用
山东胶州 孙亚冰
---- Powerbuilder以其独特的体系结构，优异的性能，越来越受到广大软件开发人员的青睐。Powerbuilder所提供的许多技术，使用起来都令人得心应手，游标（Cursors）技术，便是其中之一。游标是一个与T-SQL SELECT 语句相关的符号名，它使用户可逐行访问由SQL Server返回的结果集。游标允许程序对由查询语句SELECT返回的行集合中的每一行执行相同或不同的操作，而不是对整个行集合执行同一个操作；提供对基于游标位置的表中的行进行删除和更新的能力。 ---- 一、游标的用法：
---- 二、下面以一具体示例详细讲解游标的使用技术
---- 示例环境为：欲将工程表Project中的工程名称name字段与列表控件相捆绑，以实现相关数据的动态刷新。 ---- 1． 创建一名为project的数据表：
---- CREATE TABLE project ( id numeric(5), name varchar(50)) ；
---- 其中id为项目编号、name为项目名称。
---- 2． 新建一窗口 w_project，在窗口画板工作区增加一名为ddlb_name的下拉式列表框（DropDownListBox），
---- 3．在w_project的open事件中编写如下代码： int li_id string ls_name //游标声明 declare lcur_name cursor for SELECT name FROM project; //游标打开 open lcur_name; if sqlca.sqlcode < 0 then MessageBox ("DataBase Error!", sqlca.sqlerrtext, exclamation!) return end if ddlb_name.SetRedraw(false) do while sqlca.sqlcode = 0 //提取 fetch lcur_name into ：ls_name； if sqlca.sqlcode = 0 then //将提取的结果，逐行添加到下拉式列表框 ddlb_name.AddItem (ls_name) elseif sqlca.sqlcode < 0 then MessageBox ("DataBase Error", sqlca.sqlerrtext, exclamation!) return else exit end if loop ddlb_id.SetRedraw(true) //关闭游标 CLOSE lcur_name； //释放游标 DEALLOCATE CURSOR lcur_name； ---- 由此可见，游标作为面向集合的数据库管理系统（RDBMS）和面向行的程序设计之间的桥梁，使这两种处理方式通过它沟通起来。


 数据从文本文件写入数据库表的捷径
    许多情况下,文本文件中的数据排列顺序与数据库表中列的顺序
一致,并且其数据类型与数据库表中对应列也一致,要做的就是将该文
件中的内容存入对应数据库表中。例如在点对点通信的情况下,一方
把数据库表中的数据以文本文件格式存储并传送给另一方,另一方要
做的工作就是将收到的数据存入相应的数据库表中。PowerBuilder有
多种方式与文本文件打交道,比如使用ODBC的文本文件驱动器、采用D
DE(动态数据交换)方式、采用OLE方式或使用Cu rsor将文本文件逐行
逐列读取并写入数据库表等。但还有一种更快捷的方法:使用ImportF
ile()函数将文本文件内容直接倒入数据库表中。该函数用法为:
    datawindowname.ImportFile(filename{,startrow{,endrow &
    {,startcolumn{,endcolumn{,dwstartcolumn}}}}})
    ImportFile()函数要求数据窗列的数据类型和列的排列顺序必须
与文本文件中的数据相匹配。
    在存储文本文件时要注意接收数据库对表的格式要求,比如在Syb
ase中,表格的列与列间是采用Tab键区分的,所以如果要用ImportFile
()函数将文件内容写入Sybase表中,就要将相应文本文件的列与列间
加入Tab键——这在文件形成时即可加入。
    下面的代码将文件infor.txt写入数据窗dw-infor,并将与dw-inf
or对应的数据库表更新:
    int impt
    if (FileLength("c:\data\infor.txt") > 0) then
      impt = dw-infor.ImportFile("c:\data\infor.txt")
      if impt > 0 then
      dw-infor.settrans(sqlca);
    update(dw-infor);
    commit;
    if sqlca.sqlcode = -1 then
       messagebox("SQL ERROR",sqlca.sqlerrtext)
    end if
      else
        messagebox("注意","文件infor.txt写入失败!")
      end if
    end if
    此外,ImportFile()函数还支持从文本文件的指定开始行列到结
束行列读取并写入数据;并且支持.dbf(dBase)文件的读写,这给原有
台式数据库数据文件向新的分布式数据库表的转换带来了极大的方便
。


Retrieve后不回卷的数据窗

    我们经常面对一大堆数据,其具体体现就是数据窗很长,需要拉动

垂直滚动条才能看到后面的数据,当在包含长数据窗的窗口的Timer事

件中写下Retrieve( )语句后,令人气恼的事情就会发生:Timer事件一

执行,数据窗就翻回第一页;如果Timer事件执行的时间间歇很短,那我

们就永远没有足够的时间来察看后面的数据了。下面我们着手解决这

个问题。

    每个数据窗都拥有两个与Retrieve有关的事件:Retrievestart和

Retrieveend,它们分别允许我们在Retrieve的前后干一些事,这正是

我们所需要的。实际上,就这两个事件,我们已经能够提出两个解决方

案了。其一,在Retrievestart事件中,保存当前数据窗中可见的数据

行;然后Retrieve;接着在Retrieveend事件中,恢复先前保存的数据行

。其二,在Retrievest art事件中,保存当前垂直滚动块的位置,Retri

eve后再恢复其位置。后者使用了动态数据窗函数,实现起来更简洁一

些,下面详细探讨。

    假设已设计好了一个在窗口w-datamon中的数据窗dw-datamon,现

在可以先定义一个保存垂直滚动块位置的类型为string的Globle变量

old-vspos,然后在该数据窗的Retrievestart 事件下输入以下语句以

保存其位置:

    old-vspos = this.dwDescribe("DataWindow.VerticalScrollPo

sition")

    dw-datamon.SetRedraw(false)

    在相应的Retrieveend事件下输入恢复垂直卷滚块位置的语句:

    this.dwModify("DataWindow.VerticalScrollPosition = " + o

ld-vspos)

    dw-datamon.SetRedraw(true)

    这样,数据窗上的工作已做完。下面是相应窗口上的工作。

    该窗口的Open事件下:

    dw-datamon.Settrans(sqlca)
    dw-datamon.Retrieve( )

    timer(6)

    该窗口的Timer事件下:

    Setfocus(w-datamon)

    Retrieve(dw-datamon)

    至此,Retrieve后不会回卷的数据窗dw-datamon已经可以工作了

。值得注意的是,数据窗的排序分类等操作应在Retrieve前就在数据

库表中完成,否则Retrievestart事件保存的卷滚块位置很可能并不是

我们所期待的,换句话说,Retrievestart事件应发生在所有数据窗操

作之后;另外,在每次Retrieve后,应将处于该数据窗上的Focus移开,

以免具有焦点的数据窗的第一行第一列总要显示,故在窗口w-datamon

的Timer事件中设置了Setfocus(w-datamon)这条语句


 在PowerBuilder中创建图标栏应用

国防科技大学理学院 陈玉教
---- 在一些应用埸合，常需要在Windows 9.X或Winnt中的图标栏中显示出公司/应用程序的图标，在单击图标时弹出相应的窗口，就象金山词霸一样。下面介绍采用PowerBuilder中在Windows 9.x和Windows NT下显示图标栏图标的方法。
一、 建立一个应用程序
---- 在创建程序前，先准备一个用于在图标栏中显示的图标文件(.ICO)，并准备一个用于弹出窗口显示的图片(BMP 256色)。启动PowerBuilder，创建一个应用(Application)，取应用名Starsoft(任意)，注意采用模板生成却缺省窗口。 二、 创建要显示的图标窗口
---- 创建一个用于显示成图标的窗口，取名为W_systray，再在上面放置一张BMP图片，式样如下图所示(略)
---- 设置其它窗口的有关信息，如窗口名等。
三、 编程
---- 1、定义全局外部函数
---- PowerBuilder本身并不提供用于在图标栏中把窗口显示成图标的函数，但可以通过Windows API调用来实现，有关在PowerBuilder调用相应的API函数如下(定义在全局外部函数中)：
Public Function Integer Shell_NotifyIcon (Long dwMessage, Any lpData) Library "shell32" Alias For "Shell_NotifyIconA"
Public Function Long LoadImage (Long hInst, String lpsz, Long un1, Long n1, Long n2, Long un2) Library "user32" Alias For "LoadImageA"
Public Function Long DestroyIcon (Long hIcon) Library "user32" Alias For "DestroyIcon"
Public Function Long SetForegroundWindow (Long hwnd) Library "user32" Alias For "SetForegroundWindow" Public Function Long OpenIcon (Long hwnd) Library "user32" Alias For "OpenIcon"
---- 2、应用入口
---- 在Application的Open Scripts中，输入下列函数：
---- Open (win_systray) ---- 3、窗口函数和Scripts
---- 在W_systray窗口中，创建一个名称为notifyicondata的结构，定义如下：项目 数据类型 Cbsize Long Hwnd Long Uid Long Uflags Long Ucallbackmessage Long Hicon Long Sztip any
---- 再定义三个函数：
---- 1、AddToTray()，参数：无；返回值：布尔值(True/False) // Boolean AddToTray ()
Any nid
if hIcon = 0 then // the icon has not been loaded yet
hIcon = LoadImage(0, "SysTray.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE) end if
if hIcon = 0 then MessageBox ("错误", "不能加载图标!")
Return False
else nid = SetNotifyIconData (Handle (This), 0, NIF_MESSAGEorNIF_ICONorNIF_TIP, WM_MOUSEMOVE, hIcon, "TestTip")
Shell_NotifyIcon (NIM_ADD, nid)
Return True
end if
---- 2、RemoveFromTray ()；参数：无；返回值：无 // RemoveFromTray () Any nid
nid = SetNotifyIconData (Handle (This), 0, NIF_MESSAGEorNIF_ICONorNIF_TIP, 0, hIcon, "")
Shell_NotifyIcon (NIM_DELETE, nid)
if hIcon < > 0 then DestroyIcon (hIcon)
hIcon = 0
---- 3、SetNotifyIconData (Long hWnd, Long ID, Long Flags, Long CallbackMessage, Long Icon, String Tip)； 返回值：notifyicondata // NotifyIconData
SetNotifyIconData (Long hWnd, Long ID, Long Flags, Long CallbackMessage, Long Icon, String Tip)
Char MyTip [64]
NotifyIconData NidTemp
NidTemp.cbSize = 88 // Len (NidTemp)
NidTemp.hWnd = hWnd
NidTemp.uID = ID
NidTemp.uFlags = Flags
NidTemp.uCallbackMessage = CallbackMessage
NidTemp.hIcon = Icon MyTip = Tip + Char (0)
NidTemp.szTip = MyTip
return NidTemp
最后，在窗口的有关事件中输入函数： Open 事件
if AddToTray () then Visible = False
Close 事件
RemoveFromTray ()
CloseQuery 事件
if CanClose then Return 0 else Visible = False
Return 0
End if
Resize事件
if sizetype = 1 then Visible = False
四、 测试
---- 完成上述工作后，就可以运行并加以测试了。进入运行，在图标栏中就注册了一个图标，单击就弹出窗口，窗口示例如下图所示(略)：
---- 这时，最小化该窗口，不会在Windows 9.x或Windows NT的任务栏中显示，而是缩小成一个在图标栏中的图标。
---- 上述示例在Windows 9.8、Windows NT V4.0和PowerBuilder　6.5下测试通过。


PB数据窗口实现报表输出

褚健耘

    PowerBuilder是目前非常流行的一种开发工具,其独到之处是数据窗口(DataWindow)技术。可以说,数据窗口是PowerBuilder的核心,在对数据库的开发过程中,无论是数据的录入、查询、统计还是报表的输出,都离不开数据窗口。笔者在实际工作中用打印数据窗口的方法实现报表的输出(见图1)，方法如下：

    图1 报表的数据窗口

    一、建立含有如下控件的窗体:

控件 | name | text
checkbox | cbx_1 | 预览
dropdownlistbox | ddlb_1 | 100
dropdownlistbox | ddlb_orientation | 横向
dropdownlistbox | ddlb_papersize | 宽行纸
commandbutton | cb_1 | 打印

    二、“打印预览”检查框的clicked事件:

    string Is—count

    if this.checked then

     dw—1.Modify(″datawindow.print.preview=Yes″)

     ddlb—zoom.enabled=TRUE

    else

    dw—1.Modify(″datawindow.print.preview=No″)

     ddlb—zoom.enabled=FALSE

    end if

    三、“缩放”下拉窗口的selectionchanged事件:

     dw—1.Modify

    (″datawindow.print.preview.zoom=″＋this.text)

    四、“纸张方向”下拉窗口的selectionchanged事件:

    string Is—orient

    choose case Lower(Trim(this.text))

     case ″横向″

     Is—orient=″1″

     case ″纵向″

     Is—orient=″2″

     case ″默认″

     Is—orient=″0″

    end choose

    dw—1.Modify(″datawindow.print.orientation=″＋Is—orient)

    五、“纸张大小”下拉窗口的selectionchanged事件:

    string Is—paper=″0″

    choose case Lower(Trim(this.text))

     case ″宽行纸″

     Is—paper=″5″

     case ″A3纸″

     Is—paper=″1″

     case ″A4纸″

     Is—paper=″9″

     case ″默认″

     Is—paper=″0″

    end choose

     dw—1.Modify(″Datawindow.print.paper.size=″＋Is—paper)

    六、“打印”按钮的clicked事件:

    dw—1.print()

    运行环境:Windows NT4.0、Windows 98、PowerBuilder 6.0。


PB中窗口自动居中

李晶

    在计算机软件开发的过程中，经常遇到计算机显示器的分辨率设置不一样的情况。如在程序设计中不注意，可能造成程序界面在不同分辨率下，显示不一致的现象。为了解决这个问题，我设计了一段程序，在每一个Windows的窗口打开之时，窗口自动位于屏幕中央。 　　下列代码放在Window对象的OPEN事件之中:
/ Description: This will position the window in the center of the screen. 　　 Return: Boolean / 　　 //声明
Environment envir
Integer li_ReturnCode 　　 // Turn redraw off
This.SetRedraw(False) 　　 // 获得屏幕分辨率的大小
li—ReturnCode = GetEnvironment(env) 　　 // 把窗口移动到屏幕中央 　　 li—ReturnCode=This.move((PixelsToUnits(envir.ScreenWidth, XPixelsToUnits!) － This.Width)/2 , ＆(PixelsToUnits(envir.ScreenHeight, YPixelsToUnits!) － This.Height)/2 ) 　　 // Turn redraw on 　　 This.SetRedraw(True)
IF li—ReturnCode = 1 THEN 　�
� Return 1
ELSE
Return 0
END IF


在PowerBuild函数中使用SQL语言

汪祥

    有时我们需要在程序中直接使用SQL语言直接操作数据库, 如读、写一条记录甚至多条记录，下面介绍一种方法，即在PowerBuild函数中使用嵌入式SQL语言：

    目的：在一个列表框中显示数据库中数据表中特定列的内容。

    函数参数设计：数据库表名，列名，事务名，列表框标志位，列表框名 。

    应用程序详细设计：

    1. 建立名为a_sqltest的应用对象放在sqltest库中

    2. 创建窗口对象，名称及其控件属性如表1所示

    表1. 窗口对象属性

对象／控件 | 属性 | 值
windows | name titlebar type | w－sql－test “嵌入式sql函数演示” main
singlelineedit | name auto hscroll name auto hscroll | sle－table－name true sle－column－name true
listbox | name vscroll bar hscroll bar | lb－data true true
checkbox | name text checked | cbx－list “显示内容清除标记” true
comamnd button | name text | cb－show “显示＂
static text | name text name text | sle－1 “数据库表名＂ sle－2 “数据库列名＂

    3. 建立上述窗口函数，名称及其属性如下表2所示，参数分别表示数据库表名、列名、事务名及反映显示列表框是否在检索数据前清除上一次显示内容，函数返回所读取记录的总行数。

    表2. 嵌入式SQL函数属性

属性 | 值 | 数据类型
 | name | w－sql－test
 | returns | long
argument | as－table－name | string
argument | as－column－name | string
argument | at－trans | transaction
argument | ab－data－clear | boolean
argument | alb－list－name | listbox

    4. 把下列脚本写入函数，一般情况下SQL语句只可以从数据库中检索一条记录，但当需要一次读取多条记录时，就需要使用数据库游标。利用游标建立一个SQL语句的结果集（游标打开时发生），然后利用Fetch命令一次从结果集中取得一行数据。本函数基于参数传过来的数据库表名/列名建立一个Select语句，并声明一个游标，读取多行数据。在Fetch命令中，把数据库行数据放入变量ls－column中，注意在该命令中应在变量前加“：”以表明是变量而非列名。接着使用列表框函数Additem（）把取到的行数据放入框中。循环执行直至事务对象的SQLcode指示有错误发生（取得所有数据）。最后关闭游标并把取到的行数返回给调用者。

     Long ll－rows－number

     String ls－column－name, ls－select

     //给字符串赋值，用于确定检索的数据库表及其列名

     ls－select = "Select " ＋ as－column－name ＋ " From " ＋ as－table－name

     //声明一个数据库动态游标，存放检索的结果集

     DECLARE lc－cur DYNAMIC CURSOR FOR SQLSA;

     PREPARE SQLSA FROM :ls－select;

     //如果列表框标志位为真则清空列表框

     If Ab－data－clear Then Alb－lis－name.Reset()

     //打开动态游标一次取一行数据

     OPEN DYNAMIC lc－cur;

     //获得第一行并把数据赋与字符串

     Fetch lc－cur Into : ls－column－name;

     //连续读取结果集中数据直到结束，并把数据加入到列表框中

     Do While at－trans.SQLCode = 0

ll_rows－number ＋＋

Alb－list－name.AddItem (ls－column－name )

Fetch lc－cur Into : ls－column－name;

     Loop

     //关闭动态游标

     Close lc－cur;

     Return ll－rows－number

    5. 关闭并保存上述函数，在窗口“检索”按钮的clicked事件中写入下列脚本来调用上述函数。

    Long ll－all－rows

    ll－all－rows = Wf－sql－test(sle－table－name.Text, ＆

    sle－column－name.Text, SQLCA , cbx－list.checked ,lb－data)

    6. 关闭并保存窗口，在应用对象中的Open事件中写入脚本以便打开上述窗口。Open (W－SQL－TEST)

    7. 存储应用对象, 然后运行程序。


怎样刷新下拉式数据窗口

李金山

当我们运行PB数据库管理程序时，可能会出现这样的要求，当在一个窗口中向数据库加入一条记录，这时，使用与这个数据库相关下拉式数据窗口的窗口必须刷新其中的下拉式数据窗口。为了达到这个目的，需要给所有的窗口增加一个用户事件——ue_refresh_dddw。然后，给frame增加一个用户事件 ——ue_refresh_all_dddw，当第一个窗口保存数据时，触发事件ue_refresh_all_dddw！

ue_refresh_dddw的代码:

long l_rc
long l_col, l_colcount
string s_temp
datawindowchild dwc

// Get the Total number of columns
s_temp = dw_1.Object.Datawindow.Column.Count
l_colcount = long(s_temp)

// Cycle through all columns looking for datawindow children.
for l_col=1 to l_colcount
s_temp = dw_1.describe('#'+string(l_col)+'.name')
l_rc = dw_1.GetChild(s_temp, dwc)
if l_rc = 1 then
dwc.SetTransObject(SQLCA)
dwc.Retrieve()
end if
next

ue_refresh_all_dddw的代码:

window wSheet

string wName

// Get the First sheet
wSheet = this.GetFirstSheet()
Do While IsValid(wSheet)

// There is an open sheet
wSheet.TriggerEvent('ue_refresh_dddw')

wSheet = this.GetNextSheet(wSheet)
LOOP

查看英文版请看http://www.magicnet.net/purepower


介绍一些datawindow 未定义的 events:

pbm_dwclosedropdown:
This event occurs whenever a dropdown column is closed;

pbm_dwescape:
User pressed the escape key (within the datawindow control);

pbm_dwnbacktabout:
Datawindow has lost the focus with a Shift+Tab key combination, this is, it happens when the focus is in the first tab sequence column and the user presses Shift+Tab;

pbm_dwndropdown:
This happens whenever a dropdown column is opened;

pbm_dwngraphcreate:
This event occurs when a graph datawindow is being created;

pbm_dwnmousemove:
This event is a special mousemove event for the datawindow object, it has the following parameters:
- XPos: Mouse horizontal position, relative to the object, measured in PBU's;
- YPos: Mouse vertical position, relative to the object, measured in PBU's;
- Row: Row beneath the mouse pointer;
- dwo: Datwindow object beneath the mouse pointer;

pbm_dwnprocessenter:
User has pressed the enter key (within the datawindow control);

pbm_dwnrbuttondblclk:
Occurs when a datawindow is doubleclicked with the right mouse button;

pbm_dwnrbuttonup:
Occurs when the right mouse button is released over the datawindow control;

pbm_dwntabupout:
Occurs whenever the upper arrow is pressed (on a single line editable column);

pbm_dwntabupdown:
Occurs whenever the down arrow is pressed (on a single line editable column);

pbm_dwntabout:
Datawindow has lost the focus with the Tab key, this is, occurs whenever the focus is in the last column of the tab sequence and the Tab key is pressed;

pbm_dwscrolllinehome:
This event happens when the datawindow control gains the focus with the Tab key;


 OLE 在PB 数 据 窗 口 中 的 实 现
江 苏 省 盐 城 市 社 会 劳 动 保 险 处 丁 铖
---- OLE(Object linking and Embedding), 即 对 象 连 接 与 嵌 入 简 称, 是 在Windows 环 境 下 实 现 不 同Windows 实 用 程 序 之 间 共 享 数 据 和 程 序 功 能 的 一 种 方 法。 在PowerBuilder（ 简 称PB） 的 应 用 中 实 现OLE, PB 作 为OLE Container, 在 其 中 调 用OLE Server, 利 用OLE Server 提 供 的 功 能 来 显 示 和 操 纵OLE 对 象。
---- PB 的 数 据 窗 口(DataWindow) 支 持OLE2.0。 在 其 中 可 建 立Blob(Binary Large Object 二 进 制 大 型 对 象) 或image 类 型 的 字 段, 这 种 字 段 的 数 据 为 一OLE 对 象。 由 于 数 据 窗 口 支 持OLE2.0, 因 此 在 使 用 时 你 需 要 保 证 你 要 用 到 的OLE Server 也 要 支 持OLE2.0 对 象。 用 户 或 程 序 可 以 激 活 该OLE 字 段， 并 且 可 以 向OLE Server 发 送 简 单 的 命 令， 在PB 的 数 据 窗 口 中, 你 可 以 建 立OLE 字 段。 一 个OLE 字 段 可 以 完 成 如 下 功 能: 在 数 据 库 中 存 储OLE 对 象, 比 如Excel 电 子 表 格 或Word for Windows 的 文 本 文 件。 把OLE 对 象 从 数 据 库 中 提 取 到 数 据 窗 口。 使 用OLE Server, 比 如Excel 或Word for Windows 来 修 改 数 据。 将 修 改 过 的 数 据 存 入 数 据 库。
---- 下 文 将 结 合 一 个 例 子 解 释 如 何 完 成 上 述 功 能。 在 数 据 库 中， 建 一 个 表 中， 该 表 有 一 个image 类 型 的 字 段， 用 于 存 放Windows Paintbrush 编 辑 的bmp 图, PB 可 在 数 据 窗 口 中 提 取 该 表 的 记 录， 并 把image 字 段 中 的bmp 图 显 示 在 窗 口 中， 然 后 通 过 在 数 据 窗 中 双 点(Double Click) 该 图， 自 动 打 开Paintbrush 并 调 出bmp 图, 用 户 可 对 它 进 行 一 些 修 改 和 保 存 修 改 结 果。
---- 具 体 实 现 步 骤 如 下:
---- 一. 建 立OLE 字 段
---- 在 本 例 中, 我 们 就 建 立 一 个 表Paint， 内 含2 个 字 段, 一 个 字 段 名 为ID, 另 一 个 字 段 名 称 作File。 进 入PB 的Database 画 笔 来 建 此 表。ID 字 段 取type 为integer, 不 许 为 空;File 字 段type 为 Image， 可 为 空, 并 选ID 为 主 键. 注:File 字 段 的 数 据 类 型 即 为 所 谓Blob 类 型, 但 不 同DBMS 中 取 的 名 字 不 同, 如watcom SQL 称 为long birary, 而SQL Server 为Image 或Text。
---- 二. 设 置 Autocommit
---- 在 使 用SYBASE SQL Server 数 据 库 时, 含 有OLE 字 段 的DataWindow 所 使 用 的Transaction Object( 事 务 对 象) 中 的AutoCommit 项 必 须 置 为TRUE, 在 设 计DataWindow 前, 可 通 过Preference 画 笔, 选 择Database 图 标, 将 其 中 的Autocommit 项 赋 值 为1.
---- 三. 设 计 数 据 窗 口 ---- 在DataWindow 画 笔 中, 打 开 建 好 的Paint 表, 但 此 时 只 选 择ID 字 段 进 行 显 示, 而 先 不 要 选 择file 字 段( 我 们 将 在 设 计 窗 口 中 再 添 加 此 字 段)。DataWindow 的 设 计 窗 口, 从Objects 菜 单 中 选 择OLE Database Blob 项, 然 后 在 设 计 窗 口 中 适 当 位 置 点 鼠 标 左 键, 此 时Database Birary/Text Large Object 对 话 窗 显 示 出 来。 逐 项 填 写 对 话 窗：
---- 在Name 项, 填 入 适 当 名 字, 此 项 可 选, 但 起 了 名 字 后, 可 使 你 在 程 序 中 引 用。
---- 在Client Class 中 填 入DataWindow, 可 选 。
---- 在Client Name 中 填 入 当 前DataWindow 的 名 字, 可 选 。
---- 在Table 中 选 择Paint 表 。
---- 在Large Binary/Text Columns 中, 选 择file 字 段 。
---- 在Key Clause 中, 选id=:id, 该 项 用 于 构 造where 子 句, 即where id=:id, 用 于 定 位 相 应 记 录。
---- 在File template 中, 填 入 一 个bmp 文 件 名, 或 通 过Browse 键 来 查 找 一 个 文 件, 比 如 我 们 选 择arcade.bmp。
---- 在OLE Class,Description (Only Class is Used) 中, 通 过 下 拉 菜 单 选 择Paintbrush。
---- 在Client Name Expression: 中, 填 入 某 一 字 符 串 表 达 式, 该 表 达 式 将 OLE Server 运 行 时, 显 示 在 其Title 处, 如 填 入".BMP File"+String(id) ---- 按OK 键 。
---- 此 时, PB 关 闭 该 对 话 窗, 并 回 到DataWindow 的 设 计 窗 口,OLE 字 段 以 一 个 小 方 框 的 形 式 显 示 出 来 。 一 般 情 况 当 你 运 行 该DataWindow 时, 上 述 小 方 框 不 显 示 出 来。 因 此, 为 了 识 别 该OLE 字 段 在DataWindow 中 的 位 置, 我 们 可 以 在 该 小 方 框 后 摆 放 一 个 可 以 识 的 东 西, 如 一 个 椭 圆 等 等, 因 此 在DataWindow 显 示 时, 我 们 只 要 双 点 该 椭 圆, 即 可 激 活OLE Server。
---- 四. 预 浏 览(Preview)OLE 对 象
---- 在DataWindow 的 设 计 窗 口 下, 点 按Preveiw 图 标, 然 后 点 按Insert 图 标, 在Paint 表 中 添 加 一 行, 填 入id, 然 后 双 点 椭 圆, 此 时Paintbrush 被 激 活。 同 时 打 开arcade.bmp， 做 一 些 修 改, 然 后 从Paintbrush 的File 菜 单 中 选 择Update 项, 来 更 新 你 的 修 改, 此 时OLE Server 将 你 的 修 改 信 息 送 回DataWindow, 然 后 通 过 选 择File 菜 单 中 的Close 项 关 闭Paintbrush 并 回 到DataWindow, 此 时 在 你 的DataWindow 中OLE 字 段 所 在 处 显 示 出 了Paintbrush 所 画 的 图 标。 按Upd DB 图 标, 将OLE 数 据 存 入 数 据 库 中. 以 后, 当 你 从 库 中 查 出Paint 表 中 的 记 录 时, 你 可 以 通 过 双 点OLE 对 象 来 操 作 它, 方 法 同 上 。


用PowerBuilder 5.0 开 发DDE 应 用 程 序

zhou_haoli@ccw.com.cn

---- 摘 要： PB 5.0 具 备 快 速 开 发DDE 服 务 器 应 用 程 序 和DDE 客 户 应 用 程 序 的 功 能， 只 须 掌 握 少 量 几 个 关 于DDE 的 系 统 函 数 和 窗 口 事 件 便 可 以 了。

---- 关 键 字：DDE（ 动 态 数 据 交 换，Dynamic Data Exchange)，DDE 服 务 器 应 用 程 序(DDE Server Application),DDE 客 户 应 用 程 序(DDE Client Application)， 冷 链 接(Cold Link), 温 链 接(Warm Link), 热 链 接(Hot Link).

---- 在WINDOWS 环 境 中， 可 以 同 时 运 行 多 道 应 用 程 序，DDE 是 这 些 应 用 程 序 之 间 进 行 数 据 交 换 的 方 便 方 法。 提 供 数 据 的 应 用 程 序 叫 做DDE 服 务 器 应 用 程 序， 接 收 数 据 的 应 用 程 序 叫 做 客 户 应 用 程 序， 下 面 分 别 简 称 为 服 务 器 和 客 户。DDE 包 括 三 种 链 接 方 式，(1) 冷 链 接，(2) 温 链 接，(3) 热 链 接。 下 面 分 三 种 情 况 分 述。

---- ( 一) 冷 链 接。

---- 在 服 务 器 和 客 户 之 间 只 有 单 个 命 令 时 使 用。

---- 服 务 器 端 的 编 程：

---- 在 这 种 方 式 下， 服 务 器 不 能 主 动 地 通 知 客 户 自 己 的 数 据 变 化， 只 能 被 动 地 在 被 触 发Rem-oteExec,RemoteRequest,RemoteSend 事 件 之 后 在 这 些 事 件 的Script 中 对 客 户 进 行 回 应。 不 能 在 其 它 地 方 调 用DDE 函 数 来 主 动 地 与 客 户 联 络。 不 可 能 触 发 热 链 接 的 事 件RemoteHotLinkStart, RemoteHotL-inkStop, 当 然 属 于 客 户 的 事 件HotLinkAlarm 也 不 可 能 触 发。

---- (1) 在 某 个 窗 口 的OPEN 事 件 或 其 它 的 事 件 中 调 用StartServerDDE 函 数 来 指 定 此 应 用 程 序 充 当 服 务 器， 该 窗 口 称 为 服 务 器 窗 口。

---- (2) 在 服 务 器 窗 口 的RemoteExec 事 件 中 调 用GetCommandDDEOrigin,GetCommandDDE 函 数 来 获 取 客 户 发 来 的 命 令 及 命 令 源 ， 调 用RespondRemote 函 数 来 表 示 命 令 可 否 接 受， 并 对 命 令 进 行 处 理。 该 事 件 是 由 客 户 调 用 ExecRemote 函 数 触 发。

---- (3) 在 服 务 器 窗 口 的RemoteSend 事 件 中 调 用GetDataDDEOrigin,GetDataDDE 函 数 来 获 取 客 户 发 来 的 数 据 及 数 据 源， 调 用RespondRemote 函 数 来 表 示 数 据 可 否 接 受， 并 对 数 据 进 行 处 理。 该 事 件 由 客 户 端 调 用SetRemote 函 数 触 发。

---- (4) 在 服 务 器 窗 口 的RemoteRequest 事 件 中 调 用SetDataDDE 函 数 来 向 客 户 发 送 数 据， 并 调 用RespondRemote 函 数 来 通 知 客 户 是 否 应 允 这 个 请 求。

---- (5) 在 想 结 束DDE 时 调 用 StopServerDDE 函 数 中 止。

---- 客 户 端 的 编 程：

---- 在 这 种 方 式 下， 客 户 端 只 能 调 用ExecRemote,GetRemote,SetRemote 三 个 函 数 分 别 来 向 服 务 器 发 送 命 令， 请 求 数 据， 发 送 数 据， 不 会 有RemoteExec,RemoteSend,RemoteRequest,RemoteHot-LinkStop,RemoteHotLinkStart,HotLinkAlarm 等 任 何DDE 事 件 被 触 发。 在 这 种 方 式 下， 因 为 通 讯 的 双 方 没 有 建 立 通 道， 客 户 在 做 出 通 讯 请 求 后WINDOWS 将 轮 流 查 询 所 有 打 开 了 的 应 用， 直 到 得 到 回 应 为 止， 故 这 种 方 式 的 效 率 较 低。

---- 注 意：PowerBuilder 的Application 对 象 的 属 性DDETimeOut 决 定 了 做 为 客 户 的 应 用 程 序 向 服 务 器 请 求 回 应 的 最 大 等 待 时 间。

---- ( 二) 温 链 接。

---- 在 服 务 器 和 客 户 之 间 不 只 有 单 个 命 令 时 使 用。 服 务 器 端 的 编 程： 完 全 和 冷 链 接 一 样。

---- 客 户 端 的 编 程：

---- 大 致 和 冷 链 接 一 样， 具 体 区 别 如 下：

---- (1) 先 调 用OpenChannel 函 数 打 开 通 道， 再 开 始 调 用 上 面 所 说 的 那 三 个 函 数 与 服 务 器 通 讯。

---- (2) 通 讯 完 之 后 调 用CloseChannel 关 闭 通 道。 (

---- 3) 调 用ExecRemote,SetRemote,GetRemote 函 数 时 使 用 温 链 接 的 语 法 格 式。

---- (4) 因 为 打 开 了 专 门 的 通 道， 故WINDOWS 不 必 轮 流 查 询 所 有 打 开 的 应 用 程 序， 效 率 会 比 冷 链 接 高。

---- ( 三) 热 链 接。

---- 在 服 务 器 端 的 数 据 有 变 化 时 服 务 器 主 动 地 通 知 客 户 时 使 用。

---- 服 务 器 端 的 编 程：

---- (1) 最 先 调 用StartServerDDE, 结 束 时 调 用StopServerDDE.

---- (2) 在 数 据 发 生 变 化 时 调 用SetDataDDE 函 数 通 知 客 户， 这 将 触 发 客 户 端 的HotLinkAlarm 事 件。

---- (3) 不 要 在 其 它 的 事 件 中 编 程， 也 不 要 再 调 用 其 它 的 函 数。

---- 客 户 端 的 编 程：

---- (1) 先 调 用StartHotLink 函 数 建 立 热 链 接， 最 后 调 用StopHotLink 中 止。

---- (2) 在 建 立 热 链 接 地 窗 口 的HotLinkAlarm 事 件 中 调 用GetDataDDEOrigin,GetDataDDE 函 数 来 获 得 发 送 来 的 数 据 和 数 据 的 源， 并 对 收 到 的 数 据 进 行 处 理。 再 调 用RespondRemote 回 答 是 否 接 受。

---- (3) 不 要 在 其 它 的 事 件 中 编 程， 也 不 要 再 调 用 其 它 的 函 数。

---- ( 四) 一 个 三 种 方 式 都 可 进 行 的 例 子

---- 这 个 例 子 在WINDOW95，PB 5 环 境 下 调 试 通 过， 你 可 以 同 时 运 行DDESERVER.EXE 和 DDECLIENT.EXE 两 个 应 用， 前 者 为 服 务 器， 后 者 为 客 户， 在 客 户 端 可 以 任 意 地 建 立 一 种 链 接， 并 与 服 务 器 交 换 数 据。 具 体 过 程 如 下：

---- (1) 进 入PB5.0 开 发 环 境， 建 立 一 个 名 字 叫ddeserver.pbl 的 新PBL 库 文 件。

---- (2) 在 刚 建 立 的PBL 中 建 立 一 个 名 字 叫ddeserver 的Application 对 象。

---- (3) 按 下 面 的 界 面 建 立 一 个 新 的 窗 口。

---- (4) 它 是 名 字 叫sle_Name 的 单 行 编 辑 文 本 框。

---- (5) 它 是 名 字 叫sle_Sex 的 单 行 编 辑 文 本 框。

---- (6) 它 是 名 字 叫st_LinkType 的 静 态 文 本 框。

---- (7) 它 是 名 字 叫st_Status 的 静 态 文 本 框。

---- (8) 它 是 名 字 叫cb_Close 的 按 钮。

---- (9) 把 新 建 的 窗 口 以 名 字 w_DDEServer 存 盘。

---- (10) 在ddeserver Application 对 象 的OPEN 事 件Script 中 写 入 语 句：Open(w_DDEServer)

---- (11) 为 窗 口w_DDEServer 定 义 实 例 变 量：

    Protected:
    String 	is_NeedType=name'	//name'
                    //ex'
    String 	is_LinkType=oldLink'//oldLink'
                    //armLink'
                    //otLink'

---- (12) 为 窗 口w_DDEServer 的OPEN 事 件 Script 写 入 下 面 的 程 序：

//============================================================
//	The Window w_DDEServer	Open Event
//	Start DDE Server
//============================================================
Integer li_Temp

//Start This Application As A DDE Server,In This Window
st_Status.Text='Start DDE Server ...... !'
li_Temp=StartServerDDE(w_DDEServer,
'MySampleDDEServer','StudentInformation',&
    'StudentName','StudentSex')

    //The ApplicationName IS 'MySampleDDEServer'
    //The Topic IS 'StudentInformation'
    //The Item Has TWO,'StudentName' And 'StudentSex'

If li_Temp=1 Then

    st_Status.Text='Start DDE Server Successfully !'
Else
    st_Status.Text='Start DDE Server Failed !'
    MessageBox(DDEServer.DisplayName,&
        'Start DDE Server Failed ,Cannot Continue !',&
        StopSign!&
        )
    Halt Close
End If

st_Status.Text='Ready'

---- (13) 为 窗 口w_DDEServer 的CLOSE 事 件 Script 写 入 下 面 的 程 序：

//==========================================================
//	The Window w_DDEServer	Close Event
//	Close DDE Server
//==========================================================
Integer li_Temp

li_Temp=StopServerDDE(w_DDEServer,'MySampleDDEServer',&
    'StudentInformation')

If li_Temp<>1 Then
    Beep(100)
End If

---- (14) 为 窗 口w_DDEServer 的RemoteRequest 事 件 Script 写 入 下 面 的 程 序：

//==========================================================
//	The Window w_DDEServer	RemoteRequest  Event
//	Send Data To The  DDE Client
//==========================================================
Integer li_Temp

st_Status.Text='DDE Client Requesting'

If is_NeedType='Name' Then
    //Name
    li_Temp=SetDataDDE(sle_Name.Text)
    If li_Temp=1 Then
        st_Status.Text='Send Name Data Ok !'
        li_Temp=RespondRemote(True)
    Else
        st_Status.Text='Send Name Data Error !'
        li_Temp=RespondRemote(False)
    End If

Else//Sex
    li_Temp=SetDataDDE(sle_Sex.Text)
    If li_Temp=1 Then
        st_Status.Text='Send Sex Data Ok !'
        li_Temp=RespondRemote(True)
    Else
        st_Status.Text='Send Sex Data Error !'
        li_Temp=RespondRemote(False)
    End If

End If

---- (15) 为 窗 口w_DDEServer 的RemoteSend 事 件 Script 写 入 下 面 的 程 序：

//==========================================================
//	The Window w_DDEServer	RemoteSend  Event
//	Get Data Which Sent From The  DDE Client
//==========================================================

Integer li_Temp

String ls_AppName,ls_TopicName,ls_ItemName
String ls_StudentName,ls_StudentSex

st_Status.Text='DDE Client Sending'

//Get The Data Origin
li_Temp=GetDataDDEOrigin
(ls_AppName,ls_TopicName,ls_ItemName)

If li_Temp=1 Then
    If ls_AppName='MySampleDDEServer' And &
        ls_TopicName='StudentInformation' And &
        ls_ItemName='StudentName' Then
        //Get The Name
        li_Temp=GetDataDDE(ls_StudentName)
        If li_Temp=1 Then
            li_Temp=RespondRemote(True)
            sle_Name.Text=ls_StudentName
        Else
            //Error
            li_Temp=RespondRemote(False)
            st_Status.Text='Get Data Error !'
        End If
    Else
        If ls_AppName='MySampleDDEServer' And &
            ls_TopicName='StudentInformation' And &
            ls_ItemName='StudentSex' Then
            //Get The Sex
            li_Temp=GetDataDDE(ls_StudentSex)
            IF li_Temp=1 Then
                li_Temp=RespondRemote(True)
                sle_Sex.Text=ls_StudentSex
            Else
                //Error
                st_Status.Text='Get Data Error !'
                li_Temp=RespondRemote(False)
            End If
        Else
            //Other DDE Item
            li_Temp=RespondRemote(False)
            st_Status.Text='Other DDE Item'
        End If
    End If

Else
    st_Status.Text='Get DDE Origin Error !'
End If
(16) 为 窗 口w_DDEServer 的RemoteExec 事 件 Script 写 入 下 面 的 程 序：
//==========================================================
//	The Window w_DDEServer	RemoteExec  Event
//	Get Command Which Sent From The  DDE Client And Process
//==========================================================
Integer li_Temp
String ls_Command,ls_AppName

st_Status.Text='DDE Client Sent Command'

//Get The Command Origin
li_Temp=GetCommandDDEOrigin(ls_AppName)

If li_Temp=1 Then
    If ls_AppName='MySampleDDEServer' Then
        //Get The Command
        li_Temp=GetCommandDDE(ls_Command)
        If li_Temp=1 Then
            li_Temp=RespondRemote(True)
            //Process The Command
            CHOOSE CASE ls_Command
                CASE 'BeginColdLink'
            is_LinkType='ColdLink'
              st_LinkStatus.Text='Cold Link'
            st_Status.Text='Begin Cold Link'
    CASE 'BeginWarmLink'
             is_LinkType='WarmLink'
            st_LinkStatus.Text='Warm Link'
            st_Status.Text='Begin Warm Link'
    CASE 'ClearTheName'
            sle_Name.Text=''
            st_Status.Text='Cleared The Name'
    CASE 'ClearTheSex'
            sle_Sex.Text=''
            st_Status.Text='Cleared The Sex'
    CASE 'Name'
            is_NeedType='Name'
            st_Status.Text='Need  The Name'
            CASE 'Sex'
                is_NeedType='Sex'
                st_Status.Text='Need The Sex'

            CASE ELSE
            st_Status.Text='Other Command'
            END CHOOSE

        Else
            //Error
            li_Temp=RespondRemote(False)
            st_Status.Text='Get Data Error !'
        End If
    Else
        //Other App
        li_Temp=RespondRemote(False)
        st_Status.Text='Other Application Command'
    End If

Else
    st_Status.Text='Get DDE Command Origin Error !'
End If

---- (17) 为 窗 口w_DDEServer 的RemoteHotLinkStart 事 件 Script 写 入 下 面 的 程 序：

//==========================================================
//	The Window w_DDEServer	RemoteHotLinkStart  Event
//	Know The Link Type Is HotLink
//==========================================================
is_LinkType='HotLink'
st_LinkStatus.Text='Hot Link'
st_Status.Text='Started Hot Link'

---- (18) 为 窗 口w_DDEServer 的RemoteHotLinkStop 事 件 Script 写 入 下 面 的 程 序：

//==========================================================
//	The Window w_DDEServer	RemoteHotLinkStop  Event
//	Know The Hot Link Stopped
//==========================================================
st_Status.Text='Stopped Hot Link'

---- (19) 进 入sle_Name 的script 画 屏， 定 义 用 户 事 件， 名 字ue_KeyUp,EventID 为

---- pbm_KeyUp

---- (20) 在sle_Name 的 ue_KeyUp 事 件 的Script 中 写 入 下 面 的 程 序：

//==========================================================
//	The Control sle_Name	us_KeyUp  Event
//	Send Data To Client When HotLink
//==========================================================
Integer li_Temp
If is_LinkType='HotLink' Then
    li_Temp=SetDataDDE(This.Text,'MySampleDDEServer',&
        'StudentInformation','StudentName')

    If li_Temp=1 Then
        st_Status.Text='Set DDE Data Name Successfully !'
    Else
        st_Status.Text='Set DDE Data Name Failed !'
    End If

End If

---- (21) 进 入sle_Sex 的script 画 屏， 定 义 用 户 事 件， 名 字ue_KeyUp,EventID 为

---- pbm_KeyUp

---- (22) 在sle_Sex 的 ue_KeyUp 事 件 的Script 中 写 入 下 面 的 程 序：

//==========================================================
//	The Control sle_Sex	us_KeyUp  Event
//	Send Data To Client When HotLink
//==========================================================
Integer li_Temp
If is_LinkType='HotLink' Then
    li_Temp=SetDataDDE(This.Text,'MySampleDDEServer',&
        'StudentInformation','StudentSex')

    If li_Temp=1 Then
        st_Status.Text='Set DDE Data Sex Successfully !'
    Else
        st_Status.Text='Set DDE Data Sex Failed !'
    End If

End If

---- (23) 在sle_Sex 的 ue_KeyUp 事 件 的Script 中 写 入 下 面 的 程 序：

//==========================================================
//	The Control cb_Close	Clicked  Event
//	Close The Window w_DDEServer
//==========================================================
Close(Parent)

---- (24) 建 立 一 个 新 的Project, 名 字 ddeserver, 并 生 成EXE 文 件，ddeserver.exe.

---- (25) 用 类 似 的 方 法 建 立PBL 库 ddeclient.pbl,ddeclient Application 对 象，w_DDEClient 窗 口，ddeclient 工 程 对 象 Project，ddeclient.exe 界 面 如 下：

//==========================================================
//The Control Name List:
//==========================================================
SingleLinkEdit:	sle_Name		sle_Sex

RadioButton:	rb_1	 rb_2	 rb_3

CommandButton: cb_1	 cb_2  cb_3 cb_4
    cb_5  cb_6  cb_7  cb_Close

---- (26) 程 序 如 下：

//============================================================
//	The Window w_DDEClient	Instance Variables Define
//
//
//============================================================
Protected:
String 	is_LinkType=扖oldLink'//扖oldLink'
                //扺armLink'
                //扝otLink'
String 	is_NeedType=扤ame'	//扤ame'
                //扴ex'
Long 	il_ChannelHandle	//Used For WarmLink Channel Handle

//============================================================
//	The Window w_DDEClient	HotLinkAlarm	Event
//	Get Data Sent Form The DDE Sever
//
//============================================================
Integer li_Temp
String ls_AppName,ls_TopicName,ls_ItemName
String ls_Name,ls_Sex//The Get Data From The DDE Server

Beep(300)
li_Temp=GetDataDDEOrigin(ls_AppName,ls_TopicName,ls_ItemName)

If li_Temp=1 Then
    If ls_AppName='MySampleDDEServer' And &
        ls_TopicName='StudentInformation' And &
        ls_ItemName='StudentName' Then

        li_Temp=GetDataDDE(ls_Name)

        If li_Temp=1 Then

            sle_Name.Text=ls_Name
        Else
            st_Status.Text='Get DDE Data  Failed !'
        End If
    Else
        If ls_AppName='MySampleDDEServer' And &
            ls_TopicName='StudentInformation' And &
            ls_ItemName='StudentSex' Then

            li_Temp=GetDataDDE(ls_Sex)
            If li_Temp=1 Then
                sle_Sex.Text=ls_Sex
            Else
            st_Status.Text='Get DDE Data Failed !'
            End If
        Else
        st_Status.Text='Else Item Changes !'
        End If
    End If
Else

    st_Status.Text='Get DDE Origin Failed !'
End If
//=========================================================
//	The Control cb_7		Clicked	Event
//	Send Sex Data
//	Send Sex Data To The DDE Server
//==========================================================
Integer li_Temp
String ls_Sex

SetPointer(HourGlass!)
ls_Sex=sle_Sex.Text
If is_LinkType='ColdLink' Then

    li_Temp=SetRemote('StudentSex',ls_Sex,&
        'MySampleDDEServer','StudentInformation')

    If li_Temp=1 Then
        st_Status.Text='Send Data To Server Successfully !'
    Else
        st_Status.Text='Send Data To Server Failed !'
    End If
Else
    li_Temp=SetRemote('StudentSex',ls_Sex,&
        il_ChannelHandle)

    If li_Temp=1 Then
        st_Status.Text='Send Data To Server Successfully !'
    Else
        st_Status.Text='Send Data To Server Failed !'
    End If

End If

SetPointer(Arrow!)
//===========================================================
//	The Control cb_6		Clicked	Event
//	Send Sex Command
//	Send Sex Command To The DDE Server
//===========================================================
Integer li_Temp
String ls_Command
If is_NeedType='Sex' Then
    Return 1
End If

SetPointer(HourGlass!)
ls_Command='Sex'
If is_LinkType='ColdLink' Then
    li_Temp=ExecRemote(ls_Command,'MySampleDDEServer',&
        'StudentInformation')
    If li_Temp=1 Then
        is_NeedType='Sex'
        st_Status.Text='Execute Send
'+ls_Command+'Command Successfully !'
    Else
        st_Status.Text='Execute Send
'+ls_Command+' Command Failed !'
    End If
Else
    li_Temp=ExecRemote(ls_Command,il_ChannelHandle)

    If li_Temp=1 Then
        is_NeedType='Sex'
        st_Status.Text='Execute Send
'+ls_Command+' Command Successfully !'
    Else
        st_Status.Text='Execute Send
'+ls_Command+' Command Failed !'
    End If

End If

SetPointer(Arrow!)
//==========================================================
//	The Control cb_5		Clicked	Event
//	Send Name Command
//	Send Name Command To The DDE Server
//==========================================================
Integer li_Temp
String ls_Command

If is_NeedType='Name' Then
    Return 1
End If

SetPointer(HourGlass!)
ls_Command='Name'
If is_LinkType='ColdLink' Then
    li_Temp=ExecRemote(ls_Command,'MySampleDDEServer',&
        'StudentInformation')
    If li_Temp=1 Then
        is_NeedType='Name'
        st_Status.Text='Execute Send
'+ls_Command+'Command Successfully !'
    Else
        st_Status.Text='Execute Send
'+ls_Command+' Command Failed !'
    End If
Else
    li_Temp=ExecRemote(ls_Command,il_ChannelHandle)

    If li_Temp=1 Then
        is_NeedType='Name'
        st_Status.Text='Execute Send
'+ls_Command+' Command Successfully !'
    Else
        st_Status.Text='Execute Send
'+ls_Command+' Command Failed !'
    End If

End If

SetPointer(Arrow!)

//=========================================================
//	The Control cb_4		Clicked	Event
//	Send ClearTheSex Command
//	Send ClearTheSex Command To The DDE Server
//=========================================================
Integer li_Temp
String ls_Command

SetPointer(HourGlass!)
ls_Command='ClearTheSex'
If is_LinkType='ColdLink' Then
    li_Temp=ExecRemote(ls_Command,'MySampleDDEServer',&
        'StudentInformation')
    If li_Temp=1 Then
        st_Status.Text='Execute Send
'+ls_Command+'Command Successfully !'
    Else
        st_Status.Text='Execute Send
'+ls_Command+' Command Failed !'
    End If
Else
    li_Temp=ExecRemote(ls_Command,il_ChannelHandle)

    If li_Temp=1 Then
        st_Status.Text='Execute Send
'+ls_Command+' Command Successfully !'
    Else
        st_Status.Text='Execute Send
'+ls_Command+' Command Failed !'
    End If

End If

SetPointer(Arrow!)
//===========================================================
//	The Control cb_3		Clicked	Event
//	Send Name Data
//	Send Name Data To The DDE Server
//===========================================================
Integer li_Temp
String ls_Name

SetPointer(HourGlass!)
ls_Name=sle_Name.Text
If is_LinkType='ColdLink' Then

    li_Temp=SetRemote('StudentName',ls_Name,&
        'MySampleDDEServer','StudentInformation')

    If li_Temp=1 Then
        st_Status.Text='Send Data To Server Successfully !'
    Else
        st_Status.Text='Send Data To Server Failed !'
    End If
Else
    li_Temp=SetRemote('StudentName',ls_Name,&
        il_ChannelHandle)

    If li_Temp=1 Then
        st_Status.Text='Send Data To Server Successfully !'
    Else
        st_Status.Text='Send Data To Server Failed !'
    End If

End If

SetPointer(Arrow!)
//===========================================================
//	The Control cb_2		Clicked	Event
//	Request Data
//
//===========================================================
Integer li_Temp
String ls_Name,ls_Sex

SetPointer(HourGlass!)

If is_LinkType='ColdLink' Then
    //--------------------------------------------------
    If is_NeedType='Name' Then
        //----------------------------------------------
        li_Temp=GetRemote('StudentName',ls_Name,&
            'MySampleDDEServer','StudentInformation')

        If li_Temp=1 Then
            sle_Name.Text=ls_Name
            st_Status.Text='Get Remote Successfully !'
        Else
            st_Status.Text='Get Remote Failed !'
        End If
    Else//Sex
        //----------------------------------------------
        li_Temp=GetRemote('StudentSex',ls_Sex,&
            'MySampleDDEServer','StudentInformation')

        If li_Temp=1 Then
            sle_Sex.Text=ls_Sex
            st_Status.Text='Get Remote Successfully !'
        Else
            st_Status.Text='Get Remote Failed !'
        End If

    End If

Else//WarmLink
    //--------------------------------------------------

    If is_NeedType='Name' Then
        //----------------------------------------------
        li_Temp=GetRemote('StudentName',ls_Name,&
            il_ChannelHandle)

        If li_Temp=1 Then
            sle_Name.Text=ls_Name
            st_Status.Text='Get Remote Successfully !'
        Else
            st_Status.Text='Get Remote Failed !'
        End If
    Else//Sex
        //----------------------------------------------
        li_Temp=GetRemote('StudentSex',ls_Sex,&
            il_ChannelHandle)

        If li_Temp=1 Then
            sle_Sex.Text=ls_Sex
            st_Status.Text='Get Remote Successfully !'
        Else
            st_Status.Text='Get Remote Failed !'
        End If

    End If

End If
SetPointer(Arrow!)
//======================================================
//	The Control cb_1		Clicked	Event
//	Send ClearTheName Command
//	Send ClearTheName Command To The DDE Server
//======================================================
Integer li_Temp
String ls_Command

SetPointer(HourGlass!)
ls_Command='ClearTheName'
If is_LinkType='ColdLink' Then
    li_Temp=ExecRemote(ls_Command,'MySampleDDEServer',&
        'StudentInformation')
    If li_Temp=1 Then
        st_Status.Text='Execute Send
'+ls_Command+'Command Successfully !'
    Else
        st_Status.Text='Execute Send
 '+ls_Command+' Command Failed !'
    End If
Else
    li_Temp=ExecRemote(ls_Command,il_ChannelHandle)

    If li_Temp=1 Then
        st_Status.Text='Execute Send
'+ls_Command+' Command Successfully !'
    Else
        st_Status.Text='Execute Send
'+ls_Command+' Command Failed !'
    End If

End If

SetPointer(Arrow!)

//======================================================
//	The Control rb_3		Clicked	Event
//	Begin HotLink
//
//=========================================================
Integer li_Temp
If is_LinkType<>'HotLink' Then
    If is_LinkType='WarmLink' Then//Warm Link
        li_Temp=CloseChannel(il_ChannelHandle)
    End If

    li_Temp=StartHotLink('StudentName',&
        'MySampleDDEServer','StudentInformation')
    li_Temp+=StartHotLink('StudentSex',&
        'MySampleDDEServer','StudentInformation')
    If li_Temp=2 Then
        is_LinkType='HotLink'
        st_Status.Text='Start Hot Link Successfully !'
        cb_1.Enabled=False
        cb_2.Enabled=False
        cb_3.Enabled=False
        cb_4.Enabled=False
        cb_5.Enabled=False
        cb_6.Enabled=False
        cb_7.Enabled=False
    Else
        st_Status.Text='Start Hot Link Failed !'
    End If

End If
//=======================================================
//	The Control rb_2		Clicked	Event
//	Begin WarmLink
//
//=======================================================
Integer li_Temp
If is_LinkType<>'WarmLink' Then

    //Stop Hot Link
    If is_LinkType='HotLink' Then

        li_Temp=StopHotLink('StudentName',&
            'MySampleDDEServer','StudentInformation')
        li_Temp+=StopHotLink('StudentSex',&
            'MySampleDDEServer','StudentInformation')
        If li_Temp=2 Then
            st_Status.Text='Stop DDE
 Server Hot Link Seccessfully !'
            cb_1.Enabled=True
            cb_2.Enabled=True
            cb_3.Enabled=True
            cb_4.Enabled=True
            cb_5.Enabled=True
            cb_6.Enabled=True
            cb_7.Enabled=True

        Else
            st_Status.Text='Stop DDE
 Server Hot Link Failed !'
        End If
    End If

    //Open Channel
    il_ChannelHandle=OpenChannel('MySampleDDEServer',&
        'StudentInformation')
    If il_ChannelHandle>0 Then
        is_LinkType='WarmLink'
        li_Temp=ExecRemote('BeginWarmLink',il_ChannelHandle)
        st_Status.Text='Start Warm Link Ok !'
        cb_1.Enabled=True
        cb_2.Enabled=True
        cb_3.Enabled=True
        cb_4.Enabled=True
        cb_5.Enabled=True
        cb_6.Enabled=True
        cb_7.Enabled=True

    Else
        st_Status.Text='Open Channel Error !'
    End If

End If
//========================================================
//	The Control rb_1		Clicked	Event
//	Begin ColdLink
//
//=========================================================
Integer li_Temp

If is_LinkType<>'ColdLink' Then

    If is_LinkType='HotLink' Then//Hot Link

        li_Temp=StopHotLink('StudentName',&
            'MySampleDDEServer','StudentInformation')
        li_Temp+=StopHotLink('StudentSex',&
            'MySampleDDEServer','StudentInformation')
        If li_Temp=2 Then
            is_LinkType='ColdLink'
            st_Status.Text='Start Cold Link OK !'
            li_Temp=ExecRemote('BeginColdLink',&
            'MySampleDDEServer','StudentInformation')
            cb_1.Enabled=True
            cb_2.Enabled=True
            cb_3.Enabled=True
            cb_4.Enabled=True
            cb_5.Enabled=True
            cb_6.Enabled=True
            cb_7.Enabled=True
        Else
            st_Status.Text='Start Cold Link Error !'
        End If

    Else//Warm Link
        li_Temp=CloseChannel(il_ChannelHandle)
        If li_Temp=1 Then
            is_LinkType='ColdLink'
            li_Temp=ExecRemote('BeginColdLink',&
            'MySampleDDEServer','StudentInformation')
            st_Status.Text='Start Cold Link Ok !'
            cb_1.Enabled=True
            cb_2.Enabled=True
            cb_3.Enabled=True
            cb_4.Enabled=True
            cb_5.Enabled=True
            cb_6.Enabled=True
            cb_7.Enabled=True

        Else
            st_Status.Text='Start Cold Link Error !'
        End If

    End If

End If
//=========================================================
//	The Control cb_Close		Clicked	Event
//	Close Link And Window
//
//=========================================================
Integer li_Temp
If is_LinkType='WarmLink' Then
    li_Temp=CloseChannel(il_ChannelHandle)

ElseIf is_LinkType='HotLink' Then
    li_Temp=StopHotLink('StudentName',&
        'MySampleDDEServer','StudentInformation')
    li_Temp+=StopHotLink('StudentSex',&
        'MySampleDDEServer','StudentInformation')
End If

If is_LinkType<>'ColdLink' Then
    li_Temp=ExecRemote('BeginColdLink',&
        'MySampleDDEServer','StudentInformation')
End If
Close(Parent)

中国计算机世界出版服务公司版权所有


PB中如何得到Crosstab中的列名

北京市·清河
赵韶平

---- PowerBuilder 是大家公认的、 最佳的数据库前端开发工具之一， 其独到之处，那就是数据窗口(DataWindow)。它能够灵活地组织数据库中的各种数据， 用户能用一个简单的查询窗口就可以得到丰富的查询结果， 并且能够对查询结果集通过程序控制，得出各种用户想得到的最终结果。如果能够熟练地运用数据窗口这一专利技术， 无疑会给开发人员和用户带来巨大的方便。

---- Crosstab是数据窗口中一种用于统计、分析数据的显示风格，其含义为Cross Tabulation,即横向制表。它可以将检索到的数据分类或分组，进行累计求和或求平均值等运算，编制统计表格。

---- 但是，开发者在使用Crosstab数据窗口时会碰到一个问题：如果开发者想对其中的列内容进行处理时，不能用通用的方法得到Crosstab中的列名，使该操作无法运行。这是因为：Crosstab数据窗口中行列定义不同于其它的数据窗口，而且其列数是变化的。因此要得到Crosstab中的列名必须采用其它办法。

---- 下面以一个统计各工资段作者人数，按男女性别分别统计的Crosstab数据窗口静态风格为例探讨得到Crosstab中的列名。

---- 1、选择显示风格，定义数据源后，选择表”auths”中的列”author_code”、”salary”和”sex”；用鼠标拖动Source Data列表框中的“sex”列到Columns列表框中，使“sex”列成为数据窗口的纵向统计列；用鼠标拖动Source Data列表框中的“salary”列到Rows列表框中，使“salary”列成为数据窗口的横向统计列，将salary改为int(salary/100)*100;用鼠标拖动Source Data列表框中的“author_code”列到列表框中，使“count(author_code for Crosstab)”列成为数据窗口的统计值，此时“CrossTab Definition”对话框显示如下：(图略)

---- 2、单击OK按钮，数据窗口显示如下：(图略)

---- 3、Crosstab数据窗口中的列名

---- Crosstab显示风格有静态和动态。如采用静态风格，则在“CrossTab Definition”对话框中取消选中“Rebuild columns at runtime”复选框。数据窗口的列名在Detail栏中，有一定规律可循：

---- 第一栏的列名分两种情况：一、“CrossTab Definition”对话框中的所选的Rows的列名为表中的字段，如Rows=salary则第一栏的列名为”salary”；二、如“CrossTab Definition”对话框中的所选的Rows的列名为计算表达式，如int(salary/100)*100,则第一栏的列名均为”row_column”。

---- 其余各栏依次为所选Value的列名的排列，如Value为author_Code，则各栏依次”author_Code”，”author_code_1”, “author_code_2”,…… 栏的数目根据所选的Columns所能产生的数目而定，如Columns=sex,则只有两栏”author_Code”，”author_code_1”；

---- 最后一列的列名为“grand_count_”+所选Value名,如在此例中为”grand_count_author_code” 。如采用动态风格，则在“CrossTab Definition”对话框中选中“Rebuild columns at runtime”复选框。数据窗口的列名在Detail栏中：(图略)第一栏和最后一列的列名情况类似静态风格所述，也就不雷同了。 中间各栏的列名情况与静态风格有所不同，依次为所选rows列名的排列，如rows为”salary”，在此例中各栏依次为”salary”，”salary_1”；

---- 这样，问题就迎韧而解了。在得到Crosstab中的列名之后也就能自由地对其中的行列内容进行处理。通过上面的介绍，读者其实也能感觉到，计算机中许多不太容易实现的功能，只要好好分析，仔细琢磨，多试验几次，一般还是能找到有效的解决办法的。

中国计算机世界出版服务公司版权所有


如何获得机器的IP地址

Lately, I've got several requests for finding out how to get the current PC's IP-address. Well, it took me quite some time and probably, it doesn't work on every machine. In many cases, the IP-address is stored in a special netwerk file, such as NET.CFG but that's pretty static. These days, IP-address are given from DHCP servers so to get it from an ascii file isn't an option anymore. We searching for a more unified method. Well, all the TCP/IP traffic is done via the WinSock DLL ( at least, that's the most important one ). This DLL provides many TCP/IP functions. Each machine is called a host and you can get the hostname with a special function call...gethostname(). Now, if this function succeeds, we can get the IP-address, related to this hostname. If the gethostname fails, I'm out of luck, that means, I haven't found another way to get the IP-address yet.

Second part is getting the IP-address related to the hostname. For this, another special function is provided by the WinSock dll. To do this, we use the gethostbyname() function, with the hostname as argument. This function returns a pointer to a structure and you probably know that with PowerBuilder, we cannot do a lot more with this pointer. I've tried to manipulate the pointer in Powerbuilder, believe me I tried and tried and tried but no way. We need a DLL, or a C++ class to work with this pointer. Luckily, it's is PowerSoft themselves who are providing this DLL ( and even the C-source ). It comes with one of their application galery examples, namely the FTP example. Basically, what this function does it mapping elements of the structure to PowerBuilder variables.

Now, before we can even use WinSock functions, we must have a valid session. This session is created with the WinSock wsastartup() function. This function uses a structure, the WSAdata structure. It's defined like this:

$PBExportHeader$s_wsadata.srs
global type s_wsadata from structure
unsignedinteger version
unsignedinteger highversion
character description[257]
character systemstatus[129]
unsignedinteger maxsockets
unsignedinteger maxupddg
string vendorinfo
end type

So, create this structure globally and name it s_wsadata. Let's walk through the code:

/* First, declare all the needed variables */
s_wsadata	l_WSAData
string		ls_HostName = space(128)
string		ls_IpAddress
int		li_version = 257
blob{4} 	lb_hostaddress

/* Then, create a session, based on the winsock version. This version number consists of two part, a major and minor release number, both represented in a byte. So, version 1.1 gives us an integer version of 257 ( 256 + 1 ) */
IF wsastartup ( li_version, l_WSAData ) = 0 THEN
/* the wsadata structure contains several information. The description element tells us the winsock version */
    messagebox("Winsock Version", l_WSAData.description )
/* Now, let's find out what the hostname is of the current machine we're working on */
    IF gethostname ( ls_HostName, len(ls_HostName) ) < 0 THEN
        messagebox("GetHostName",WSAGetLastError())
    ELSE
/* With the hostname, call the DLL function and map the IP-address pointers to a PB blob variable, with a length of 4 bytes. This is done because the internal structure contains 4 pointers, each pointer point to one of the parts of the IP-address. An IP-address namely, consists of 4 bytes */
        Messagebox("Hostname", ls_HostName)
        GetHost(ls_HostName, lb_HostAddress)
/* Convert the pointers to scalars, and concatenate them to one string, the IP-address */
        ls_IpAddress = string(asc(string(blobmid(lb_HostAddress,1,1))),"000") + "."
        ls_IpAddress += string(asc(string(blobmid(lb_HostAddress,2,1))),"000") + "."
        ls_IpAddress += string(asc(string(blobmid(lb_HostAddress,3,1))),"000") + "."
        ls_IpAddress += string(asc(string(blobmid(lb_HostAddress,4,1))),"000")

        Messagebox("Ip Address", ls_IpAddress )
    END IF
/* We're finished, clean up the mess we made */
    WSACleanup()
ELSE
    messagebox("GetHostName",WSAGetLastError())
END IF

Right. Now, to get this thing on the road, you need some external functions. For 16bit applications:

function int WSAStartup ( int lVersionRequested, ref s_WSAData w ) library "winsock.dll"
function int WSACleanup () library "winsock.dll"
function int WSAGetLastError () library "winsock.dll"
function int gethostname ( ref string name, int namelen ) library "winsock.dll"
function string GetHost(string lpszhost, ref blob lpszaddress ) library "pbws.dll"

And for 32bit applications:

function int WSAStartup( uint UIVersionRequested, ref s_WSAData lpWSAData ) library "wsock32.dll"
function int WSACleanup() library "wsock32.dll"
function int WSAGetLastError ( ) library "wsock32.dll"
function int gethostname ( ref string name, int namelen ) library "wsock32.dll"
function string GetHost(string lpszhost, ref blob lpszaddress ) library "pbws32.dll"

The pbws.dll and pbws32.dll are the PS DLLs I was talking about. Click on the folder image to download them, very small, including the source code. That should do it. For more information about WinSock programming, see the WinAPI helpfiles and MSDN ( which is alos available on Microsoft's website.

Good Luck!

E-mail: maosd@163.net


如何终止retreive

1. You need to set dbparm = 'Async = 1'
2. Create a instance variable boolean ib_stop = false
3. Write the code to trap escape key in windows's key event. If escape is pressed
set ib_stop = true. ( Escape key is 'KeyEscape!' )
4. Write in retrieverow event
if ib_stop = true then return 1
5. Write in retrieveend event
ib_stop = false
That's OK.

OR:
1. set dbparm = 'Async = 1'
2. Write the code to trap escape key in windows's key event. If escape is pressed
dw_1.DBCancel()

Thay's OK!

E-mail: maosd@163.net


如何运行自己的应用程序

When you make a PowerBuilder executable in the Windows 95 environment, you have to tell Windows where the PowerBuilder run-time DLLs are. In Windows 3.1, you had to add the PowerBuilder deployment kit directory to the path. This will work in Windows 95, too, but I thought we didn't need ugly PATH statements in "autoexec.bat" any more. We don't.

Instead, you edit the registry. (In case you haven't edited the registry before, go to Start, then Run, and type "regedit".) Go to the 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths' section. This section of the registry contains all of the executable programs that are registered with Windows. Select PB050.EXE, for example. You'll see a key on the right called "Path". Right-click, select Modify, and you'll get the path for PowerBuilder 5.0.

To register your own application, create a new key under the App Paths section; add a new "string value" called "path"; and set that path to include all the directories you want Windows to look to find program elements.

For example, I created a new key under App Paths called "MYPROG.EXE"; then I created a new string value called "path"; and I set the value of path to "c:\Program Files\Common Files\Powersoft Shared;c:\SQLANY50\win32". Now when I run MYPROG, Windows finds all of the run-time elements the program needs.

This PureTip comes from Mike Brown, Editor of Pure Power.

E-mail: maosd@163.net


给tab page增加快捷键

The PowerBuilder tab control does not allow keyboard accelerators to access tab pages. Here's a way around it:

Create a new menu item that corresponds to each tab page. Make the menu item invisible. Then give the menu item an appropriate keyboard accelerator. When the user hits the keyboard accelerator, it's going to execute the clicked event of the menu item.

In that menu item's clicked event, trigger a user event on the window:

parentwindow.Event Dynamic ue_some_event()

In the user event, select the tab page:

tab_1.SelectTab(2)

There is one drawback to this solution: you cannot show an underline under the appropriate letter on the tab page as you do with other controls.

This PureTip comes from Mike Brown, Pure Power.

E-mail: maosd@163.net


启动PB时打开库画板

I always work from the library painter. I find that it's much faster to select an object to work on and let PowerBuilder bring up the right painter rather than opening the painter and picking an object. Wouldn't it be nice if PowerBuilder would open the library painter every time I run it?

There's a command-line argument you can give PowerBuilder that will bring up the library painter - or any other painter - when you run PowerBuilder: it's /p library. The graphic below shows the Properties of my PowerBuilder icon.

Another useful paramater is /p appliaction

E-mail: maosd@163.net


取得DOS环境变量值

Sometimes, you need to access DOS environment variables. PB does not provide a
function for this so we have to do this ourself. You could implement this by using a DLL
but I'd rather stick with PowerBuilder. Instead of DLLs, we use the native Windows API
functions. For 16bit, you can't access the environment variables in one go. This is
because a string is returned with '0' characters, representing a delimiter. With PB, this
means 'end of the string'. With a trick, we can jump to the next value in the string. We do
that as long as we have not found what we're looking for. Check it out:
16 bit way
External functions:
FUNCTION long GetDosEnvironment() Library "krnl386.exe"
FUNCTION string AnsiUpper(long lAddress) Library "user.exe"
Create a function for accessing a wanted environment variable. Declaration:
string f_getenvironment ( as_var )
Code:
int iLen
long lRet
string sValue
/* get the 'address' of the first environment variable */
lRet = GetDosEnvironment()
/* add '=' to the environment variable where looking for. This is for searching in the string */
as_Var = Upper(as_Var) + "="
/* get the current lenght of the variable */
iLen = len(as_Var)
/* lRet points to the start of a string. With AnsiUpper(), we get the string */
sValue = AnsiUpper(lRet)
DO WHILE sValue <> ""
/* check if the string is what where looking for. If so, return the part after the '=' sign */
IF left(sValue + "=", iLen) = as_Var THEN RETURN mid(sValue, iLen + 1)
/* else, move lRet to the next variable in the string */
lRet += Len(sValue) + 1
sValue = AnsiUpper(lRet)
LOOP
/* nothing found, return error */
RETURN "error"
?
32 bit way
In 32bit Windows environments, it's more simple because a better Windows API
function is provided (Watch out: CaSeSenSiTiVe):
FUNCTION long ExpandEnvironmentStringsA(
REF String lpszSrc,
REF string lpszDest,
long cchDest ) library "kernel32.dll"
This function replaces in the total environment string, all occurrences idientified by
%ourvariable% with it's according value. Easy, so create the same function like:
string f_getenvironment ( as_var )
and code:
string ls_dest
ls_dest = space(128)
long ll_ret
/* put our wanted variable within the percentage signs */
as_var = '%' + as_var + '%'
/* get it */
ll_ret = ExpandEnvironmentStringsA(as_var,ls_dest,127)
/* check */
IF ll_ret <= 0 THEN
RETURN "error"
ELSE
RETURN ls_dest
END IF
?
That's all there is to it. Enjoy!
E-mail: maosd@163.net


使Sybase SQL Anywhere的启动窗口不出现在任务栏

Sybase SQL Anywhere在 启 动 时 ， 会 在 Win95任 务 栏 出 现 一 个 图 标 ， 如 果 不 小 心 hutdown或 关 掉 此 窗 口 ， 程 序 将 不 能 运 行 ， 这 种 情 况 在 设 计 下 还 不 严 重 ， 但 一 旦 给 用 户 安 装 系 统 后 ， 在 任 务 栏 出 现 窗 口 即 不 美 观 又 不 安 全 ， 还 得 多 废 口 水 给 用 户 解 释 。 按 照 下 面 方 法 可 去 掉 任 务 栏 上 的 提 示 。
在 [控 制 面 板 ]或 PB中 配 置 ODBC， 指 向 你 原 来 设 置 的 数 据 源 ， 然 后 单 击 “ 配 置 ” 按 钮 ， 再 选 择 “ Options...” 按 钮 ， 将 Start Command由 下 面 ：
原 来 语 法 ： c:\SQLAny50\Win32\dbeng50.exe -d -c512
改 为 ： c:\SQLAny50\Win32\dbeng50.exe -d -Q -c512
即 多 加 一 个 -Q （ 注 意 Q一 定 是 大 写 ） 。

陶清


Power Builder同时连接多个数据库

赵韶平

只 需 设 置 两 个 TRANSACTION对 象 即 可 。
以 下 为 同 时 连 接 SQL SERVER和 DBF数 据 库 的 示 例 ：
连 接 SQL SERVER：
SetPointer(HourGlass!)
//窗 口 居 中
f_center(this)
//根 据 sgselect 连 接 不 同 的 套
string sSelect
string temp
sSelect="d_gdzc"+sgselect
SQLCA.DbParm = ProfileString(gzINIFile,"Database","DbParm","")
SQLCA.DBMS = ProfileString(gzINIFile,"Database","DBMS","")
SQLCA.Database = sSelect
SQLCA.ServerName = ProfileString(gzINIFile,"Database","ServerName","")
SQLCA.LogId = "sa"
RegistryGet("HKEY_CURRENT_USER\software\microsoft\windows\currentversion\explorer\g", "a", RegString!,temp )
f_anencrypt (temp,SQLCA.LogPass)
SQLCA.AutoCommit = false
SQLCA.Lock = ProfileString(gzINIFile,"Database","Lock","")
connect using sqlca;

IF SQLCA.SQLCode <> 0 THEN
MessageBox("联 接 失 败 ", &
"不 能 联 接 数 据 库 !(错 误 信 息 :" + SQLCA.SQLErrText+")")
setpointer(Arrow!)
RETURN
halt close
END IF

定 义 函 数 ：
fuction f_connectdbf(string sdbparm) return any
string nul //用 于 生 成 null值
transaction trans_name
trans_name=create transaction
trans_name.DBMS = "ODBC"
//trans_name.Database = "ZWDBF"
//trans_name.LogId =
//trans_name.LogPass =
trans_name.AutoCommit = False
trans_name.DBParm = sDBParm
//trans_name.Lock =ProfileString(gzINIFile,"Database","Lock","")
//trans_name.DbParm =ProfileString(gzINIFile,"Database","DbParm","")
CONNECT USING trans_name;

IF trans_name.SQLCode <> 0 THEN
MessageBox("联 接 失 败 ", &
"不 能 联 接 财 务 数 据 库 ! 请 检 查 财 务 路 径 是 否 正 确 。 (错 误 信 息 :" + SQLCA.SQLErrText+")")
rollback;
return setnull(nul)
else
return trans_name
END IF

连 接 FOXPRO数 据 库 （ 同 时 连 接 第 二 个 数 据 库 ， 还 可 以 连 接 第 三 、 第 四 个 ） ：
openwithparm(w_disp,"正 在 联 接 DBF卡 片 数 据 库 ...",parent)
transaction sqldbf
//用 SQLDBF连 接 财 务 （ 固 资 ） 数 据 库
sqldbf=f_connectdbf("Connectstring='DSN=gzdbf'")
//判 断 是 否 联 接 成 功
if isnull(sqldbf) then
Messagebox("出 错 ","联 接 DBF固 定 资 产 数 据 库 出 错 ！ ")
end if