正在开发一个非.net得数据表格组件,用到.net应用中去……
在家正在开发一个grid COM组件,因为实在用.net Winform提供的DataGrid不爽,功能太少了,搞什么都得自己写大堆代码,连最基本得行选都得自己写datagrid.select(...) ...烦了;还是封装几个COM组件,反正系统也只需要运行到win2k的工控机上;大致确定了一下功能和名称,叫做GridCellPro组件,支持多种功能,比如合计列,合计行,打印,中文金额,过滤,排序,加亮,列排序,固定列,多种CELL;还有一个就是报表,水晶报表很强大,可和我使用习惯不一样,也许是习惯了borland的贴心设计吧,还是做好表格组件再说吧,希望能改善一下开发效率。
目前编写完框架代码,已经把表格框架搞完了,只是属性定制对话框还有点问题,到网上搜来个文章看了看,还不错,关键得地方都讲到了;顺便也收录进来,今后忘了又看。
原始地址:http://hubdog.csdn.net/Hubdog/ActiveX2.htm
转自哈巴狗的小窝
完善ActiveX控件
从VCL出发生成一个基本的ActiveX控件是很容易的,但为了让它更容易使用,比如添加属性页编辑器,还需要做一些额外的工作。接下来我们将继续完善ListX控件,这次不是改进它运行时的功能,而是增强它设计时的编辑功能,为它提供一个属性页和下拉式属性编辑器。
属性列表
如图1.7所示,在Visuabl Basic的窗体上放置一个ListBoxX控件,这时可以注意到在属性察看器中Cursor 和DragCursor 属性,不像通常在Delphi的属性察看器中所看到的ListBox控件的相应属性那样显示crDefault 和其他crDrag 枚举变量,而是显示0和-12的数值。这是因为TCursor 类型在Delphi中是整数类型,所以当ActiveX control wizard转化TListBox 控件时,Cursor属性就按一个整数属性来处理了。这样,在Visual Basic中我们要想设定列表框控件的光标为时钟形状,我们必须设定Cursor 属性为-11, 它等价于定义在Controls单元中的crHourGlass 常数值,但这样显得非常不直观,很容易忘记和混淆。
图1.7
同时,在Delphi中使用TListBox 控件时,除了是显示一个符号名而不是数值外,属性察看器还会显示一个下拉式列表,里面包含了所有可能的光标值,那么我们能不能使我们的ActiveX控件在Visual Basic中有同样的属性列表呢?Nothing is impossible!我们可以重载TActiveXControl类的GetPropertyString、GetPropertyStrings和GetPropertyValue 方法来做到。下面就是修改后的ListBox控件代码:
unit ListBoxImpl;
interface
uses
Windows, ActiveX, Classes, Controls, Graphics, Menus, Forms, StdCtrls,
ComServ, StdVCL, AXCtrls, DelphiByDesignXLib_TLB;
type
TListBoxX = class( TActiveXControl, IListBoxX )
private
{ Private declarations }
FDelphiControl: TListBox;
//省略…
protected
{ Protected declarations }
procedure InitializeControl; override;
procedure EventSinkChanged(const EventSink: IUnknown); override;
procedure DefinePropertyPages( DefinePropertyPage: TDefinePropertyPage); override;
function GetPropertyString( DispID: Integer; var S: string ): Boolean; override;
function GetPropertyStrings( DispID: Integer; Strings: TStrings ): Boolean; override;
procedure GetPropertyValue( DispID, Cookie: Integer; var Value: OleVariant ); override;
{ Methods that support properties }
. . .
end;
implementation
uses
TabWidthPpg, AboutListBox, SysUtils;
function TListBoxX.GetPropertyString( DispID: Integer;var S: string ): Boolean;
begin
case DispID of
5: // 5 =IlistBoxXDisp接口中DragCursor属性的DispID
begin
S := CursorToString( Get_DragCursor );
Result := True;
end;
26: // 26 = IlistBoxXDisp接口中Cursor属性的DispID
begin
S := CursorToString( Get_Cursor );
Result := True;
end;
else
Result := False;
end;
end;
function TListBoxX.GetPropertyStrings( DispID: Integer; Strings: TStrings ): Boolean;
var
I: Integer;
Cookie: Integer;
TempList: TStringList;
begin
case DispID of
5, // 5 =IlistBoxXDisp接口中DragCursor属性的DispID
26: // 26 = IlistBoxXDisp接口中Cursor属性的DispID
begin
TempList := TStringList.Create;
try
GetCursorValues( TempList.Append );
for I := 0 to TempList.Count - 1 do
begin
Cookie := StringToCursor( TempList[ I ] );
Strings.AddObject( TempList[ I ], TObject( Cookie ) );
end;
finally
TempList.Free;
end;
Result := True;
end;
else
Result := False;
end;
end;
procedure TListBoxX.GetPropertyValue( DispID, Cookie: Integer;
var Value: OleVariant );
begin
case DispID of
5, // 5 =IlistBoxXDisp接口中DragCursor属性的DispID
26: // 26 = IlistBoxXDisp接口中Cursor属性的DispID
begin
{ Cookie 代表被选的项目}
Value := Cookie;
end;
end;
end;
{= 省略…}
initialization
TActiveXControlFactory.Create( ComServer, TListBoxX, TListBox,
Class_ListBoxX, 1, '{B19A64E4-644D-11D1-AE4B-444553540000}', 0);
end.
这里TListBoxX 控件由于是继承于TActiveXControl,所以在TListBoxX类中可以重载上面的方法。
GetPropertyString 函数在属性察看器显示一个属性时会被调用,通过重载这个方法,我们可以为一个属性值提供一个字符串表达。它同Delphi的属性编辑器TPropertyEditor类的GetValue方法比较类似。
GetPropertyString 函数会提供两个参数,第一个是被请求字符串表达的属性的dispid。因为所有控件属性的显示都会调用这个方法,所以必须过滤要处理的属性,这通过用Case语句来检验Dispid就可以了。要想确定每个属性对应的Dispid,可以察看类型库接口单元。类型库单元的代码如下:
unit DelphiByDesignXLib_TLB;
{ This file contains pascal declarations imported from a type library.
This file will be written during each import or refresh of the type
library editor. Changes to this file will be discarded during the
refresh process. }
interface
uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;
const
LIBID_DelphiByDesignXLib: TGUID =
'{B19A64DB-644D-11D1-AE4B-444553540000}';
const
{ Component class GUIDs }
Class_ListBoxX: TGUID = '{B19A64DE-644D-11D1-AE4B-444553540000}';
type
{ Forward declarations: Interfaces }
IListBoxX = interface;
IListBoxXDisp = dispinterface;
IListBoxXEvents = dispinterface;
{ Forward declarations: CoClasses }
ListBoxX = IListBoxX;
{ Forward declarations: Enums }
TxBorderStyle = TOleEnum;
TxDragMode = TOleEnum;
TxImeMode = TOleEnum;
TxListBoxStyle = TOleEnum;
TxMouseButton = TOleEnum;
{ Dispatch interface for ListBoxX Control }
IListBoxX = interface(IDispatch)
['{B19A64DC-644D-11D1-AE4B-444553540000}']
function Get_DragCursor: Smallint; safecall;
procedure Set_DragCursor(Value: Smallint); safecall;
. . .
function Get_Cursor: Smallint; safecall;
procedure Set_Cursor(Value: Smallint); safecall;
. . .
procedure AboutBox; safecall;
. . .
property DragCursor: Smallint
read Get_DragCursor write Set_DragCursor;
. . .
property Cursor: Smallint read Get_Cursor write Set_Cursor;
end;
{ DispInterface declaration for Dual Interface IListBoxX }
IListBoxXDisp = dispinterface
['{B19A64DC-644D-11D1-AE4B-444553540000}']
property BorderStyle: TxBorderStyle dispid 1;
property Color: TColor dispid 2;
property Columns: Integer dispid 3;
property Ctl3D: WordBool dispid 4;
property DragCursor: Smallint dispid 5;
property DragMode: TxDragMode dispid 6;
property Enabled: WordBool dispid 7;
property ExtendedSelect: WordBool dispid 8;
property Font: Font dispid 9;
property ImeMode: TxImeMode dispid 10;
property ImeName: WideString dispid 11;
property IntegralHeight: WordBool dispid 12;
property ItemHeight: Integer dispid 13;
property Items: IStrings dispid 14;
property MultiSelect: WordBool dispid 15;
property ParentColor: WordBool dispid 16;
property ParentCtl3D: WordBool dispid 17;
property Sorted: WordBool dispid 18;
property Style: TxListBoxStyle dispid 19;
property TabWidth: Integer dispid 20;
property Visible: WordBool dispid 21;
procedure Clear; dispid 22;
property ItemIndex: Integer dispid 23;
property SelCount: Integer readonly dispid 24;
property TopIndex: Integer dispid 25;
property Cursor: Smallint dispid 26;
procedure AboutBox; dispid -552;
end;
{ Events interface for ListBoxX Control }
IListBoxXEvents = dispinterface
['{B19A64DD-644D-11D1-AE4B-444553540000}']
procedure OnClick; dispid 1;
procedure OnDblClick; dispid 2;
procedure OnKeyPress(var Key: Smallint); dispid 3;
procedure OnColorItem(Index: Integer;
var Color: TColor); dispid 4;
end;
implementation
end.
从上面可以看到,与控件属性对应的Dispid可以在IListBoxXDisp 声明部分找到。
GetPropertyString 函数的第二个参数是一个字符串变量。这个字符串变量将会被显示为相应的属性值,但要注意的是函数的返回值一定要设为True,返回False 将使属性察看器忽略返回的字符串参数。
在上面的GetPropertyString 方法中,使用了CursorToString 函数来转化当前属性值为一个字符串,同时Get_Cursor和Get_DragCursor方法被用来获得当前的属性值。
实现GetPropertyString 方法可以确保对于每个Cursor数值都会有一个字符串来说明它,但是如何显示所有光标的下拉列表呢?这时就必须重载GetPropertyStrings 和GetPropertyValue 方法了。GetPropertyStrings 负责生成一个将要显示在下拉列表中的字符串列表。当用户从列表中选择了一个列表项后,GetPropertyValue 方法就会被调用来返回真正的数值。
首先让我们来看GetPropertyStrings 函数,这个方法有两个参数:一个是Dispid,一个是字符串列表,Dispid自然还是对应于操作的属性,而字符串列表则是用来容纳将要显示在下拉列表中的字符串,此外,字符串列表还必须为每个列表项保存一个唯一的“cookie”值,当用户选择了一个列表项后,对应的cookie值会传递给GetPropertyValue 方法。
为了把cookie值同列表中的字符串关联起来,调用Strings.AddObject 方法来添加字符串到列表中:
try
GetCursorValues( TempList.Append );
for I := 0 to TempList.Count - 1 do
begin
Cookie := StringToCursor( TempList[ I ] );
Strings.AddObject( TempList[ I ], TObject( Cookie ) );
end;
finally
图1.8 |
把要显示的字符串作为第一个参数,而第二个参数为Cookie值来调用。Cookie值可以取任意值,这里使用的是Cursor本身对应的整数值。注意AddObject方法的第二个参数需要是TObject类型的参数,所以这里进行了一次类型映射。
GetPropertyValue 方法则有三个参数:第一个参数是属性对应的Dispid,第二个参数是对应于被选列表项的Cookie值,而第三个是用来返回属性值的参数。这个方法的处理非常简单,对于我们这个例子来说,只要把Cookie值返回就可以了,因为它就是实际的Cursor对应的值。
实现这些方法后,我们要做的就是重新注册控件了,图1.8显示了新界面更加友好的属性下拉列表。
实现属性页支持
仔细看一下Visual Basic中的ListBoxX控件的属性察看器,就会发现Items 属性没有显示在其中,但ListBoxX控件是有Items 属性的,其实这是因为ActiveX Control Wizard把它由published属性声明为public类型的属性了,这使得我们只能在运行时操作Items 属性,而无法在设计时编辑Items 属性。因为Visual Basic没有为Items类型的属性提供缺省的属性编辑器,所以ActiveX Control Wizard就没有published Items属性。
图1.9 |
但可以通过自定义的属性页编辑器来提供对Items属性设计时的编辑支持。属性页总能在各种支持ActiveX控件程序中看到,它提供了方便的属性编辑功能。Delphi 提供了一些现成的预定义的属性页支持,可以把它同ActiveX控件相关联。每个属性页有一个对应的Class ID,它声明在AxCtrls单元中,图1.9列出了每个ID和对应的属性页的功能:
这些预定义的属性页被设计成可以用于任意的ActiveX控件。每个属性页都使用运行时类型信息 (RTTI)来确定控件中哪个属性可以使用相应的属性页进行编辑,并会把每个属性名添加到一个下拉编辑框中,这样的话,一个属性页就可以编辑所有相同类型的属性了。图1.10显示了用来编辑ListBoxX控件的Items属性的字符串属性页:
要想把预定义的属性页同ActiveX控件相关联,只要在控件的DefinePropertyPages 方法中添加对DefinePropertyPage方法的调用就可以了,下面代码把字符串和字体属性页同ListBoxX控件进行了关联:
图1.10
{ TListBoxX }
procedure TListBoxX.DefinePropertyPages(
DefinePropertyPage: TDefinePropertyPage );
begin
{ 把预定义的属性页同控件关联 }
DefinePropertyPage( Class_DStringPropPage );
DefinePropertyPage( Class_DFontPropPage );
//省略…
end;
用户定制的属性页
除了使用预定义的属性页外,还可以创建自定义的属性页。选菜单File|New,切换到ActiveX页,然后选择Property Page项,Delphi将生成一个属性页的窗体文件和单元文件。下面就是生成的TabWidthPpg属性页的代码:
unit TabWidthPpg;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, StdCtrls, ExtCtrls, Forms,
ComServ, ComObj, StdVcl, AxCtrls,
ComCtrls;
type
TPpgTabWidth = class(TPropertyPage)
GrpPreview: TGroupBox;
GrpTabWidth: TGroupBox;
LstPreview: TListBox;
ChkUseTabs: TCheckBox;
TrkTabWidth: TTrackBar;
procedure ChkUseTabsClick(Sender: TObject);
procedure TrkTabWidthChange(Sender: TObject);
private
{ Private declarations }
protected
procedure UpdatePropertyPage; override;
procedure UpdateObject; override;
public
{ Public declarations }
end;
const
Class_PpgTabWidth: TGUID =
'{8BE91420-9070-11D1-AE4B-44455354616F}';
implementation
{$R *.DFM}
procedure TPpgTabWidth.UpdatePropertyPage;
var
I: Integer;
begin
{ 使用OleObject更新对象}
{ 从对象中复制字符串到预览列表框 }
for I := 0 to OleObject.Items.Count - 1 do
LstPreview.Items.Add(OleObject.Items[I]);
ChkUseTabs.Checked := OleObject.TabWidth > 0;
TrkTabWidth.Position := OleObject.TabWidth div 4;
LstPreview.TabWidth := OleObject.TabWidth;
end;
procedure TPpgTabWidth.UpdateObject;
begin
{ 使用OleObject更新控件TabWidth属性}
OleObject.TabWidth := LstPreview.TabWidth;
end;
procedure TPpgTabWidth.ChkUseTabsClick(Sender: TObject);
begin
TrkTabWidth.Enabled := ChkUseTabs.Checked;
if ChkUseTabs.Checked then
LstPreview.TabWidth := TrkTabWidth.Position * 4
else
LstPreview.TabWidth := 0;
end;
procedure TPpgTabWidth.TrkTabWidthChange(Sender: TObject);
begin
Modified;
LstPreview.TabWidth := TrkTabWidth.Position * 4;
end;
initialization
TActiveXPropertyPageFactory.Create(
ComServer, TPpgTabWidth, Class_PpgTabWidth);
end.
图1.11 |
注意上面TPpgTabWidth 类是从TPropertyPage继承来的,它的上层父类是TCustomForm。因此我们可以像普通的Delphi窗体一样定制属性页。这个属性页允许用户可视化的调整ActiveX控件的TabWidth 属性。图1.11就是属性页示意图。
属性页显示时应该能够根据存储在ActiveX控件中的数据更新相应属性页页面的显示,这一切都可以在UpdatePropertyPage方法中来加以实现。上面代码中的TPpgTabWidth. UpdatePropertyPage 方法就是先从ActiveX控件中获得字符串,接着设定ListPreview 列表框,然后是checkbox状态和TabWidth 属性被初始化。一旦激活属性页后,必须能够根据用户输入更新ActiveX控件的属性,这是通过实现UpdateObject 方法来完成的。上面代码中TPpgTabWidth.UpdateObject 方法就是简单的根据预览列表的TabWidth设定更新ActiveX控件的。要注意的是方法中用到的OleObject 就代表ActiveX控件,因此可以使用它来设定数据。
同预定义的属性页一样,我们也要把自定义的属性页同ActiveX控件相关联。同前面一样,通过在DefinePropertyPages 方法中添加对DefinePropertyPage的调用就可以了,这回参数就是新建立的属性页的GUID ,下面就是实现代码:
{ 把用户定制的属性页同控件相关联 }
DefinePropertyPage( Class_PpgTabWidth );
分发ActiveX控件
显然分发包中必须包括编译生成的*.ocx 文件,如果ActiveX项目中用到了其他运行时包,我们还需要分发相应的文件。
如果创建ActiveX控件时选择生成设计时的许可文件,还需要分发相应的*.lic文件。
另外,如果创建的ActiveX项目用到了IStrings 接口或预定义的字体、颜色、字符串或图像属性页的话,我们还必须分发标准的VCL类型库,它包括StdVcl32.dll和StdVcl32.tlb类型库文件。这两个文件是被Delphi安装到了Windows的系统目录下(比如,C:\WinNT\System32)。凡是使用了预定义的属性页的控件必须分发StdVcl32.dll,然而如果只使用了IStrings接口的话,可以只分发StdVcl32.tlb类型库。
注册ActiveX的工具Turbo Register Server
要想使ActiveX控件生效,必须更新注册表信息,这可以使用Turbo Register Server (TRegSvr)程序来完成,这个程序是Borland作为一个演示程序提供的。例子源代码位于Demos\ActiveX\TRegSvr目录下。
TRegSvr 是一个简单的命令行程序,它支持一些开关来控制ActiveX控件的注册,除了要注册的文件名外不加任何参数的话就会注册指定文件。加了-u开关的话就会注销ActiveX控件,-t开关是用来表明ActiveX控件的类型库也应该被注册,如果要注册的文件名为*.tlb,则这个开关可以不设定。-q开关表示TRegSvr不显示任何输出信息,这使得它比较适用于嵌入于安装程序中。
下面是使用TRegSvr程序注册TListBoxX 控件的例子示意:
TRegSvr -q XXX.ocx
TRegSvr -q StdVcl32.dll