用Windows公共控件增强PB应用的界面

阅读:482次   时间:2006-04-06 00:00:00   字体:[ ]

  Powerbuilder提供了较为丰富的控件,特别是7.0版新增了ProgressBar、TrackBar等控件,但仍有不少Windows公共控件没有利用起来,如IP Address、StatusBar等,值得注意的是带微帮助的多文档窗口(MDI with MicroHelp)的状态条的实现与Windows公共控件没有任何关系,故Powerbuilder状态条所提供的功能相当有限。文本将就如何使用IP地址控件展开讨论,比起其它控件:它用到的消息较少,对Comctl32.dll有版本要求,所以适合在此讨论。

  PowerScript是基于对象的语言,继承是面向对象方法的重要原则,如何利用现有代码进行高效的复用是我们在实现时应考虑的。因此,笔者强烈建议将调用Windows公共控件的代码封装在外部控件用户对象中,方便以后在布局视图中将它作为用户对象控件布置在窗口或可视用户对象上。

  接着将具体讨论创建外部控件用户对象的步骤。

  一、创建控件

  在新建向导的对象标签页上选择外部控件图标(New->Object->External Control)即可创建一个外部控件用户对象,然后读者请注意属性(Property)视图的常规(General)标签页,在该标签页上有很多影响调用效果的属性,现一一介绍如下:

Text属性:字符串型,窗口的标题。绝大多数的Windows公共控件都是窗口,都要调用CreateWindowExA函数来创建,Text属性其实就是CreateWindowExA的参数lpWindowName。现列出CreateWindowExA函数的Powersoft原型:
Function ULong CreateWindowExA(
ULong dwExStyle, /* 扩展的窗口风格*/
String lpClassName, /* 已注册的类名*/
String lpWindowName, /* 窗口名*/
ULong dwStyle, /* 窗口风格*/
Int x, /* 窗口的水平位置*/
Int y, /* 窗口的垂直位置*/
Int nWidth, /* 窗口的宽度*/
Int nHeight, /* 窗口的高度*/
ULong hWndParent, /* 父窗口的句柄*/
ULong hMenu, /* 菜单的句柄*/
ULong hInstance, /* 应用实例的句柄*/
ULong lpParam /* 窗口创建的用户参数*/
)Library "User32.DLL"
   对有些控件该标题会显示出来,如:状态条控件会将它显示在状态条的第一个部分;对有些控件该标题不会显示出来,IP地址控件就是这样的。具体显不显示读者日后自己可以试试。
Tag属性:字符串型,这个读者都熟悉,不多说了。
LibraryName属性:字符串型,动态链接库的路径。对于Windows公共控件通常位于%System%\Comctl32.dll,如:
C:\Windows\System\Comctl32.dll和C:\Winnt\System32\Comctl32.dll
填入该属性值后,Powerbuilder会判断该值是否是Comctl32.dll的路径,如果是的话将调用InitCommonControls(),否则不做任何动作。这个动作的原意是使用户不用调用InitCommonControls()来初始化,但造成了对4.71版及以后版本Comctl32.dll的支持问题,这会在后面探讨。

ClassName属性:字符串型,窗口的类名。这是调用CreateWindowExA要用到的参数lpClassName,该属性和LibraryName属性填写好后,Powerbuilder将自动调用CreateWindowExA函数。
如果控件是Animate、Header、HotKey、ListView、ProgressBar、StatusBar、Tab、ToolTip、 ToolBar、TrackBar、TreeView 或Up-Down控件的话,该控件的效果将立刻显示在布局视图上。
需要注意的是,在用C/C++编程时,类名都用#define定义的字面量来代替,如:
#define WC_IPADDRESSA "SysIPAddress32"
在Powerbuilder中读者要自己打开Commctrl.h查找与C/C++类名对应的字符串,对于IP地址控件它的C/C++类名为WC_IPADDRESSA,相应应填入字符串"SysIPAddress32"。

Style属性:32位十进制整形数,窗口风格,缺省为0。读者可将WinUser.h中的窗口风格的定义值转换为十进制,并可对多种风格进行组合。
对于控件该值至少为WS_CHILD即1073741824,不过我们创建的是外部控件用户对象,所以Powerbuilder可以确定风格必含有WS_CHILD,所以该属性是否逻辑或(or)1073741824都没关系。

Visible属性:布尔型,窗口风格。对应窗口风格WS_VISIBLE,在Powerbuilder自动调用CreateWindowExA或SetWindowLong时使用。

Enabled属性:布尔型,窗口风格。对应窗口风格WS_DISABLED,在Powerbuilder自动调用CreateWindowExA或SetWindowLong时使用。

