从已有的控件继承编写控件时,如何才能屏蔽父类已经Published了的属性呢?。。。。。(100分)

  • 主题发起人 主题发起人 littlefat
  • 开始时间 开始时间
L

littlefat

Unregistered / Unconfirmed
GUEST, unregistred user!
我因为自己的需要编写了一个自定义的控件,从dxDBGrid(DevExpress公司的著名Grid控件)继承,其中封装了一个控件内部使用的DataSource和ADOStoredProc,并且在控件的Create中已经设定了ADOStoredProc->DataSource->dxDBGrid的数据连接关系,所以dxDBGrid的DataSource属性应该屏蔽掉(不出现在设计时期的Object Inspector中),但我不知道如何才能屏蔽!
部分代码如下(略去了不相干的函数和过程):

type
TListMode=(lmtop,lmQuickSearch,lmQuery,lmAll); //列出模式

TEndxDBGrid = class(TdxDBGrid)//从dxDBGrid继承
private
{ Private declarations }
FADOSP:TADOStoredProc;
FDataSource:TDataSource;

protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent);override;
destructor Destroy;override;

published
{ Published declarations }
property ADOStoredProc:TADOStoredProc read FADOSP; //发布ADOStoedProc成一个属性
end;

procedure Register;

implementation

{ TEndxDBGrid }

constructor TEndxDBGrid.Create(AOwner: TComponent);
begin
inherited;

FDataSource:=TDataSource.Create(nil);//创建控件内部使用的DataSource
FADOSP:=TADOStoredProc.Create(Self); //创建控件内部使用的ADOStoreProc
FADOSP.Name:=Self.Name+'ADOSP'; //ADOStoedProc的名字

FDataSource.DataSet:=FADOSP; //建立连接关系
Self.DataSource:=FDataSource;

FInited:=False;
end;

destructor TEndxDBGrid.Destroy;
begin
FADOSP.Close;
FADOSP.Free;
FDataSource.Free;

inherited;
end;
 
重新声明一个public/protected同名的属性(隐藏,最好不要更该该属性的意义,否则我不能确定对方强制转换的时候是如何访问的)
protected
property DataSource: TDataSource read GetDataSource write SetDataSource;

function GetDataSource: TDataSource;
begin
Result:= Inherited DataSource;
end;

procedure SetDataSource(Value: TDataSource);
begin
Inherited DataSource:= Value;
end;
 
修改代码为:
type
TdxDBGridEn = class(TdxDBGrid)
private
{ Private declarations }
。。。
procedure SetDataSource(const Value: TDataSource);
protected
{ Protected declarations }
property DataSource:TDataSource read GetDataSource write SetDataSource;
public
{ Public declarations }
constructor Create(AOwner: TComponent);override;
destructor Destroy;override;

implementation
。。。
function TdxDBGridEn.GetDataSource: TDataSource;
begin
Result:= Inherited DataSource;
end;

procedure TdxDBGridEn.SetDataSource(const Value: TDataSource);
begin
Inherited DataSource:=Value;
end;

但是编译时发出警告说我隐藏了基类的:[Warning] dxDBGridEn.pas(14): Method 'GetDataSource' hides virtual method of base type 'TCustomdxDBGrid'
 
从TdxDBGrid的上一层继承,自己选择要发布的属性。
 
根据zjan521兄的提示,修改后代码如下:

type
TdxDBGrid3En = class(TdxDBGrid)
private
{ Private declarations }
FADOSP:TADOStoredProc;
FDataSource:TDataSource;

procedure SetDataSource(const Value: TDataSource);

protected
{ Protected declarations }
function GetDataSource: TDataSource;override; //注①
public
{ Public declarations }
property DataSource:TDataSource read GetDataSource write SetDataSource;

constructor Create(AOwner: TComponent);override;
destructor Destroy;override;

published
{ Published declarations }
Property ADOStoreProc:TADOStoredProc read FADOSP write FADOSP;
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents('iChinese', [TdxDBGrid3En]);
end;

{ TdxDBGrid3En }

constructor TdxDBGrid3En.Create(AOwner: TComponent);
begin
inherited;

FDataSource:=TDataSource.Create(nil); //创建控件内部使用的DataSource
FDataSource.Name:=Self.Name+'DataSource';
FADOSP:=TADOStoredProc.Create(Self); //创建控件内部使用的ADOStoreProc
FADOSP.Name:=Self.Name+'ADOSP'; //ADOStoedProc的名字

FDataSource.DataSet:=FADOSP; //建立连接关系
Self.DataSource:=FDataSource;

end;

