精通OCX工程构架
1 工程构架
不能为MDI。ocx工程的主窗体嵌在浏览器中,当前的活动窗口为浏览器,如果
为MDI类型,当子窗体打开时,则找不到活动的MDI父窗体。所以,工程中所有
窗体的FormStyle属性都应设为fsNormal。
2 在程序中关闭浏览器
可以在程序中向浏览器发送关闭消息。在消息发送之前,必须得到浏览器的窗
口句柄。代码如下: PostMessage(GetActiveWindow,wm_close,0,0);
3 非主窗体的创建与关闭
在每个非主窗体创建之前应先判断其实例是否存在,存在的话则激活其实例
而不创建。
procedure OpenChild(cls:TFormClass;frm:TForm);
var
tmp:TForm;
i:integer;
begin
for i:=0 to Screen.FormCount -1 do
if CompareText(Screen.Forms[i].ClassName,cls.ClassName)=0 then begin
tmp:=Screen.Forms[i];
if tmp.WindowState =wsMinimized then
ShowWindow(tmp.Handle,sw_shownormal);
if not tmp.Visible then tmp.Visible :=true;
tmp.BringToFront;
exit;
end;
Application.CreateForm(cls,frm);
end;
其中cls为窗体的类名,frm为窗体的实例。如窗体的Name属性为Form1,cls即为
TForm1,frm为Form1。
应特别注意Application.CreateForm(cls,frm);一行。用此种方法创建的窗体关
闭时必须在OnClose事件中调用窗体的Release方法。Action:=caFree是不起作用
的。此行也可写成frm:=cls.Create(Application),此种情况下Action:=caFree
和Release都可以把窗体关闭并释放。
4 键盘操作
ActiveForm中对一些键盘操作进行了屏蔽。如tab键、按钮等组件的alt快捷操
作。对于tab,可以采取以下办法解决:在Edit等组件的OnKeyDown事件中进行
判断,如果是tab则移动焦点。代码如下:
if Key=vk_tab then Perform(wm_nextdlgctl,0,0);
同时,如果想让回车键也移动焦点,可以书写代码如下:
if (Key=vk_tab) or (Key=vk_return) then Perform(wm_nextdlgctl,0,0);
1 从EXE到OCX工程
此类转化有一个常用的也可以说较笨的的方法,把EXE工程主Form上的所有
元素和其事件处理程序都拷贝过来。此法虽说稳妥,但麻烦的一塌糊涂。容易让
人头大。下面着重介绍一个比较快捷省事的办法,通过类的继承来实现转化。概
括来说,就是把EXE工程的主Form直接作为OCX工程的主Form,并把其父类由TForm
改为TActiveForm。详细介绍如下。
(1)准备工作
在开始之前,最好把EXE工程的所有文件如.pas、.dfm(工程.dpr等除外)拷到
一个新的目录下。如果不想这样做,也可以把OCX工程保存到同一目录,但不要和
EXE工程重名,以免覆盖。生成一个ActiveForm(注意:此保持空白,不要往其上
放任何组件),为了便于说明,设其name为ActiveFormX,单元文件存为
ActiveFormX.pas,同时保存OCX工程。假设原来主Form的name为frmMain,单元文件
为MainForm.pas.
(2)改变继承关系
打开MainForm.pas,找到TfrmMain类的声明部分:把TfrmMain = class(TForm)
改为TfrmMain = class(TActiveFormX)
(3)把frmMain作为工程的主Form
打开ActiveFormX.pas,找到initialization 部分,如下所示:
initialization
TActiveFormFactory.Create(
ComServer,
TActiveFormControl,
TActiveFormX,
Class_ActiveFormX,
1,
'',
OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
tmApartment);
把第五行中TActiveFormX改为TfrmMain,这样OCX工程的主Form就成了原来EXE工程的
主Form,即TfrmMain。
(4)属性声明
查看一下delphi源码,可以看到下面的继承链条:
TCustomForm->TCustomActiveForm->TActiveForm
TCustomForm->TForm
TForm的部分published属性在TActiveForm未被声明,而这些属性存在于它们共同的父
类TCustomForm中,并且在public部分。所以,你如果在属性编辑器中改变了frmMain的
这些属性,delphi就会从当前类按TActiveFormX->TCustomActiveForm->TCustomForm顺
序在published部分查找并设置这些属性,而这三个父类published部分并未包含这些属
性。这样,delphi将提示地址错误。所以,只要在TActiveFormX中声明这些属性,问题
即可解决。以On开头的事件属性也是同样的道理。打开ActiveFormX.pas,把以下代码拷
到TActiveFormX的声明部分即可。
published
property Action;
property Align;
property AlphaBlend default False;
property AlphaBlendValue default 255;
property BiDiMode;
property BorderIcons;
property BorderStyle;
property ClientHeight;
property ClientWidth;
property TransparentColor default False;
property TransparentColorValue default 0;
property Ctl3D;
property UseDockManager;
property DefaultMonitor;
property DockSite;
property DragKind;
property DragMode;
property Enabled;
property ParentFont default False;
property FormStyle;
property HelpFile;
property Icon;
property Menu;
property ObjectMenuItem;
property ParentBiDiMode;
property Position;
property Visible;
property WindowState;
property WindowMenu;
property OnCanResize;
property OnClose;
property OnCloseQuery;
property OnConstrainedResize;
property OnDockDrop;
property OnDockOver;
property OnEndDock;
property OnGetSiteInfo;
property OnHide;
property OnHelp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnResize;
property OnShortCut;
property OnShow;
property OnStartDock;
property OnUnDock;
对于上面所列的事件属性,也不必全部都声明。frmMain中有处理程序的声明一下就
行了。
(5)更改事件连接
如果你在TfrmMain的OnPaint事件中写了代码,可以发现,这些代码是不会被执行
的。原因何在?打开ActiveFormX.pas,找到TActiveFormX的Initialize过程,可以发
现如下代码:
inherited Initialize;
OnActivate := ActivateEvent;
OnClick := ClickEvent;
OnCreate := CreateEvent;
OnDblClick := DblClickEvent;
OnDeactivate := DeactivateEvent;
OnDestroy := DestroyEvent;
OnKeyPress := KeyPressEvent;
OnPaint := PaintEvent;
原来问题出在这里,OnPaint事件被delphi吃掉了,改成了执行PaintEvent。怎么搞?
把次行注释掉吧,然后再写一个你自己的OnPaint事件处理过程,不要忘了带参数sender。
如:procedure MyPaint(Sender:TObject);
然后在TfrmMain的OnCreat事件中赋给OnPaint就行了。如:OnPaint:=MyPaint;
上面列举的几个事件都和OnPaint类似。模仿OnPaint就ok了。
2 从OCX到EXE工程
(1)OCX工程框架
综上所述,OCX工程最好不要把ActiveForm作为主Form,而另外生成一个普通Form
作为主Form,再按上面所说的方法进行处理。然后其它的数据操作Form也为普通Form,
被主Form调用。
(2)转化到EXE工程
只要你按(1)做了,问题就非常好办。生成一个普通工程,把OCX工程除ActiveForm
以外的所有Form加进工程就行了。
如果你的工程属于OCX工程,并且需要连接数据库服务器。那么,工程应该适
应不同的数据库连接参数,如数据服务器名、数据库名、用户名、密码等当它们发
生变化时,工程应不需修改。这就要求OCX工程能携带参数。怎么搞?往下看。
在你看这篇文章之前,最好先看一下三金所写的另外一篇文章 “EXE工程和OCX
工程的转化“。否则,后果自负。别怕,only a joke!:),不过,三金还是劝你看
一下,本篇你就会明白得快一些。如果你的OCX工程主Form是普通Form,并且继承于
工程中的ActiveForm,then,let's go on!
为了便于说明,假设工程中的ActiveForm的name为ActiveFormX,单元文件为
ActiveFormX.pas,工程主Form的name为frmMain,单元文件为MainForm.pas。总的说
来,就是在就是在TActiveFormX与TfrmMain之间加一个中间类,由此类完成参数的接
收。设此类为TActiveFormNewX,因为此类作为二者的中间类,就需要把TfrmMain的父
类由TActiveFormX改为该类。打开MainForm.pas,找到TfrmMain的声明:
TfrmMain=class(TActiveFormX)改为TfrmMain=class(TActiveFormNewX),并且,
TActiveFormNewX继承于TActiveFormX。TActiveFormNewX的声明和实现如下,你应该
把它拷到ActiveFormX.pas单元中TActiveFormX的后面。
TActiveFormNewX = class(TActiveFormX,IPersistPropertyBag)
public
ServerName,DBName,UserName, UserPassword:String;
protected
function IPersistPropertyBag.InitNew=PersistPropertyBagInitNew;
function IPersistPropertyBag.Load =PersistPropertyBagLoad;
function IPersistPropertyBag.Save =PersistPropertyBagSave;
function IPersistPropertyBag.GetClassID=PersistPropertyBagGetClassID;
function PersistPropertyBagInitNew:HResult;stdcall;
function PersistPropertyBagLoad(const pPropBag:IPropertyBag;Const pErrorLog:
IErrorLog):HResult; stdcall;
function PersistPropertyBagSave(const pPropBag:IPropertyBag;fClearDirty:BOOL;
fSaveAllProperties:BOOL):HResult; stdcall;
function PersistPropertyBagGetClassID(out classID:TCLSID):HResult; stdCall;
end;
function TActiveFormNewX.PersistPropertyBagInitNew:HResult;
begin
Result:=S_OK;
end;
function TActiveFormNewX.PersistPropertyBagLoad(const pPropBag:IPropertyBag;
Const pErrorLog:IErrorLog):HResult;stdCall;
var
Str:OleVariant;
begin
if pPropBag.Read('ServerName', Str ,pErrorLog) = S_OK then
ServerName :=Str;
if pPropBag.Read('DBName', Str ,pErrorLog) = S_OK then
DBName :=Str;
if pPropBag.Read('UserName', Str ,pErrorLog) = S_OK then
UserName :=Str;
if pPropBag.Read('UserPassword', Str ,pErrorLog) = S_OK then
UserPassword :=Str;
Result:=S_OK;
end;
function TActiveFormNewX.PersistPropertyBagSave(const pPropBag:IPropertyBag;
fClearDirty:BOOL;fSaveAllProperties:BOOL):HResult;
begin
Result:=S_OK;
end;
function TActiveFormNewX.PersistPropertyBagGetClassID(out classID:TCLSID):
HResult; stdCall;
begin
Result:=S_OK;
end;
从代码不难看出,此类有四个public成员:ServerName,DBName,UserName,
UserPassword。参数就是传给了它们。既然是public成员,且TfrmMain是该类子
类,所以,就可以在TfrmMain中得到这四个值。注意,应该把代码写在TfrmMain
的OnShow中,而不是OnCreate。
在IE中应这样书写:
<OBJECT
classid="clsid:3E71BE48-9AE1-431D-BD68-B17AA355BF38"
codebase="ActiveFormProj1.ocx#version=1,0,0,0"
width=538
height=350
align=center
hspace=0
vspace=0
>
<param name=ServerName value=sanjin>
<param name=DBName value=tian>
<param name=UserName value=user>
<param name=UserPassword value=>
</OBJECT>
如果你想添加或减少参数,不用我多说了吧!ok,解决。
有朋友要发表意见:“我的OCX工程主Form是TActiveForm,照你的办法,我
可以在让我的TActiveForm继承于此类,我在TActiveForm中按同样的办法接收不
就的了!“。那我告诉你,参数的确能得到,不过你如果重新打开以下你的工程,
按F11看一下,TActiveForm的说有属性的和普通Form没什么两样了,虽然程序不
出错,但除了什么事和我无关。你要问为什么?我现在头有些大,要休息了。