pb6.5技巧

PowerBuilder程序中的并发控制

并发能力是指多用户在同一时间对相同数据同时访问的能力。一般的关系型数据库都具有并发控制的能力,但是这种并发功能也会对数据的一致性带来危险。试想若有两个用户都试图访问某个银行用户的记录并同时要求修改该用户的存款余额时,情况将会怎样呢?我们可以对PowerBuilder中的DataWindow进行设置来进行并发控制。所谓并发控制就是指在用户数据修改的过程中保证该数据不被覆盖或改变的方式,在下面的例子中我们将看到如何设置DataWindow来控制开发访问。

  公司的某员工在银行前台取款2,000元,银行出纳查询用户的存款信息显示银行存款余额20,000元;正在这时,另一银行帐户转帐支票支付该帐户5,000元,机器查询也得到当前用户存款20,000元,这时银行的出纳员看到用户存款超过了取款额,就支付了客户2,000元并将用户存款改为18,000元,然后银行的另一名操作员根据支票,将汇入的5,000元加上,把用户的余额改为25,000元,那么数据库管理系统是否可以接受这些修改呢?

  在DataWindows的设计中,我们选择菜单RowsUpdate…,会出现Specify Update Characteristics的设置窗口,在这个窗口中我们设置Update语句中Where子句的生成,以此来进行开发控制。在这里有三个选项,我们分别看一看在本例中这三个选项的结果:

  (1)Key Columns:生成的Where子句中只比较表中的主键列的值与最初查询时是否相同来确定要修改的记录。在上述的例子中,转帐支票的操作将覆盖出纳员做出的修改,这样银行损失两千元。

  (2)Key and Updateable Columns:生成的Where子句比较表中主键列和可修改列的值与最初查询时是否相同。在上例中两次查询出的结果都是有两万余额,当第一个人修改余额时,余额仍是二万元,所以修改成立,而支票转帐操作时余额已不是二万,所以该列不匹配,修改失败。

  (3)Key and Modified Columns:Where子句比较主键和将要修改的列,在本例中,结果与Key and Updateable Columns的选择相同,因为余额已改变,不再与最初的查询相同,因此仍然不能修改。

  让我们作另外一个假设,我们把银行后台作支票转帐操作改为冻结用户存款,即把状态字段的值改为冻结,而且事件发生的次序如下面例子所述,前台出纳的修改能不能成立呢:

  (1).Key Columns:Where子句只比较主键值,显然出纳员的修改是允许的。

  (2).Key and Updateable Columns:生成的Where子句包括比较所有可修改的列,因此出纳修改时Statue字段为冻结与出纳查询时的tive不符,修改失败,同时显示错误信息。

  (3).Key and Modified Columns:Where子句的比较包括主键和要修改的列,由于本列中修改列仍为20,000元没有变化,所以出纳的修改可以成立。

  在本例中,我们可以看到Key and Updateable Columns的选项最严格,可以避免出现状态列发生改变时余额作修改的错误,但是这也会禁止我们作一些本当允许的并发修改,如出纳修改存款余额,而业务员修改用户的联系地址等。因此我们应当根据实际情况,选择适当的Update设置。

  根据我们使用数据库的不同,我们还有一些其他的控制并发访问和修改的选择方案,如对数据加锁。锁是一个用户避免其他用户对指定行作修改的操作。在结束一个事务如执行commit,rollback,disconnect等语句时自动将锁释放。如果您使用的DBMS支持锁的操作,在Power-Builder的DataWindow设计时,Select语句可在from子句中加上with holdlock:即在data Window的SQL Window中,在表窗口的标题处点击右鼠标,弹出菜单的最后一个选项即为Holdlock。选择该项,生成的SQL语句将在retrievel()函数执行后将所查询的数据加锁,以避免其他用户的修改访问,直至commit,rollback等事件发生后解锁。这种方式带来的问题是,当用户查询完数据后可能离开计算机长时间不用,这段时间内其他用户均无法修改数据。此外有些DBMS如Sybase等不支持行级锁,也就是说当你对某一行查询时更多的行都被上了锁,这就更增加了并发处理的局限性。另一个值得注意的问题是在多窗口应用中某一个窗口的事务提交将会导致使用一事务中其他数据窗口的查询行解锁,这时修改将可能发生错误。

  某些DBMS系统支持一个称作"时间戳(timestamp)"的数据项来控制并发性。每张表中都有一个时间戳的数据列,当Insert语句或Update语句对数据行作修改时该列自动被修改为当前时间。当你要作修改时,where子句可检查时间戳列在查询时和修改时两个值是否相符,以此来确保您做出的修改不会覆盖别人的修改,因此这种确认方式与key and Updateable Columns选项相同。即使两个用户对同一行的不同列作修改,后一个修改者也将失败。在常用的关系型数据库中Sybase和Microsoft的SQL Server支持时间戳的使用。而在PowerBuilder中,不管用户后台连接何种数据库,只要表中带有timestamp的列名且数据类型为datetime,PB将自动忽略Update characteristics的选项,而在where子句中生成主键和时间戳列的比较。

  如果您所用的数据库不支持时间戳但支持触发器,您也可以在表中增加一列整数型的列。当有对表中某种记录作修改时,该列自动加1。下列使用的是Watcom数据库,对Shipper表增加Updcnt字段并作两个触发器,这样任何用户或进程试图修改某行记录时,该字段均可发生变化。
对INSERT触发器的编写如下:

DROP TRIGGER INS—SHIPPER’
CREATE TRIGGER SHIPPER BEFORE INSERT ON SHIPPER
REFERENCING NEW AS Newvalue
FOR EACH ROW
BEGIN
SET newvalue.UpdCnt=newvalue.UpdCnt+1;
END'

  同理可编写UPDATE触发器。

  在您的PowerBuilder应用之中,除表的主键外,必须再加上这一列作为检测列加入Update语句中的Where子句中,这样再作Update操作时,后台数据库会比较修改时与用户作Retrieve操作时数据是否相等,以确认是否能作修改。在DataWindows中在Specify Update Characteris-tics的对话框的右下角的Unique key column(s)中加上Updcnt一项,同时注意where clause中选择Key columns,这样PowerBuilder在构造where子句时就会认为Updcnt亦是表的主键,而成为检测项。

  当数据窗口的Update函数被调用后,触发器将修改过记录中的Updcnt列表为新值,为保证下一次修改能够有效,您应当立即作Retrieve()以使DataWindow缓冲区中Updcnt的值与数据库相同。显然修改后立即查询的代价要比其他任何一种并发控制的代价要小得多。

 

PB中实现系统热键功能的新方法

