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

时间:2022-08-20 20:36:31

    先说一些题外话。

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

    ②我使用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多的问题。。。。


    考虑以下代码(派生控件的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上?

7 个解决方案

#1


參看
<<delphi 精要>>

#2


A.第二次在IDE中加载该Form时,TdxDBGrid3En.Create再次得到执行的机会,因为第一次设计时已经有ADOStoredProc控件并设定了相关的属性,且能在第二次成功加载,TdxDBGrid3En.Create构建方法再次执行的时候会告诉我同名的控件已经存在而创建失败(且把原来控件设定的正确属性设又定错了),解决这个问题的关键是TdxDBGrid3En.Create函数中,如何判断第一次创建的控件已经存在了呢?(又是使用csDesigning in ComponentState吗?但是ADOStoredProc在第一次设计时期可能被改名字了)

-----------------------------
不明白,怎么会创建2次呢?既然你是复合控件,那么应当有一个主控件,在主控件的Create里Create其他控件,没理由执行2次的啊?

下次重新在IDE中加载该From时,仍必须重新设定各种属性:(!
不应该的,你看那么多控件Create的时候都写了缺省值,但如果你修改了的话,第2次用IDE打开就不会是缺省值了,你 published的属性,Delphi会自动帮你保存到DFM文件里的。

#3


其实你没有什么必要苦恼呀。如果你想实现完全的封装(别人看不见那个DataSource什么的)只需要把DataSource的Owner设成自己或者nil,然后做一个套子把DataSource中的属性装起来然后Publish就行了。

一个很简单的例子。
published
  property DataSet : TDateSet read GetDataSet write SetDataSet;

......

function TMyGrid.GetDataSet : TDataSet;
begin
  Result := FDataSource.DataSet
end;

procedure TMyGrid.SetDataSet(Value : TDataSet);
begin
  Result := Value;
end;

这样一来,别人照样能改变你所授权的DataSource的DataSet属性,改动也能保存下来。

最后说一句:除非用你的控件的人不熟悉Delphi,否则没有必要一定把DataSource封装到Grid中。

#4


保存控件属性?作个广告,www.yixel.com
有我自己的LexLib库,里面有序列化的库,可以将TPersistant派生出来的对象的published的属性保存到文件中,自带了一个保存为XML和INF的Reader/Writer,当然你也可以自己来写Reader/Writer来实现更多文件类型

#5


对于楼上的楼上的问题A,答案是:会的。因为如果使用Form作为Owner的话,在设计时,DataSource已经当作是一个控件存在了DFM里面。然后在运行时,在程序装载完DFM后,你又重新创建了一次DataSource的实例,所以会出问题。解决的方法正如楼主自己写的,在创建的时候判断ComponentState中是否有csDesigning。

#6


哦,长见识了。

#7


aiirii(ari-爱的眼睛):你说的是罗小平编写的《Delphi精要》吗?我在书店里看到过该书的第二版,第十好像没有关于控件编写部分的内容;

miky(miky) :无论是首次将控件放到Form上还是第二次在IDE中加载该Form,所有Form上的控件的Create方法都会被执行的。试想一下我前面说的,将Create的AOwner参数设定为AOwner,当一将我自定义的控件放到Form上时,如果控件的Create方法没有被执行,那么我的控件内部使用的子控件ADOStoredProc怎么会自动出现在Form上呢?

cybercake(数字蛋糕):
    ①DataSource的的Owner设定为nil在我的实例中没有什么影响,因为我的我自定义控件的Create方法中创建了DataSource并设定了正确的属性(连接),所以即使不能保存DataSource的属性也不要紧。另外,我前面一贴中 lxpbuaa(桂枝香在故国晚秋) 兄已经告诉我如何将DataSource属性设定为只读的方法,我试验了一下,很管用,在这里先谢谢 lxpbuaa(桂枝香在故国晚秋)(兄了!
    ②我昨天试验了一下csDesigning,发现不行,可能是我不得要领,能否给出提示?

Eastunfail(龙子龙孙)==(恶鱼杀手) :谢谢!你的东东我会去看的,不过我还是想通过Delphi VCL体系自身提供的功能来解决存储和重新加载的问题,能给我一些提示吗?

感谢各位!

#1


參看
<<delphi 精要>>

#2


A.第二次在IDE中加载该Form时,TdxDBGrid3En.Create再次得到执行的机会,因为第一次设计时已经有ADOStoredProc控件并设定了相关的属性,且能在第二次成功加载,TdxDBGrid3En.Create构建方法再次执行的时候会告诉我同名的控件已经存在而创建失败(且把原来控件设定的正确属性设又定错了),解决这个问题的关键是TdxDBGrid3En.Create函数中,如何判断第一次创建的控件已经存在了呢?(又是使用csDesigning in ComponentState吗?但是ADOStoredProc在第一次设计时期可能被改名字了)

-----------------------------
不明白,怎么会创建2次呢?既然你是复合控件,那么应当有一个主控件,在主控件的Create里Create其他控件,没理由执行2次的啊?

下次重新在IDE中加载该From时,仍必须重新设定各种属性:(!
不应该的,你看那么多控件Create的时候都写了缺省值,但如果你修改了的话,第2次用IDE打开就不会是缺省值了,你 published的属性,Delphi会自动帮你保存到DFM文件里的。

#3


其实你没有什么必要苦恼呀。如果你想实现完全的封装(别人看不见那个DataSource什么的)只需要把DataSource的Owner设成自己或者nil,然后做一个套子把DataSource中的属性装起来然后Publish就行了。

一个很简单的例子。
published
  property DataSet : TDateSet read GetDataSet write SetDataSet;

......

function TMyGrid.GetDataSet : TDataSet;
begin
  Result := FDataSource.DataSet
end;

procedure TMyGrid.SetDataSet(Value : TDataSet);
begin
  Result := Value;
end;

这样一来,别人照样能改变你所授权的DataSource的DataSet属性,改动也能保存下来。

最后说一句:除非用你的控件的人不熟悉Delphi,否则没有必要一定把DataSource封装到Grid中。

#4


保存控件属性?作个广告,www.yixel.com
有我自己的LexLib库,里面有序列化的库,可以将TPersistant派生出来的对象的published的属性保存到文件中,自带了一个保存为XML和INF的Reader/Writer,当然你也可以自己来写Reader/Writer来实现更多文件类型

#5


对于楼上的楼上的问题A,答案是:会的。因为如果使用Form作为Owner的话,在设计时,DataSource已经当作是一个控件存在了DFM里面。然后在运行时,在程序装载完DFM后,你又重新创建了一次DataSource的实例,所以会出问题。解决的方法正如楼主自己写的,在创建的时候判断ComponentState中是否有csDesigning。

#6


哦,长见识了。

#7


aiirii(ari-爱的眼睛):你说的是罗小平编写的《Delphi精要》吗?我在书店里看到过该书的第二版,第十好像没有关于控件编写部分的内容;

miky(miky) :无论是首次将控件放到Form上还是第二次在IDE中加载该Form,所有Form上的控件的Create方法都会被执行的。试想一下我前面说的,将Create的AOwner参数设定为AOwner,当一将我自定义的控件放到Form上时,如果控件的Create方法没有被执行,那么我的控件内部使用的子控件ADOStoredProc怎么会自动出现在Form上呢?

cybercake(数字蛋糕):
    ①DataSource的的Owner设定为nil在我的实例中没有什么影响,因为我的我自定义控件的Create方法中创建了DataSource并设定了正确的属性(连接),所以即使不能保存DataSource的属性也不要紧。另外,我前面一贴中 lxpbuaa(桂枝香在故国晚秋) 兄已经告诉我如何将DataSource属性设定为只读的方法,我试验了一下,很管用,在这里先谢谢 lxpbuaa(桂枝香在故国晚秋)(兄了!
    ②我昨天试验了一下csDesigning,发现不行,可能是我不得要领,能否给出提示?

Eastunfail(龙子龙孙)==(恶鱼杀手) :谢谢!你的东东我会去看的,不过我还是想通过Delphi VCL体系自身提供的功能来解决存储和重新加载的问题,能给我一些提示吗?

感谢各位!