关于自定义控件属性如何保存等问题的再讨论。。。(11分)

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

littlefat

Unregistered / Unconfirmed
GUEST, unregistred user!
原来结贴的问题和没结贴的问题是分开显示的,没有注意过

先说一些题外话。

①这是我第一次尝试编写有实用价值的控件。以前我没有任何编写控件的经验,翻阅了很多书,也在网上搜索良久,可惜没有什么结果,关于控件开发的资料实在是太少了,仅仅看到一些诸如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兄!

(原帖:http://www.delphibbs.com/delphibbs/dispq.asp?lid=2604525)
 
到 www.eVget.com 有你需要的控件产品和资料
 
谢谢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兄解惑!

谢谢!
 
后退
顶部