本方法可以实现在任何时候,即无论你的窗口是不是当前获得焦点的窗口,还是处于系统图标区,只要用户按下热键,都将触发窗口中的事件,在本例中实现窗口的状态切换。

    1、首先我们必须声明Windows API的外部函数:
  FUNCTION Integer GlobalAddAtom(ref string lpString) LIBRARY "kernel32.dll" ALIAS FOR "GlobalAddAtomA" FUNCTION ulong RegisterHotKey(ulong hwnd,ulong id,ulong fsModifiers,ulong vk) LIBRARY "user32.dll"

  2、接下来,我们必须给一些需要用到的定量赋上初始值。
    Public:
        constant integer MOD_ALT = 1
  constant integer MOD_CONTROL = 2
  constant integer MOD_SHIFT = 4

  3、接下来,我们必须利用下面代码在系统中注册我们要使用的热键:
  //在窗口的Open事件中
  long ll_RC
  string ls_str
  ls_str = "My atom ID"
  atomid = GlobalAddAtom(ls_str) //得到唯一的ID,保证不和其他应用程序发生冲突
  ll_RC = RegisterHotKey(Handle(this), atomid, MOD_ALT + MOD_CONTROL, 65) // 65为'A'
//注册的热键为Ctrl+Alt+A
  if ll_RC = 0 then
  messagebox("错误","错误信息")
  end if

  4、最后,编写当用户按下热键时的处理程序:
  //在窗口的Other事件中
  IF wparam = atomid THEN
      This.Show()
  //在这里编写处理程序
  END IF

低级键盘钩子屏蔽Win键、Alt+Tab键的响应

如果你是基于Windows操作系统做系统集成的,你可能希望你的最终产品独占系统资源。你希望规范用户行为,比如你不希望用户通过按Ctrl+Alt+Del终止某个进程,或者按下Win键弹出开始菜单,

    或者按下Alt+Tab组合键切换到别的应用程序。笔者已有相关一篇文章《Win2K/NT下屏蔽Ctrl+Alt+Del的响应》,介绍了如何通过GINA编程接口屏蔽Ctrl+Alt+Del的响应。作为续篇,本文将继续介绍屏蔽Win键和Alt+Tab组合键的方法。  
  
  由于这些按键的响应是系统级的,我们不可能简单地通过某个程序来控制它们。因此,我们需要使用微软提供的另外一种编程接口——钩子(Hook)。大家可能已经对钩子很了解了(网上有很多介绍钩子技术和应用的文章)。简单来说,钩子是一种通过替换系统提供的标准接口来截获特定的事件(消息),最终达到改变或增强系统默认行为目的的技术。我们现在的任务,就是要在用户按下Win键或Alt+Tab组合键、但系统还没有响应之前截获它们,然后改变系统的默认行为。很显然,我们要做一个全局钩子(钩子函数放在独立的DLL中实现),而且是个低级键盘钩子(Low  Level  Keyboard    hook)。 

  第一步,钩子DLL的实现。我们首先要定义一个全局数据区(记住这是一个全局钩子),如下(放在cpp文件的上头): 

  #pragma  data_seg("mydata") 
  HHOOK            glhHook            =  NULL;                        //  安装的鼠标钩子句柄 
  HINSTANCE    glhInstance    =  NULL;              //  DLL实例句柄 
  #pragma  data_seg() 

  然后在.def文件中声明这个数据区,如下: 
  SECTIONS  
  mydata  READ  WRITE  SHARED 

  当这个DLL被某个进程载入时,程序从WinMain进入,此时我们需要把模块句柄保存下来,如下: 
  glhInstance  =  (HINSTANCE)  hModule; 

  接下去,我们就要定义两个导出函数,以及钩子的处理函数。我们重点看一下这个钩子处理函数(另外两个导出函数比较简单,只是通过调用SetWindowsHookEx和UnhookWindowsHookEx实现安装/卸载钩子函数;只需注意SetWindowsHookEx第一个参数为WH_KEYBOARD_LL,第四个参数为0)。 
  //  低级键盘钩子处理函数 
  LRESULT  CALLBACK  LowLevelKeyboardProc(int  nCode,  WPARAM  wParam,  LPARAM  lParam) 
  { 
         BOOL  fEatKeystroke  =  FALSE; 
         PKBDLLHOOKSTRUCT  p  =  NULL;  

    if  (nCode  ==  HC_ACTION)   
         { 
                 p  =  (PKBDLLHOOKSTRUCT)  lParam; 
                 switch  (wParam)   
                 { 
                         case  WM_KEYDOWN:   
                         case  WM_SYSKEYDOWN: 
                                                 case  WM_KEYUP:         
                         case  WM_SYSKEYUP:   
                 fEatKeystroke  =  (p->vkCode  ==  VK_LWIN)    ¦  ¦  (p->vkCode  ==  VK_RWIN)    ¦  ¦    //  屏蔽Win 
                 //  屏蔽Alt+Tab 
                 ((p->vkCode  ==  VK_TAB)  &&  ((p->flags  &  LLKHF_ALTDOWN)  !=  0))    ¦  ¦ 
                 //  屏蔽Alt+Esc 
                 ((p->vkCode  ==  VK_ESCAPE)  &&  ((p->flags  &  LLKHF_ALTDOWN)  !=  0))    ¦  ¦ 
                 //  屏蔽Ctrl+Esc 
                 ((p->vkCode  ==  VK_ESCAPE)  &&  ((GetKeyState(VK_CONTROL)  &  0x8000)  !=  0)); 
                 break; 
                         default: 
                                 break; 
             } 
         }  
 
  return  (fEatKeystroke  ?  TRUE  :  CallNextHookEx(glhHook,nCode,wParam,lParam)); 
  } 

  大家可以看到,当程序发现按下的是Win键或者Alt+Tab组合键,就不再调用CallNextHookEx函数将这个消息传递下去。以此,我们做到了屏蔽这些按键的响应。  
  
  第二步,钩子DLL的测试程序。在VC中创建一个基于对话框的应用程序。通过调用LoadLibrary("KeyMask.dll")载入钩子DLL,通过GetProcAddress(m_hDll,"StartKeyMask")和GetProcAddress(m_hDll,"StopKeyMask")导入两个安装/卸载钩子的函数。在主对话框上定义两个按钮分别调用这两个函数,如下:

  当按下“Start_Hook”按钮,我们的钩子函数就起作用了。试一下Win键,或者Alt+Tab组合键,没反应了吧?!“Stop_Hook”按钮可以解除这个钩子。  

  讲到这,大家可能觉得钩子其实也是很容易的东西。是的,钩子容易使用,而且功能强大。但是,笔者建议,如果不是十分必要,请尽量少用钩子。因为钩子在实现强大功能的同时,可能也会严重降低你系统的性能。有时候是得不偿失的! 

PB中实现数据窗口动态排序的三种方法

在PowerBuilder中使用数据窗口检索到的数据往往是无序的,虽然可以通过设置Select语句实现排序的功能,但是数据窗口一旦生成都无法进行动态调整。笔者总结了在已经生成的数据窗口中实现动态排序的三种方法,现介绍给大家。

    一、 准备工作

  设计如图1所示的示例窗口。为了更好地比较三种不同的方法,dw—1中的数据来自两个表student和class。student表中包含四个字段sid(学号)、sname(姓名)、saddr(住址)和cid(班号),class表中包含两个字段cid(班号)和cname(班级名称)。