Border属性:布尔型,窗口风格。对应窗口风格WS_BORDER,在Powerbuilder自动调用CreateWindowExA或SetWindowLong时使用。
在上文中谈到InitCommonControls带来的问题,这主要是在于使用公共控件的第一步是初始化公共控件。
■ 对于Animate、Header、HotKey、ListView、ProgressBar、StatusBar、Tab、ToolTip、ToolBar、TrackBar、TreeView或Up-Down控件,既可以调用InitCommonControls,又可以调用InitCommonControlsEx来初始化;
■ 对于其它控件,要求必须调用InitCommonControlsEx,而Powerbuilder在构造外部控件时调用的是InitCommonControls,势必造成一些控件不能成功初始化。

  笔者在用户对象的Constructor事件中加入了对InitCommonControlsEx的调用,仍然不成功,由此可知:Powerbuilder调用InitCommonControlsEx早于Constructor事件的执行,同样也早于窗口Open事件的执行,所以笔者建议在应用(Application)对象的Open事件中初始化控件(经一次初始化,可多次创建具有相同类名的控件)。考虑到封装原则可在用户对象中定义一个对象函数Initialize,在其中设置调用InitCommonControlsEx的参数并调用它,在应用对象的Open事件中调用它即可。
  在此列出InitCommonControlsEx的Powersoft原型:
  Function Boolean InitCommonControlsEx(stc_initcommoncontrolsex InitCtrls)Library "Comctl32.dll"
  结构stc_initcommoncontrolsex的成员为:
  ULong dwSize //8 Bytes
  ULong dwICC //公共控件类标识,具体参阅MSDN
  此外,Powerbuilder在调用CreateWindowExA后,自动调用了ShowWindow函数使控件显示出来。

  二、向控件发送消息

  在Powerbuilder中要发送消息我们一般用Send函数,但该函数的最后一个参数是一个长整形值,不能通过它来传递地址,所以我们要利用多态性原则,即在用户对象中声明多个局部外部函数(Local External Function) SendMessageA。


如果要传字符串的话可声明为:
Function ULong SendMessageA(Ulong hWnd,ULong Msg,ulong wParam,String lParam)Library "user32.dll"

如果要传无符号长整形数的指针可声明为:
Function ULong SendMessageA(Ulong hWnd,ULong Msg,ulong wParam,ref ULong lParam)Library "user32.dll"

如果要传结构变量的话可声明为:
Function ULong SendMessageA(Ulong hWnd,ULong Msg,ulong wParam,stc_point lParam)Library "user32.dll"
  由于Powerbuilder自动调用了CreateWindowExA并保存了返回的句柄,所以调用Handle(ecuoName)可以获得控件的句柄,其中ecuoName是该对象的实例名,在此建议,请为每条消息写一个函数,这样可以将对Handle函数的调用局限于用户对象内,使得外部代码与具体实例无关。
  现举一例,向IP地址控件发送GetAddress消息:
  UInt ecuo_ip_addr::GetAddress(ref ULong Addr){
  return SendMessageA(Handle(this),1126,0,Addr)
  }

  三、处理通知消息

  几乎所有的控件都会向父窗口发送通知消息,比如说:当IP地址控件获得键盘焦点后会向父窗口发送EN_SETFOCUS消息,通常我们需要处理这些消息。即使你用不着这些消息,但从代码复用角度来看也有必要为每个消息声明一个对应的事件便于以后使用。


理论上,为处理这些消息,我们只要为每个消息声明一个用户事件,并将该事件的消息标识设为与Windows消息对应的标识,如:与EN_SETFOCUS对应的标识是pbm_ensetfocus,然后在事件中添加处理脚本即可。

实际上,由于Sybase在设计Powerbuilder时为防止消息的错误分发,对消息的目的地(控件)进行了合法性判断,笔者猜测可能是通过控件的类名来判断的。对于EN_SETFOCUS消息,Sybase的逻辑似乎是该消息的目的地的类名必须是Edit、ComboBox、ListBox中的任意一个,所以传给IP地址控件的EN_SETFOCUS消息将因控件类名不合法而被丢弃,也就是事件脚本永远不会被执行。
不过通过对Powerbuilder的消息分发机制进行修改后就可以解决消息被丢弃的问题,下面将具体探讨如何实现,这种方法同样适用于那些没有消息标识对应的通知消息。
读者得先在用户对象中声明一个消息标识为pbm_command的用户事件,然后在事件脚本中写个Choose Case语句,进行消息分发。
Long ecuo_ip_addr::Event dispatch(Long hWndChild, UInt ChildID, UInt NotificationCode){
choose case notificationcode
case 256//EN_SETFOCUS
event SetFocus()
case 512//EN_KILLFOCUS
event KillFocus()
case 768//EN_CHANGE
event Change()
end choose
return 0
}

  代码中NotificationCode是dispatch事件的参数,用它来判断通知消息,常用的通知消息号可在WinUser.h中找到。要注意的是,事件返回值将影响其它消息的处理,这和CloseQuery等事件的返回值的作用是一样的。此外,为尽量不影响整个消息分发机制强烈建议将修改局限于用户对象中。

  通过对上述三个部分的讨论希望对读者有帮助,文本只涉及消息分发机制的一部分,日后笔者将与读者详细探讨消息分发机制。文字文字文字

关于本站 - 广告服务 - 会员指南 - 联系方法
Copyright ©2003-2011 源码天空 All Rights Reserved