destructor TdxDBGrid3En.Destroy;
begin
FADOSP.Close;
FADOSP.Free;
FDataSource.Free;

inherited;
end;

function TdxDBGrid3En.GetDataSource: TDataSource;
begin
Result:=FDataSource; //注②
//Result:=Inherited DataSource;
//Inherited;
end;

procedure TdxDBGrid3En.SetDataSource(const Value: TDataSource);
begin
Inherited DataSource:=Value; //注③
//Inherited;
//FDataSource:=Value;
end;

end.

说明:
①:基类中已经有了GetDataSource方法,所以只能overrider了,并且只能放在protect 区域;
②③:如何按照兄台的写法,编译可以通过,但是在设计期,只要一将该控件拖放到Form上,整个Delphi IDE立即退出!连提示都没有![:(]; 只有将③处写成:
procedure TdxDBGrid3En.SetDataSource(const Value: TDataSource);
begin
Inherited;
FDataSource:=Value;
end;
才行。

但是,不管怎样,将控件拖放到Form上后,该控件的DataSource属性仍出现在Object Inspector中![:(][:(]

说实话我对Result:= Inherited DataSource; Inherited DataSource:=Value;的写法的意思不成很清楚,为什么可以这么写呢?这样写是什么意思呢?还请兄台不吝赐教!
 
测试当中
 
不好意思,当时没有理解你的意思,出错了招。
1.Result:= Inherited DataSource;//Inherited 表示向上继承,也就是获取父类的DataSource属性.Inherited DataSource:=Value;//设置父类的属性
2.做不到完全隐藏.而如果更该该属性的意义和逻辑,在用户强制类型转换的时候可能会带来逻辑上的混乱.
3.补救方法 最好的是为你的控件的属性生成一个属性编辑器(paDialog类型)这样在IDE当中属性的显示是灰色的,跟随着一个[...],在属性编辑当中,弹出说明信息或者干脆什么都不作,这样在IDE当中就不能设置编辑该属性,令外
published
property DataSource: TDataSource read _GetDataSource write _SetDataSource stored False;//stored False在设计期不存储该值

function _GetDataSource: TDataSource;
begin
Result:= Inherited DataSource;
end;

procedure _SetDataSource(Value: TDataSource);
begin
if csDesigning in ComponentState then
begin
//当处于设计期的时候,不作任何操作,或者提示信息
end;
Inherited DataSource:= Value;
end;
这样,
A. 在IDE-〉Object Inspecter不能编辑属性(安装属性编辑器)
B. 操作无效(覆盖属性之后)并且值并不保存(另外在_GetDataSource中也可通过判断是在设计时还是运行时从而在没有安装属性编辑器的时候并不显示内部程序设计的值)。而在运行时,所有的代码逻辑都是不受影响(否则不符合OOP的定义,逻辑上是混乱的)的,包括类型转换。
 
感谢各位特别是zjan521兄!先给分了。不过我仍有问题要请教:

先说一些题外话。

①这是我第一次尝试编写有实用价值的控件。以前我没有任何编写控件的经验,翻阅了很多书,也在网上搜索良久,可惜没有什么结果,关于控件开发的资料实在是太少了,仅仅看到一些诸如Component Message和Component Notify之类的极其简单的介绍,连个示例都没有。我也对这些控件消息和控件通知服务(过程)只有一点皮毛的理解。此次提问,zjan521代码中的“if csDesigning in ComponentState then”我还是第一次看到(猜测其意义是 if是在设计时期 then... ,由此我想Delphi中应该还有很多关于如何控制控件在设计时期和运行时期不同的状态的处理或者判断方法,这些我一点都不懂)。唯一看到有一点价值的资料是《控件开发FAQ》还是台湾人翻译的,其中提到的控件开发参考资料《Developing Delphi Component》等都无法找到。所以,恳请zjan521兄能否给出一些控件开发的参考资料或实例?

②我使用Delphi一年多,从开始时用Access作数据库,到后来转到SQL Server上来,编程方法也逐步从单纯使用数据库控件Open、Edit、Post到使用嵌入SQL语句,而现在正尝试使用面向对象的方法来封装数据对象(OR Mapping?)以及对数据的操作,这其中也得到一个很忙碌的高手朋友的一些启发和指导(可惜他太忙了,大部分情况下我只是听到他的只言片语,其他都是我自己琢磨)。
我最近编写了一个管理软件,在代码中没有用到任何SQL语句,几乎所有的数据操作都是通过封装的对象的方法来完成的。现在那软件基本已经完成投入使用了。正是因为使用了面向对象的方法,在软件设计中有几次大的需求改变,而我只是修改了一些类的实现方法和属性就能对付了,基本的架构没有太大的变化,这一点跟以前使用Open/Edit/Post的方法比起来工作量小得多了。总结下来,使用面向对象的方法来封装数据对象,对最基本的五个个操作CRUD(微软总结的Create,Read,Upate,Delete)和List(这是我的术语)的封装实现中,List最为麻烦。按说,采用面向对象的方法来进行数据库操作,就应该完全不使用和数据库有紧偶合的数据感知控件,如典型的DataSet(ADOTable,ADOQuery等)-》DataSource-》Grid数据感知控件等。但是,现阶段要自己实现数据的完美显示(可以分组、排序、导出等),还要在显示的基础上实现快速查找、复杂查询等派生功能,工作量实在是太大了,而且凭自己的力量也不可能做得很完美。所以我在尝试使用面向对象的数据库编程时,对List的实现方法部分作了妥协,仍采用数据库控件的显示方式,但仅仅是用来显示(DataSet的AutoEdit属性设定为False)。另外,市面上的Grid控件种类实在是太多了,功能又炫又强,不用也实在是可惜。我为了实现通用的快速查找、复杂查询等List的各种扩展功能,才决定自己编写控件(从dxDBGrid继承),但因为我实在是写控件的新手,所以碰到N多的问题。。。。

③同样的问题我也在CSDN上发问,最后的结果跟zjan521兄一样,基类已经发布的属性在派生类中是无法隐藏的,否则就违反了OO的原则,所以,对于本帖的关于隐藏父类的已经Published了的属性的问题,我现在的得到的答案是不可能。zjan521兄给出了用属性编辑器来实现弥补的方法,但是我现在不会啊(说实在的,你的代码我还不能完全看懂,比如Store Flase,我只是在李维的VCL剖析一书中看到,当时就不解其意)。。。

④关于控件的编写我还有很多很基础的疑问,本来应该另外开贴来请教zjan521兄,但是考虑到我的分数已经不多了,另外抓住一位高手实在是难得,机会不容错过啊!再请教一个问题:

考虑以下代码(派生控件的Create):

constructor TdxDBGrid3En.Create(AOwner: TComponent);
begin
inherited;

FDataSource:=TDataSource.Create(nil); //注1
FADOSP:=TADOStoredProc.Create(Self); //注2

//FADOSP:=TADOStoredProc.Create(AOwner); //注3

......
end;

DataSource和ADOStordProc都是非可视化控件,现在我要在我的自定义控件中使用它们。现在我们来关注Create函数的参数AOwner:Tcomponent :
1.如果AOwner设定为nil,则在设计时期我拖放我自定义的控件到Form上时,以nil为参数创建的子控件就不会出现在Form上。但是也正因为Owner是nil,所以,在保存Form的时候,该控件的属性不会被保存(李维的书上说,在保存Form的时候,Delphi IDE会根据Owner遍历每个子控件并保存每个子控件在设计时期定义的属性到dfm文件中去)。具体在我的自定义控件中,对于DataSource的Owner设为nil问题不大(DataSource我要设定的属性也就一个,在上面的构建代码中设定了,只是在这里没有写出来),但是对ADOSteodProc,问题就大了,如果Owner是nil,那么ADOStoredProc控件就不会出现在Form上,而且,在Object Inspector中该属性是空的!我的本意是我可以在Object Inspector中展开ADOStoredProc来指定其Connection和ProcName,并且将其Active,这样我才能在设计时期将要显示的字段添加到Grid中(然后设定Grid显示字段的各种不同属性);
2.如果AOwner设定为Self,在设计时期该子控件也不会出现在Form上,但是比设定为nil好一些,ADOStoredProc可以在Object Inspector中展开并设置各种属性,也能够Active,并且Grid中能正常显示数据。但是基于同样的原因,设计时期设定的ADOStoedProc的各种属性仍不能保存到dmf中去,下次重新在IDE中加载该From时,仍必须重新设定各种属性:(
3.如果AOwner设定为AOwner,那么就是说,让新建立的子控件成为其Form的子控件(新建立的子控件的Parent是Form)。这时,可以在Object Inspector中展开ADOStoredProc并设置各种属性,也能在保存Form时将各种属性保存到dfm文件中去,但是这样做会有两方面的问题:
A.第二次在IDE中加载该Form时,TdxDBGrid3En.Create再次得到执行的机会,因为第一次设计时已经有ADOStoredProc控件并设定了相关的属性,且能在第二次成功加载,TdxDBGrid3En.Create构建方法再次执行的时候会告诉我同名的控件已经存在而创建失败(且把原来控件设定的正确属性设又定错了),解决这个问题的关键是TdxDBGrid3En.Create函数中,如何判断第一次创建的控件已经存在了呢?(又是使用csDesigning in ComponentState吗?但是ADOStoredProc在第一次设计时期可能被改名字了)
B.我编写这个控件的原意是将ADOStordProc、DataSource和dxDBGrid封装在一起形成一个控件并在该控件的基础上实现我额外的功能,现在如果AOwer设定为AOwner,那么在设计时期,一旦将我自定义的控件放到Form上,ADOStoredProc控件也会同时出现在Form上,这不符合“我只放了一个控件”的习惯,更严重的问题是,因为不该出现在Form上的ADOStoredProc出现在Form上,一不小心就可能把ADOStoredProc控件给删了,这让人很不爽。。。

综上所述,我的问题是,如何编写TdxDBGrid3En.Create构建函数,让我既可以在设计时期在Object Inspector中设定ADOStoredProc的各种属性,并且这些设定的属性能够保存到dfm中,而且在第二次加载的时候不会出现错误;又能够不让ADOStoredProc在设计时期不出现在Form上?

真诚希望能和zjan521兄多多交流!(只是不知道zjan521兄是否嫌我水平太次!我的mail:fbyr@hzcnc.com)。一个人摸索着前进,速度实在是太慢了!而且,我几乎每天都会被自己想破脑袋都想不出来的问题而郁闷不已!

再次感谢各位和zjan521兄!
 
不用这么客气.大家互相交流嘛.我其实只是对VCL/PASCAL略知皮毛.
1.其实只要你了解了vcl基本的框架,其他的仅仅就是经验和技巧了.ComponentState/ComponentStyle/ControlState/ControlStyle在帮助当中有解释。意思和你想得一样
2.OR方面,我很感兴趣.有机会讨论下
3.如果你想要在设计时可以设置
published
property DataSource: TDataSource read FDataSource write SetDataSoure;
constructor TdxDBGrid3En.Create(AOwner: TComponent);
begin
inherited;
FDataSource:=TDataSource.Create(Self); //注1
FDataSource.SetSubComponent(True);
......
end;

procedure SetDataSource(const Value: TDataSource);
begin
FDataSource.Assign(Value);
end;
 
谢谢zjan521兄!这几天我都在为自定义空间的事情大伤脑筋。不过这两天下来有一些进展,我们继续讨论。

第一、我找到一种让派生控件隐藏父类已经Published了的属性的一种方法,但是不是很正规,或许违反了OO的原则吧。请看代码:
TdxDBGrid3En = class(TdxDBGrid)
private
...
FHiddenProperty:String;
...
published
//不定义成TDataSource类型而定义成别的只读类型!比如字符串
property DataSource:String read FHiddenProperty;

end;

procedure Register;

implementation

{ TdxDBGrid3En }

constructor TdxDBGrid3En.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
//随便赋值一个字符串
FHiddenProperty:='Hidden Property';
......
end;

这样的控件安装后,将其拖放到Form上后,在Object Inspector中就找不到Datasource属性啦:)

第二、我放弃将子控件的Owner设定为AOwner(Form)或者Self了,还是使用nil作为参数,这样,子控件将会是无属主的控件了,所以IDE在保存Form的时候不会将子控件的属性保存到DFM中去。前面已经说了,Datasource的属性我只用到了一个,只要在控件的Create方法中正确设定,保存不保存到DFM中都没有关系;后来想想其实TADOStoredProc也一样,我只关心TADOStoredProc的三个属性:ADOConnection、ProcedureName和Active。我可以在派生控件中定义这三个属性,然后在这三个属性的Get(Read)和Set(write)方法中正确设置TADOStoredProc的相应三个属性即可。代码如下:

type
TdxDBGrid3En = class(TdxDBGrid)
private
{ Private declarations }
FAdosp:TADOStoredProc;
FDataSource:TDataSource;

FADOConnection:TADOConnection;
FActive:Boolean;
FProcName:String;

FHiddenProperty:String;

function GetActiveStatus: Boolean;
function GetADOConnection: TADOConnection;
procedure SetActiveStatus(const Value: Boolean);
procedure SetADOConnection(const Value: TADOConnection);
function GetProcedureName: String;
procedure SetProcedureName(const Value: String);
protected
{ Protected declarations }
procedure Loaded;override; //注①
function GetDataSource: TDataSource;Override;
public
{ Public declarations }
constructor Create(AOwner: TComponent);override;
destructor Destroy;override;

published
{ Published declarations }
property ADOConnection:TADOConnection read GetADOConnection write SetADOConnection;
property Active:Boolean read GetActiveStatus write SetActiveStatus;// stored False; //注②
property ADOSP_ProcName:String read GetProcedureName write SetProcedureName;
property DataSource:String read FHiddenProperty;// write SetDataSource;
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents('iChinese', [TdxDBGrid3En]);
end;

{ TdxDBGrid3En }

constructor TdxDBGrid3En.Create(AOwner: TComponent);
begin
inherited Create(AOwner);

FHiddenProperty:='Hidden Property';

FAdosp:=TADOStoredProc.Create(nil);
FAdosp.Name:=Self.Name+'ADOSP';

FDataSource:=TDataSource.Create(nil);
FDataSource.Name:=Self.Name+'DataSource';

inherited DataSource:=FDataSource;
FDataSource.DataSet:=FAdosp;

end;

destructor TdxDBGrid3En.Destroy;
begin
FAdosp.Close;
FAdosp.Free;

FDataSource.Free;

inherited;
end;

procedure TdxDBGrid3En.Loaded;
begin
inherited Loaded;

FADOConnection:=Self.ADOConnection;
FADOSP.Connection:=FADOConnection;
FADOSP.ProcedureName:=FProcName;
FADOSP.Active:=FActive;
end;

procedure TdxDBGrid3En.SetActiveStatus(const Value: Boolean);
begin
FADOSP.Active:=Value;
FActive:=Value;
end;

procedure TdxDBGrid3En.SetADOConnection(const Value: TADOConnection);
begin
FADOSP.Connection:=Value;
FADOConnection:=Value;
end;

procedure TdxDBGrid3En.SetProcedureName(const Value: String);
begin
FADOSP.ProcedureName:=Value;
FProcName:=Value;
end;

end.

说明:
现在TADOStoredProc的三个属性都能在设计时期被IDE保存到Form中去。为了在IDE再次加载该Form时能重新正确TADOStoredProc在上次保存到IDE时的状态。我查阅了Delphi的Help,其中关于TComponent的Loaded方法的描述如下:

When the streaming system loads a form or data module from its form file, it first constructs the form component by calling its constructor, then reads its property values from the form file. After reading all the property values for all the components, the streaming system calls the Loaded methods of each component in the order the components were created. This gives the components a chance to initialize any data that depends on the values of other components or other parts of itself.

当从流系统加载Form或者从Form文件中加载数据模块时,首先通过调用Form上各控件的Constructs方法来创建各控件,然后从Form文件中读取他们的属性值。当读完所有控件的所有属性值后,流系统按照控件创建的顺行来调用每个控件的Loaded方法。这给每个控件在初始化时需要引用到其他控件的属性值或自身其他部分值一个机会来初始化自身。

按照这段说明,我重载了Loaded方法(①)。在Form第二次被加载时,该方法中应该被执行。在该方法中,我将控件的三个属性(已从Form中读出)ADOConnection、ProcedureName、Active重新设定TADOStroedProc。但是Loaded方法好像没有按照我的希望工作。当第二次在IDE这加载该Form(第一次设计Form时将Active属性设定为True)时,IDE报错说Missing ADOConnection属性,然后Active属性自动恢复成False!但是当所有错误报告完毕后,我在Object Inspector中只要将Active属性重新设定为True,Grid中就立即显示数据了,而这可以说明,实际上ADOConnection和ProcedureName属性在加载Form的过程中都已经被正确设置。但为什么ADOConnection为什么会Missing,我就百思不得其解了!

无奈只能将Active属性设定为不保存(②),但是我实在是搞不清楚为什么会出现上述情况,还请zjan521兄解惑!

谢谢!

 
不好意思,最近这几天比较忙,系统又出了问题,TdxDBGrid现在手头也没有了.只能暂时口头表述,不足之处,请包涵.
1.我的建议,还是仅仅把那个DataSource最多修改为只读属性就可以了(result:= inherited DataSource),基本上还是不要改变原来的逻辑含义为好
2.这两个字段是不需要的 FActive:Boolean; FProcName:String;其属性值可以通过TADOStoredProc的相关属性保存即可.这样反而更加简明扼要,思路也更清晰.具体逻辑可以参考TDBEdit.DataSource(TDataEdit.DataLink)
property ProcName: string read GetProcName write SetProcName;
function GetProcName:string;
begin
result:= FAdoProc.ProcName;
end;
procedure SetProcName(value: string);
begin
FAdoProc.ProcName:= Value;
end;
3.在声明属性的时候记得把Actived属性放到最后(实例可以参见TControl.ParentFont).结合第二点,应改没有什么别的问题了
 
后退
顶部