图1


二、三种方法的源程序

  三种方法中的“执行”按钮的代码分别为:

  方法1:
  用SetSQLselect()
  string ls—oldsql,ls—newsql,ls—order ls—column
  ls—oldsql=dw—1.getsqlselect()
  choose case ddlb—1.text
  case ″学号″ls—column=″sid″
  case ″姓名″ls—column=″sname″
  case ″住址″ls—column=″saddr″
  case ″班号″ls—column=″class.cid″
  case ″班级名称″ ls—column=″cname″
  end choose
  if rb—1.checked then ls—order=″ASC″
  else ls—order=″DESC″
  end if
  ls—newsql=ls—oldsql+″ ORDER BY ″+ &
  ls—column+″ ″+ls—order
  if dw—1.setsqlselect(ls—newsql)=-1 then
  messagebox(″警告″,″数据设置失败″,stopsign!)
  else dw—1.settransobject(sqlca)
  dw—1.reset()
  dw—1.retrieve()
  dw—1.setsqlselect(ls—oldsql)
  end if 
  方法2:
  用describe()和modify()
  string ls—mod, ls—order,ls—old,ls—column
  ls—old=dw—1.describe(′datawindow.table.select′)
  dw—1.settransobject(sqlca)
  choose case ddlb—1.text
  case ″学号″ls—column=″sid″
  case ″姓名″ls—column=″sname″
  case ″住址″ls—column=″saddr″
  case ″班号″ls—column=″class.cid″
  case ″班级名称″ ls—column=″cname″
  end choose
  if rb—1.checked then ls—order=″ASC″
  else ls—order=″DESC″
  end if
  ls—mod=″datawindow.table.select=′ ″+ls—old+&
  ′ORDER BY ″ ′+ls—column+′ ″ ′+ls—order+″ ′ ″
  dw—1.modify(ls—mod)
  dw—1.retrieve()
  dw—1.modify(″datawindow.table.select= &
  ′ ″+ls—old+″ ′ ″) 

  方法3:
  用setsort()和sort()
  string ls—sort,ls—order,ls—column
  choose case ddlb—1.text
  case ″学号″ ls—column=″#1″
  case ″姓名″ ls—column=″#2″
  case ″住址″ ls—column=″#3″
  case ″班号″ ls—column=″#4″
  case ″班级名称″ ls—column=″#5″
  end choose
  if rb—1.checked then ls—order=″A″
  else ls—order=″D″
  end if
  ls—sort=ls—column+′′+ls—order
  dw—1.setsort(ls—sort)
  dw—1.sort() 

