笔者在《用 Windows 公共控件增强 PB 应用的界面》一文中,曾经讨论过使得 PB 能支持较新的 Win32 公共控件的方法。当时提出了在应用对象的 Open 事件中用 InitCommonControlsEx 函数来代替 PB 自动调用的 InitCommonControls 函数,这种方法的缺点很明显:
-
用户程序员必须关注 Win32 公共控件的初始化。用户程序员必须在应用对象的 Open 事件中调用InitCommonControlsEx 函数为一类控件初始化。
-
在设计时期不能实现所见即所得。由于在设计时期,InitCommonControlsEx 函数可能未被调用,所以用户程序员将看不见控件效果。
在 PB9 进行 beta 测试之际,笔者和 Sybase 的程序员进行了探讨,提出了 3 个修改方案,分别为:
- 在PB的源代码中原先调用 InitCommonControls 的地方,删除调用 InitCommonControls 的代码,改成一个根据控件类名填充 INITCOMMONCONTROLSEX 结构的 switch…case 语句块,然后再调用 InitCommonControlsEx。这个方案对用户程序员来说最方便,但这样会迫使 Sybase 跟在 Microsoft 后面添加更多的 case ClassName 块来支持在不久将来推出的新控件。所以该方案被否决了。
- 将 INITCOMMONCONTROLSEX 结构的 dwICC 成员直接暴露在外,用户直接在属性标签页上设置该值。这会使实现细节暴露,不便于日后的代码修改,所以不宜采用。
- 在外部可视用户对象的 create 事件中调用 InitCommonControlsEx 函数进行控件初始化。该方案被最终采纳。它在不增加外部可视控件的开发者的工作量的情况下,省去了用户程序员和 Sybase 的麻烦!
在讨论 PB 的内部事件 Create 和 Destroy 之前,先熟悉一下 Source Editor。Source Editor 提供 PB 对象基于文本的定义。我们可以右击任何 PB 对象,在弹出菜单中选择 Edit Source 菜单项,来调用该功能。现以 w_demo 的源码为例进行讨论:
global type w_demo from window
end type
type uo_1 from evuo_datetimepick within w_main /* evuo_datatimepick 使用的是 DataTimePick 控件 */
end type
end forward
global type w_demo from window /* w_demo 的成员变量声明或定义 */
integer width = 2533
integer height = 1392
boolean titlebar = true
string title = " Demonstration "
boolean controlmenu = true
boolean minbox = true
boolean maxbox = true
boolean resizable = true
long backcolor = 67108864
string icon = " AppIcon! "
boolean center = true
uo_1 uo_1
end type
global w_demo w_demo
on w_demo.create /* Create 相当于 C++ 的构造函数 */
MessageBox( " w_demo " , " 事件:Create( )~n(在创建uo_1之前) " )
this .uo_1 = create uo_1 /* 创建 uo_1 对象 */
MessageBox( " w_demo " , " 事件:Create( )~n(在创建uo_1之后) " )
this .Control[] = { this .uo_1}
end on
on w_demo.destroy /* Destroy 相当于 C++ 的析构函数 */
MessageBox( " w_demo " , " 事件:Destroy( )~n(在删除uo_1之前) " )
destroy( this .uo_1)
MessageBox( " w_demo " , " 事件:Destroy( )~n(在删除uo_1之后) " )
end on
event open;MessageBox( " w_demo " , " 事件:Open( ) " )
end event
event close;MessageBox( " w_demo " , " 事件:Close( ) " )
end event
type uo_1 from evuo_datetimepick within w_main /* uo_1 的成员变量的声明或定义 */
integer x = 100
integer y = 100
integer taborder = 10
end type
event constructor;call super::constructor;
MessageBox( " uo_1 " , " 事件:Constructor( ) " )
end event
event destructor;call super::destructor;
MessageBox( " uo_1 " , " 事件:Destructor( ) " )
end event
在窗口 w_demo 上布放了一个外部可视用户对象 (External Visual User Object) uo_1,该对象实现了 Win32 公共控件 DateTimePick。在源码中的 MessageBox 函数调用是为了测试各个事件的执行次序而写的,同样在 evuo_datetimepick 对象源码的 create 和 destroy 事件中也有对 MessageBox 函数的调用。通过运行程序,我们可以获得如下的执行次序:
"w_demo" | "事件:Create( )~n(在创建uo_1之前)" |
"evuo_datetimepick" | "事件:Create( )" |
"w_demo" | "事件:Create( )~n(在创建uo_1之后)" |
"uo_1" | "事件:Constructor( )" |
"w_demo" | "事件:Open( )" |
"w_demo" | "事件:Close( )" |
"uo_1" | "事件:Destructor( )" |
"w_demo" | "事件:Destroy( )~n(在删除uo_1之前)" |
"evuo_datetimepick" | "事件:Destroy( )" |
"w_demo" | "事件:Destroy( )~n(在删除uo_1之后)" |
通过上表,可以肯定 Create 和 Destroy 的功能和 C++ 的构造函数和析构函数一样。由于有这样的认识,笔者就假设外部可视用户对象的 Create 事件的执行早于对 CreateWindowExA 函数的调用。因而,在外部可视用户对象的 Create 事件中加入对 InitCommonControlsEx 函数的调用,可让对象在设计时期就所见即所得。以 evuo_datetimepick 为例,修改代码如下:
stc_initcommoncontrolsex InitCtrls
InitCtrls.dwSize = 8
InitCtrls.dwICC = 256
InitCommonControlsEx(InitCtrls)
end on
保存以后,无论何时在窗口上布放该对象都正常显示。
此外,Source Editor 对处理继承引起的编译错误很有效。如果在父类对象中删除了一个控件,但派生类中引用了该控件,则派生类因编译错误而不能打开。以往只能用版本控制来恢复,然后先注释掉派生类中的引用,然后删除父类中的控件。现在用 Source Editor 则可以直接编辑派生类,并改正之。同样,通过修改跟从在 from 之后的父类名,可以很方便的改变对象的父类。