三、三种方法的比较

  1.第一种和第二种方法要求数据窗口在生成时是无序的,第三种方法无此要求。

  2.对于来自不同表单的相同的列名(如student.cid、class.cid)用第二种方法排序实现起来较麻烦,因为在用modify()函数时要特别注意引号的使用。但是第二种方法比第一种方法的执行速度要快。

  3.第三种方法使用起来最方便,既可以引用列名也可引用列号(如#4表示第四列)来指定序列。

 

PB的可执行文件所需的环境DLL

一个EXE文件(或者再加PBD文件)要提交给脱离了PowerBuilder环境的用户使用时,还必须提供一些PowerBuilder应用程序执行、数据库连接等实现所必需的环境动态链接库文件。如果缺少这些dll文件,应用程序可能无法启动,或者无法连接到数据库服务器。

  这就是说,经过编译生成的PowerBuilder应用程序需要一定的运行环境。

  以下几个文件在PowerBuilder的Shared\PowerBuilder文件夹中(或者使用开始菜单中的" 查找"),提交应用程序时需要将它们拷贝到EXE文件所在的路径下(对于32位Windows或NT操作系统):

  PBVM70.DLL :PowerBuilder 虚拟机,必需PBTRA60.DLL :用来数据库跟踪调用,可选PBRTC60.DLL:对Rich Text 的支持,可选PBMSS70.DLL:Microsoft SQL Server数据库服务器的直连接口(Native database interfaces),使用MSS必备的DLL,如果使用别的DBMS,采用相应的其他DLL PBDWE60.DLL:DataWindow 引擎,如果使用了 DataWindow和 Report,必需 NTWDBLIB.DLL:DBMS 客户端链接库,负责执行与服务器的连接,必需DBNMPNTW.DLL:Named Pipes Network Library,网络连接方式之一 DBMSSOCN.DLL:TCP/IP Network Library,网络连接方式之二如果不是使用专用接口,而是采用ODBC,则另需要:PBODB70 .DLL PBODB70 .INI。

PB编写邮件应用程序

由于受到强大数据库功能的掩盖,PB的邮件功能鲜为人知。在VB中可以轻松地利用CDO控件发邮件(Delphi中用NMSMTP),而PB中的邮件函数比它们更灵活、方便。

    PB以其独特高效的数据库访问技术,赢得了广大程序员的青睐。从表面上看,它似乎只适合于开发数据库应用程序,而事实上,PB遵照信报接口MAPI的标准,开发了许多内部函数和数据结构,用于对电子邮件提供支持,因此也是一个相当不错的邮件应用程序开发工具。

  一个邮件应用程序要处理的基本事务主要包括:登录到邮件服务器开始会话、接发邮件、结束邮件会话。在PB中,所有这些事务都围绕mailSession对象来展开,因此,编程时,需要在主窗口中定义一个该类型的实例变量:
  mailSession MyMail
  然后在Open事件中将其初始化:
  MyMail=Create mailSession

1.登录到邮件服务器

  MyMail经过初始化以后,还要用mailLogon命令将其连接到一个邮件服务器:

  MyMail.mailLogon(′jq75′,′alexander′,mailNewSessionWithDownLoad!)

  
  前两个参数分别为用户名和口令,若被忽略,运行时就会出现一个注册对话框。最后一个参数表示新建一个邮件会话,并将服务器上属于jq75的邮件下载到他的收件箱中。


图1

2.阅读邮件

  用户信息在服务器上通过验证以后,一个有效的邮件会话就建立了,接下来要做的工作就是接发邮件。

  PB用一个mailMessage对象来描述一封邮件,该对象封装了邮件的主题、地址、消息体和附件等信息。图1是邮件应用程序的阅读界面,它列出了收件箱中的所有邮件,可选择其中一封进行阅读。主窗口Open事件的代码为:

…… //连接到邮件服务器
MyMail.mailGetMessages()
//用收件箱中的邮件填充MyMail对象
iNum=UpperBound(MyMail.MessageID[])
//获取收件箱中的邮件数目
For i=1 To iNum //读取收件箱中的每一封邮件,

并将其主题添加到列表框中
MyMail.mailReadMessage(MyMail.MessageID[i],msg, mailEntireMessage!,True)
tab—1.tabpage—1.plb—1.AddItem(msg.Subject,1)
Next //msg为mailMessage类型的实例变量
在plb—1控件的SelectionChanged事件中加入下列代码:
MyMail.mailReadMessage(MyMail.MessageID[index],msg, mailEntireMessage!,True)
sle—1.Text=msg.Subject
//显示当前邮件的主题
sle—2.Text=msg. Recipient[1].Address
//显示发件人地址
sle—3.Text=msg.DateReceived
//显示收件时间
mle—1.text=msg.NoteText
//显示邮件的消息体
iAttachmentFileNum=UpperBound(msg.AttachmentFile[])
//获取当前邮件的附件数目
For i=1 To iAttachmentFileNum
strAttFile= strAttFile +msg.AttachmentFile[i].

PathName+″~r~n″
Next //strAttFile为String类型的实例变量



  在“附件”按钮的Clicked事件中加入下列代码:

3.发送邮件

  图2是邮件应用程序的发送界面,它接收收件人地址、邮件主题、消息体和附件,用于填充一个mailMessage类型的对象msg,然后发送。“添加附件”按钮用于选择磁盘文件,其Clicked事件代码为:

 


图2



 

String docName, Named
Integer Value
Value = GetFileOpenName(″选择附件″, docName, Named,

″DOC″, ″All Files (*.*),*.*″)
If Value = 1 Then
plb—2.AddItem(docName,1)
//docName中必须包含完整的路径信息
“删除附件”按钮用于把已添加的文件去掉,

其Clicked事件代码为:
Ind= plb—2.SelectedIndex()
If Ind〈〉-1 Then plb—2. DeleteItem(Ind)
“发送”按钮的Clicked事件代码为:
msg.Subject=sle_4.Text //邮件主题
msg.NoteText=mle—2.Text //消息体
msg.Recipient[1].RecipientType=mailTo!
//指定收件人类型为mailTo!
msg.Recipient[1].Address=′SMTP:′+sle—5.Text
//收件人地址,前面必须加上SMTP协议
For i=1 To plb—2.TotalItems()
//把选中的文件加到附件中
msg.AttachmentFile[i].FileType=mailAttach! //指定附件i的类型
msg.AttachmentFile[i].PathName= plb—2.Text(i)

//附件i的完整文件名
Next
MyMail.mailSend(msg) //发送邮件


4.结束邮件会话

  在“退出”按钮的Clicked事件中加入下列代码:

 

MyMail.mailLogoff() //结束邮件会话
Destroy MyMail //销毁会话对象



  至此,一个简单的邮件应用程序就编好了,它虽然还不足以和FoxMail、Outlook相媲美,但已经具备了它们最基本的功能。

  例子中的程序在PB6.5中调试通过,大部分代码可以直接利用。

PB生成GUID解决主键重复

编一个全局函数如下:

    //---------------------------------------------
  //Function f_get_GUID//得到全球唯一码
  //Argument: <ref string> of_guid //全球唯一码
  //Return: [None]
  //Create by 泥草鞋 2004-03-10
  //----------------------------------------------
  oleObject PBObject 
  long ll_result
  PBObject = CREATE oleObject

  ll_result = PBObject.ConnectToNewObject("PowerBuilder.Application")
  IF ll_result < 0 THEN
  messagebox(gs_message,"连接失败:与PowerBuilder.Application连接出错!")
  return
  ELSE
  ll_result = PBObject.GenerateGUID(REF of_guid)
  END IF

  IF ll_result < 0 THEN
  messagebox(gs_message,"生成GUID失败:不能获得GUID!")
  return
  END IF

  of_guid = mid(of_guid,2,len(of_guid) - 2)
  //End of Fuction

  好啦,以后不用为主键重复问题再烦恼了吧。

  不过,请注意该函数需要的支持文件:(以PB7.0为例)
  pbappl.reg(执行它注册一遍,7.0以下必须此文件)
  Pbvm70.dll(不用多说了)
  Pbaen70.tlb(PB自动应用服务类库入口)

   GUID如何做到唯一?

  要确保一个标识是唯一的,仅有两个方法:
  1.通过一些机构组织来登记;
  2.使用特别的算法来产生唯一的数字,这些数字可被认为在世界范围内是唯一的。
  第一个方法很常见,比如身份证号码是国家制定的标识个人。它的问题是,制定这标识的机构组织自身存在执行偏差,而且手续繁琐,多数情况下你得花钱费时。
  第二个方法更适合于开发者。如果你可以发明一个算法,每次调用它都可以产生一个可被认为是唯一的名字,那么这个问题就解决了。

  事实上,开发软件基金会(Open Software Foundation,OSF)已经研究出一种能产生唯一标识符的算法,产生全球唯一标识符(Universally Unique Identifier,UUID)。在COM的命名标准上,微软使用同样的算法!在COM中微软将它重命名为Globally Unique Identifier(GUID)。
  生成GUID的算法根据以下几个方面:1.当前日期和时间。2.网络适配器卡地址。3.时针序。4.自动递增计数器。其中,网卡地址是相互不同的,对没有网卡的机器,地址对使用中的机器保持唯一性。
  GUID的记录通常采用16进制。不过这没有关系,一个典型的GUID类似为:"88AB240C-F761-49B8-B47F-94B0ABA4115A",略去"-",即为一个128位的唯一数字。2的128次方是一个非常大的数字。128位的接口标识符使得我们可能创建大约340282366920900000000000000000000000000个独立的接口,足够为将来10782897524560000000年每秒创建一万亿个接口。
  这是个什么概念呢?就拿这几个数字中最小的“一万亿”来说。假如把银河系缩小一万亿倍,也就是把银河系直径缩小到一百万公里,这时太阳就变成一粒芝麻,那么最大的行星木星就变成了一粒灰尘。
  即使地球毁灭了,它仍然是全球唯一。

PB动态报表的实现

在通常的管理信息系统开发过程中,总是有没完没了的报表需要制作,调试报表花费的时间也是最多而且乏味,还常常不能满足客户的要求。如果能够让用户自己调整报表的格式和内容,然后将它保存下来,程序下次启动时若能自动调用保存了的报表格式那就方便多了。 

    实现原理 
 
  PowerBuilder中有一种以PSR为后缀的特殊的保存报表的文件格式(本文简称PSR文件),数据窗口可以直接读取PSR文件生成报表,而程序通过生成PSR文件,就可以实现动态报表格式的保存。 

  首先,通过设置数据窗口对象(dataobject)中的文本、列等的Resizeable和moveable属性为1来实现对象位置的拖动控制,通过数据窗口的Modify函数实现对象值的更改(包括增加和删除)。 

  其次要保存报表格式。在一个应用中,数据窗口对象的名称总是惟一的,将每一个数据窗口对象转化成PSR文件存于数据库表中。在窗口打开时,程序先校验报表格式是否存在。如果存在,将报表格式读出来放在一个临时文件当中,然后设置数据窗口(datawindow)的数据对象(dataobject)为这个报表文件,并提取数据; 如果不存在,直接提取数据即可。 

实现过程 

  1. 建立一个数据库表用以保存报表格式文件,各个字段定义如下: 
  
  2. 建立一个窗口w_temp。 定义实例变量如下: 
   string is_dwtype,is_dwobject 
   //保存报表中对象的类型及名称 
  
  3. 在窗口的Open事件中加入如下代码, 校验报表格式是否存在,如果存在,读取定义好的报表格式到数据窗口。 
  blob emp_pic 
  long ll_handle 
  string ls_dwobject,ls_reportfile,ls_path 
  ls_dwobject = dw_print.dataobject 
  //判断是否存在该数据窗口的报表格式 
  select count(*) into:ll_count from dyn_report where dwobject =:ls_dwobject; 
  if ll_count>0 then 
  //读取报表格式文件到大文本变量 
  selectblob memo into:emp_pic from dyn_report where dwobject =:ls_dwobject; 
  //创建PSR临时文件并保存到硬盘 
  ls_reportfile =‘\temp7089.psr’ 
  ll_handle = FileOpen(is_reportfile,StreamMode!,write!,LockWrite!,Replace!) 
  FileWrite(ll_handle,emp_pic) 
  FileClose(ll_handle) 
  dw_print.dataobject = ls_reportfile 
  dw_print.settransobject(sqlca)
  else 
  Dw_print.settransobject(sqlca) 
  End if
  Dw_print.retrieve() 
  4. 保存报表格式,这可以通过Cb_savereport按钮的clicked事件实现。 
  string ls_filename 
  long ll_count 
  blob Emp_id_pic 
  ls_filename =“temp70201.psr” 
  //保存报表格式到硬盘临时文件 
  dw_print.saveas(ls_filename,PSReport! ,false) 
  sqlca.autocommit = true 
  select count(*) into :ll_count from dyn_report where dwobject =:is_dwobject; 
  if ll_count =0 then 
  insert into dyn_report(dwobject,rptitle) 
  values(:is_dwobject,:ls_filename,:ls_path); 
  end if
  //从硬盘临时文件读取数据保存到数据库表中 
  emp_id_pic = of_readbmpfile(ls_filename) 
  //该函数将二进制文件内容读到大文本对象中 
  UPDATEBLOB dyn_report SET memo = :Emp_id_pic where dwobject = :is_dwobject; 
  //更新数据库
  sqlca.autocommit = false 
  
  5. 动态报表的实现。通过数据窗口dw_print的clicked事件捕获数据窗口中的对象,并将对象名存放在实例变量is_dwobject中,为下一步修改报表做准备。 
  string ls_type,ls_dwoname
  //得到对象类型和名称 
  ls_type = trim(upper(dwo.type)) 
  ls_dwoname = trim(dwo.name) 
  is_dwtype = ls_type 
  choose case ls_type 
  case “TEXT”,“CommandButton”,“GROUPBOX” 
  is_dwobject = ls_dwoname 
  //设置为可以拖动和改变大小 
  this.modify(ls_dwoname+“.Resizeable=‘”+“1’”) 
  this.modify(ls_dwoname+“.moveable=”+“1”) 
  case “LINE”
  //直线对象不能通过设置Resizeable和moveable属性进行调整,必须通过其他途径 
  is_dwobject = ls_dwoname 
  case “RECTANGLE”,“ELLIPSE”,“GRAPH”,“BITMAP” 
  is_dwobject = ls_dwoname 
  this.modify(ls_dwoname+“.Resizeable=‘”+“1’”) 
  this.modify(ls_dwoname+“.moveable=‘”+“1’”) 
  case “COLUMN”,“COMPUTE”
  is_dwobject = ls_dwoname 
  this.modify(ls_dwoname+“.Resizeable=‘”+“1’”) 
  this.modify(ls_dwoname+“.moveable=‘”+“1’”)
  end choose 
  
  最后再通过modify()函数就可以实现基本的动态报表操作,这一类的文章较多,PB中也有大量的例子可直接使用,在此不再赘述。 
  
  6. 在cb_exit按钮的clicked()事件中加入:close(parent)。 
  
  7. 在应用的open事件中加入: open(w_temp)。然后保存并运行,全部工作到此结束! 

  本程序在PB7.0、Oracle 8.05下调试通过。

 

pb中用语音读金额

1、将金额转换成大写金额;
  2、根据大写金额依次朗读出来;

    为此,需要完成以下内容:
  1、分别录制各WAV文件:0,1,2,3,4,5,6,7,8,9,元,角,分,拾,佰,仟,万,亿,整
  2、在工程中声明两个External 函数(Windows的API函数),用于发声;
         Function boolean sndPlaySoundA (string SoundName, uint Flags) Library "WINMM.DLL"
         Function uint waveOutGetNumDevs () Library "WINMM.DLL"
  3、新建一函数用于在程序中调用发声:
         Function PlaySound(string as_filename,integer ai_option) returns integer

  该函数的代码如下:
        uint lui_numdevs
        lui_numdevs = WaveOutGetNumDevs()
        If lui_numdevs > 0 Then
     sndPlaySoundA(as_filename,ai_option)
        return 1
        Else
    return -1
        End If

  4、新建一函数用于将小写金额转换成大写金额:
        Function xx2dx(Decimal ls) returns string

  代码实现如下:
  string dx_sz,dx_dw,str_int,str_dec,dx_str,fu,a,b,b2,c,d,result
  long num_int,num_dec,len_int,i,a_int,pp

  dx_sz = "零壹贰叁肆伍陆柒捌玖"
  dx_dw = "万仟佰拾亿仟佰拾万仟佰拾元" 
    /*处理小于零情况*/
  if ls<0 then
     ls = ls*(-1)
     fu = "负"
  else
       fu = ""
  end if

    /*取得整数及整数串*/
  dx_str = string(ls)
  if (ls>0) and (ls<1) then dx_str = "0"+dx_str
  pp = pos(dx_str,".")
  if pp>0 then
    str_int = mid(dx_str,1,pos(dx_str,".")-1)
  else
    str_int = dx_str
  end if
  num_int = long(str_int)

    /*取得小数及小数串*/
  if (ls>0) and (ls<1) then
    num_dec = ls * 100
  else
    num_dec = (ls - num_int) * 100
  end if
  str_dec = string(num_dec)
  len_int = len(str_int)
  dx_str = "" 
   /*转换整整部分*/
  for i = 1 to len_int
      /*a为小写数字字符,b为对应的大写字符,c为对应大写单位,d为当前大写字符串的最后一个汉字*/
     a= mid(str_int,i,1)
     a_int = long(a)
     b = mid(dx_sz,(a_int*2)+1,2)
     c = mid(dx_dw,((13 - len_int +i - 1)*2+1),2)
     if dx_str<>"" then
       d=mid(dx_str,len(dx_str)-1,2)
     else
        d= ""
     end if

     if (b="零") and ((d="零") or (b=b2) or (c="元") or (c="万") or (c="亿")) then  b = ""
     if (a="0") and (c<>"元") and (c<>"万") and (c<>"亿") then c=""
     if ((c="元") or (c="万") or (c="亿")) and (d="零") and (a="0") then
        dx_str = mid(dx_str,1,len(dx_str)-2)
        d=mid(dx_str,len(dx_str)-1,2)
        if ((c="元") and (d="万")) or ((c="万") and (d="亿")) then c = ""
     end if 
      dx_str = dx_str + b+ c
      b2 = b
  next

    /*处理金额小于1的情况*/
    if len(dx_str) <= 2 then dx_str= ""
    /*转换小数部分*/
    if (num_dec<10) and (ls>0) then
      a_int = long(str_dec)
      b = mid(dx_sz,(a_int*2+1),2)
      if num_dec = 0 then dx_str = dx_str + "整"
      if num_dec > 0 then dx_str = dx_str +"零"+b+"分"
    end if
 
    if num_dec >= 10 then
      a_int = long(mid(str_dec,1,1))
      a = mid(dx_sz,(a_int*2+1),2)
      a_int = long(mid(str_dec,2,1))
      b = mid(dx_sz,(a_int*2+1),2)
      if a<>"零" then a = a+"角"
      if b <> "零" then
    b = b+"分"
      else
   b= ""
      end if
      dx_str = dx_str + a + b
    end if
    if ls= 0 then dx_str = "零元整"
    dx_str = fu+dx_str
 
    result = dx_str
  return result
  5、声明一函数,用于最终调用:
        Function MyReadMoney(Decimal AMoney)
       
  代码实现如下:
  integer i,count
         string ls_dxje

  ls_dxje = xx2dx(Amoney)

  count = len(ls_dxje)

  for i= 1 to count step 2
   CHOOSE CASE mid(ls_dxje,i,2)
   CASE "零"
    playsound("0.wav",0)   
   CASE "壹"
    playsound("1.wav",0)   
   CASE "贰"
    playsound("2.wav",0)   
   CASE "叁"
    playsound("3.wav",0)   
   CASE "肆"
    playsound("4.wav",0)   
   CASE "伍"
    playsound("5.wav",0)   
   CASE "陆"
    playsound("6.wav",0)   
   CASE "柒"
    playsound("7.wav",0)   
   CASE "捌"
    playsound("8.wav",0)   
   CASE "玖"
    playsound("9.wav",0)   
   CASE "拾"
    playsound("十.wav",0)   
   CASE "佰"
    playsound("佰.wav",0)   
   CASE "仟"
    playsound("仟.wav",0)   
   CASE "万"
    playsound("万.wav",0)   
   CASE "亿"
    playsound("亿.wav",0)   
   CASE "元"
    playsound("元.wav",0)   
   CASE "角"
    playsound("角.wav",0)   
   CASE "分"
    playsound("分.wav",0)   
   CASE "整"
    playsound("整.wav",0)   
   END CHOOSE
  next

  6、在程序中,可以任意调用此函数。当然,可以适当加入出错处理一类的代码。
  7、备注:如果不能发声,应检查声音文件是否在可执行文件的同一路径,最好是在函数MyReadMoney中,对各wav文件直接加上路径如C:\temp

PB代码优化

现今计算机的运行速度已经很快了,并且由于老板时常在耳边念着紧箍咒,因此,我们有意或者无意的忘记优化我们的代码,只要能完成任务就行了(我也是)。不过,我们闲下来的时候,不妨也来看看我们的代码是否有需要改进的地方。下面就是我觉得值得优化的几种情况。

 

第一种情况:


  IF condition1 AND condition2 THEN

        //Code goes here.

END IF

IF condition1 THEN

        IF condition2 THEN

                 //Code goes here.

        END IF

END IF

 

对于书写的第一种方式,由于PB编译方式与常见的方式不同,就是无论条件1的值如何,都要对条件2进行运算。这样一来,当条件1为false时,就可能要无谓的对条件2进行运算了。就按随机的概率而言,可能会多进行一半的运算。因此,对于大多数情况而言,我想最好是以第二种方式书写。当然,特殊情况也是有的,那就是你的确想对条件2进行运算。类似地,对于or也一样。

 

IF condition1 OR condition2 THEN

        //Code goes here.

END IF

IF condition1 THEN

        //Code goes here.

ELSE

        IF condition2 THEN

                 //Code goes here.

        END IF

END IF
  第二种情况:

IF NOT condition THEN

        //Code goes here.

END IF

IF condition THEN

        //No code goes here.

ELSE

        //Code goes here.

END IF

 

对于上一种方式,条件表达式返回false并且再进行一个运算,才执行下面的代码。这样相对于下面一种书写方式可能多执行了一个运算。如果大家有什么疑问,您不妨测试一下下面这个例子:

 

//小测试:其中的判断条件只是为了表示一下,实际情况可能更复杂。

 

long i                           //计数器

long ll_start                 //执行开始时间

long ll_used1     //方式一耗时

long ll_used2     //方式二耗时

 

//方式一

ll_start = Cpu()

for i = 1 to 900000

        if not (1 > 1) Then

                 i = i

        end if

next            

ll_used1 = Cpu() - ll_start

//方式二

ll_start = Cpu()

for i = 1 to 900000

        if 1 > 1 Then

                

        else

                 i = i

        end if

Next

ll_used2 = Cpu() - ll_start

 

//查看结果

If ll_used2 > ll_used1 Then

        MessageBox("提示","前者运行时间短!")

Else

        MessageBox("提示","后者运行时间短!")

End If

 

可能有人会说,用下面的那种方式,如果在条件表达式返回false的时候,那么,if下就没有代码,这样看起来就不太舒服。的确是这样。因此,我们在写成上面的那种方式时,尽量保持不要使用not运算,而保持条件表达式本身就返回希望的true值。

  第三种情况:

IF condition1 THEN

        //condition1

ELSEIF condition2 THEN

        //condition2        

ELSEIF condition3 THEN

        //condition3

ELSE

        //Other

END IF

choose case /*expression*/

        case /*item*/

                 /*statementblock*/

        case /*item*/

                 /*statementblock*/

        case else

                 /*statementblock*/

end choose

 

对于形如这样的表达式,我想我们一般都没去考虑先后顺序。但是,其实我们应该把最可能发生的情况,放在前面,这样可以避免在对一大堆条件进行判断后,才到我们真正需要运行代码的地方。
  

第四种情况:

 FOR ... TO ...

       

        IF condition THEN

                 //true

        ELSE

                 //false

        END IF

       

NEXT

IF condition THEN

        //true

        FOR ... TO ...

                 //Code goes here

        NEXT

ELSE

        //false

        FOR ... TO ...

                 //Code goes here

        NEXT

END IF

 

尽管下面这种书写方式,看起来好象代码多了一些,但是却极大的避免了在每个循环中都进行条件判断。其实,一个原则就是,我们应当尽量避免在循环中进行条件判断,而是把条件判断放到循环体的外面进行。

 

其实,真正对于PB语言有特殊性的,也就是第一种情况,对于后面三种情况,对于别的编程语言,我想也同样适用。

 

这是我的一点体会,谬误的地方请大家指正。

 

如何在PB中实现串口编程

可以使用mscomm32.ocx控件.

    脚本如下:

  String ls_data 
  //使用COM1端口。 
   ole_1.object.CommPort = 1 
  //设置速率为9600,无奇偶校验,8 位数据,一个停止位。 
  ole_1.object.Settings = "9600,N,8,1" 
  //读入整个缓冲区的数据。 
  ole_1.object.InputLen = 0 
  打开端口 
  ole_1.object.PortOpen = True 

  //发送attention命令 
  ole_1.object.Output = "ATV1Q0" + char(13) 

  //等待数据。 
  Do 
  Yield() 
  //从Com端口取数据 
  ls_data += ole_1.object.Input 
  LOOP Until(Pos(ls_data, "OK" + char(13) + char (10)) > 0) 

  //向Com端口发送数据使用Output方法 
  ole_1.Object.Output = ls_data 

  //关闭端口。 
  ole_1.object.PortOpen = FALSE 

PB透明文本的实现

(1) 新建Standard Visual用户对象uo_transparent_st,类型为定义实例变量:Boolean ib_painting
  (2) 编写uo_transparent_st的Constructor 事件脚本:
  // 透明色 536870912
  This.backcolor = 2^29

  (3) 定义用户对象uo_transparent_st的用户事件ue_paint(Event ID:pbm_paint),并编写脚本如下:
  IF IsValid(This) THEN
  if ib_painting THEN Return 0
  ib_painting = True
  This.Visible = False
  Do While Yield()
  Loop
  This.Visible = True
  ib_painting = False
  END IF</P><P>Return 0

 

PB下实现圆形的窗口

API调用:

首先在窗口定义下列局部外部函数(Local External Functions...)

  FUNCTION ulong CreateEllipticRgn(ulong X1,ulong Y1,ulong X2,ulong Y2) LIBRARY "gdi32.dll"

  FUNCTION ulong SetWindowRgn(ulong hWnd,ulong hRgn,boolean bRedraw) LIBRARY "user32.dll"

    在窗口的open事件中加上:

   long hrgn

   long lres

    hrgn=createellipticrgn(20,20,400,400)//其中参数为左上到右下的坐标值,可修改。

   lres=setwindowRgn(handle(this),hrgn,true)

   //记得在窗口中放置一个按钮关闭窗口

   //在窗口处放置一幅图片即可实现圆形的窗口了。

   但不知如何实现任意多边形的窗口,哪位大虾可以指点一二?

  查阅API函数手册中有如下函数定义,应该是用来定义多边形窗口的,但不知如何使用。

  FUNCTION ulong CreateEllipticRgnIndirect(ref Rect lpRect) LIBRARY "gdi32.dll"

 

窗体动态效果的实现

最近用pb做了一个触摸屏的程序,项目组要求窗口显示关闭的时候有点动态效果,于是我就写了如下的程序,供大家参考借鉴。

 

 

  ---------------------------------
  // 实现关闭窗体时的动态效果
  // -------------------------------
  // 函数名:gf_closequery
  // 参数说明:
  //         window    window类型,调用窗口的名字
  //         closetype integer类型,窗口关闭方式,value = 0~10
  // -------------------------------
  // 申明局部变量
  int li_x ,li_y,li_width,li_height,li_ceny,

li_cenx,li_xminusy,li_wminush
  Integer li_gd

  // 取出当前窗口的坐标值、大小值
  li_x = window.x
  li_y = window.y
  li_width = window.width
  li_height = window.height

  // 设置窗体关闭前的动画效果
  // 关键是看哪个值发生了变化——x、y、h、w
  CHOOSE CASE closetype
   CASE 0      // closetype = 0,从下到上逐渐消失
  for li_gd = li_height to 0 step -1
   window.height = li_gd
   window.show()
  next
 CASE 1      // closetype = 1,从上到下逐渐消失
  for li_gd = li_y to li_height+li_y step 1
   window.y = li_gd
   window.height = li_height+li_y - li_gd
   window.show()
  next
 CASE 2      // closetype = 2,从右到左逐渐消失
  for li_gd =  li_width to 0 step -1
   window.width = li_gd
  next
 CASE 3      // closetype = 3,从左到右逐渐消失
  for li_gd =  li_x to li_x+li_width step 1
   window.x = li_gd
   window.width = li_x+li_width - li_gd
   window.show()
   next
  case 4      // closetype = 4,从上下向中间挤压逐渐消失
  li_ceny = li_y+li_height/2
  for li_gd = li_y to li_ceny step 1
   window.y = li_gd

 

 

   window.height = li_height - 2*(li_gd - li_y)
  next
 case 5      // closetype = 5,从左右向中间挤压逐渐消失
  li_cenx = li_x+li_width / 2
  for li_gd = li_x to li_cenx step 1
   window.x = li_gd
   window.width = li_width - 2*(li_gd - li_x)
  next
 case 6      // closetype = 6,从左上->右下
  for li_gd = li_y to li_height+li_y step 1
   window.y = li_gd
   window.height = li_height+li_y - li_gd
   if window.x < li_x + li_width then
    window.x = li_x + (li_gd - li_y)
   else
    window.x = li_x + li_width
   end if
   if window.width > 0 then
    window.width = li_x+li_width - window.x
   else
    window.width = 0
   end if
  next
  window.x = li_x + li_width
  window.y = li_height+li_y
  window.width = 0
  window.height = 0
   window.show()
   case 7      // closetype = 7,从右下->左上
  for li_gd = li_height to 0 step -1
   window.height = li_gd
   if window.width > 0 then
    window.width = li_width - (li_height - li_gd)
   else
    window.width = 0
   end if
  next
  window.x = li_x
  window.y = li_y
  window.width = 0
  window.height = 0
  window.show()
 case 8      // closetype = 8,从右上->左下
  for li_gd = li_y to li_height+li_y step 1
   window.y = li_gd
   window.height = li_height+li_y - li_gd
   if window.width > 0 then
    window.width = li_width - (li_gd - li_y)
   else

 

 

    window.width = 0
   end if
  next
  window.x = li_x
  window.y = li_height+li_y
  window.width = 0
  window.height = 0
  window.show()
   case 9      // closetype = 9,从左下->右上
  for li_gd = li_x to li_x+li_width step 1
   window.x = li_gd
   window.width = li_width +li_x -li_gd
   if window.height > 0 then
    window.height = li_height -(li_gd - li_x)
   else
    window.height = 0
   end if
  next
  window.x = li_x+li_width
  window.y = li_y
  window.width = 0
  window.height = 0
  window.show()
 case 10      // closetype = 10,从四面到中间
  li_ceny = li_y+li_height/2
  li_cenx = li_x+li_width / 2
  for li_gd = li_y to li_ceny step 1
   window.y = li_gd
   window.height = li_height - 2*(li_gd - li_y)
   if window.x < li_x + li_cenx then
    window.x = li_x + (li_gd - li_y)
   else
    window.x = li_x + li_cenx
   end if
   if window.width > 0 then
    window.width = li_width - 2*(li_gd - li_y)
   else
    window.width = 0
   end if
  next
  window.x = li_cenx
  window.y = li_ceny
  window.width = 0
  window.height = 0
  window.show()
 case else
  window.show()
  window.width = li_width
  window.height = li_height
  window.x = li_x
  window.y = li_y
END CHOOSE
return 0
************************
// 调用该函数在窗体的 closequery 事件中
gf_closequery (w_main,mod(integer(string(now(),"ss")),11))



    上面是关闭时的效果,窗体打开时的动态效果的语句跟上面的差不多,在此就不写啦,如果有需要的可以告诉我,我单独发送。谢谢。

 

PB实现数据窗口动态排序的方法

在PowerBuilder中使用数据窗口检索到的数据往往是无序的,虽然可以通过设置Select语句实现排序的功能,但是数据窗口一旦生成都无法进行动态调整。笔者总结了在已经生成的数据窗口中实现动态排序的三种方法,现介绍给大家。

 一、 准备工作

  设计如图1所示的示例窗口。为了更好地比较三种不同的方法,dw—1中的数据来自两个表student和class。student表中包含四个字段sid(学号)、sname(姓名)、saddr(住址)和cid(班号),class表中包含两个字段cid(班号)和cname(班级名称)。


图1

  二、三种方法的源程序

  三种方法中的“执行”按钮的代码分别为:

  方法1:用SetSQLselect()

 

  string ls—oldsql,ls—newsql,ls—order ls—column
  ls—oldsql=dw—1.getsqlselect()
  choose case ddlb—1.text
  case ″学号″ls—column=″sid″
  case ″姓名″ls—column=″sname″
  case ″住址″ls—column=″saddr″
  case ″班号″ls—column=″class.cid″
  case ″班级名称″ ls—column=″cname″
  end choose
  if rb—1.checked then ls—order=″ASC″
  else ls—order=″DESC″
  end if
  ls—newsql=ls—oldsql+″ ORDER BY ″+ &
  ls—column+″ ″+ls—order
  if dw—1.setsqlselect(ls—newsql)=-1 then
  messagebox(″警告″,″数据设置失败″,stopsign!)
  else dw—1.settransobject(sqlca)
  dw—1.reset()
  dw—1.retrieve()
  dw—1.setsqlselect(ls—oldsql)
  end if 

 

  方法2:用describe()和modify()

 

  string ls—mod, ls—order,ls—old,ls—column
  ls—old=dw—1.describe(′datawindow.table.select′)
  dw—1.settransobject(sqlca)

 

 

  choose case ddlb—1.text
  case ″学号″ls—column=″sid″
  case ″姓名″ls—column=″sname″
  case ″住址″ls—column=″saddr″
  case ″班号″ls—column=″class.cid″
  case ″班级名称″ ls—column=″cname″
  end choose
  if rb—1.checked then ls—order=″ASC″
  else ls—order=″DESC″
  end if
  ls—mod=″datawindow.table.select=′ ″+ls—old+&
  ′ORDER BY ″ ′+ls—column+′ ″ ′+ls—order+″ ′ ″
  dw—1.modify(ls—mod)
  dw—1.retrieve()
  dw—1.modify(″datawindow.table.select= &
  ′ ″+ls—old+″ ′ ″) 

 

  方法3:用setsort()和sort()

 

  string ls—sort,ls—order,ls—column
  choose case ddlb—1.text
  case ″学号″ ls—column=″#1″
  case ″姓名″ ls—column=″#2″
  case ″住址″ ls—column=″#3″
  case ″班号″ ls—column=″#4″
  case ″班级名称″ ls—column=″#5″
  end choose
  if rb—1.checked then ls—order=″A″
  else ls—order=″D″
  end if
  ls—sort=ls—column+′′+ls—order
  dw—1.setsort(ls—sort)
  dw—1.sort() 

 


  三、三种方法的比较

  1.第一种和第二种方法要求数据窗口在生成时是无序的,第三种方法无此要求。

  2.对于来自不同表单的相同的列名(如student.cid、class.cid)用第二种方法排序实现起来较麻烦,因为在用modify()函数时要特别注意引号的使用。但是第二种方法比第一种方法的执行速度要快。

  3.第三种方法使用起来最方便,既可以引用列名也可引用列号(如#4表示第四列)来指定序列。

 

如何判断当前操作系统是否为98/2000/XP

方法1.

environment env
integer resp
string temp,ls_version
resp = getenvironment(env)


choose case env.ostype
case aix!
temp = 'AIX'
case hpux!
temp = 'HPUX'
case macintosh!
temp = 'MacIntosh'
case osf1!
temp = 'OSF1'
case sol2!
temp = 'Solaris 2'
case Windows!
temp = 'Windows'
case Windowsnt!
temp = 'Windows NT'
end choose
ls_version = temp + ' '+string(env.osmajorrevision)+'.'+string(env.osminorrevision)+'.'+string(env.osfixesrevision)

messagebox("Windows version",ls_version)



  每种操作系统都有其版本号,自己在不同的操作系统上运行一下就知道了.然后再转换成自己熟悉的windows名称就可以了

方法2.

Long L1
dec{2} ldc_WinVer
string ls_WinVer
L1 = GetVersion()
ldc_WinVer = MOD(intlow(L1),256) + int(intlow(L1)/256)/100
choose case ldc_WinVer
case 3.10
ls_WinVer = "Windows 3.x"
case 4
ls_WinVer = "Windows NT 4.0"
case 4.10
ls_WinVer = "Windows 98"
case 5
ls_WinVer = "Windows 2000"
case 5.01
ls_WinVer = "Windows XP"
case 5.02
ls_WinVer = "Windows 2003"
end choose
messagebox("Windows version",ls_WinVer)


---------------------------------------------------------------

下面给出一个函数


// Function: gf_getos()

// Description: Get current Os name

// Arguments: value integer

// Returns: string
// 95-98 : Windows
// 2000- : WindowsNT
// Else : ""

// Author:Kilojin Date: 2005.02.14

// Modify History:
//

environment env
integer rtn
rtn = GetEnvironment(env)
IF rtn <> 1 THEN RETURN ""
CHOOSE CASE env.OSType
CASE Windows!
// Windows 95 or 98 code
return "Windows"
CASE WindowsNT!
// Windows NT-specific code
return "WindowsNT"
CASE Sol2!
IF env.OSMinorRevision = 5 THEN
RETURN ""
ELSEIF env.OSMinorRevision = 6 THEN
// Solaris 2.6 code
RETURN ""
END IF
CASE ELSE
RETURN ""
END CHOOSE

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页