Board logo

Subject: 建议:是否可以实现将Form中的控件和事件改为private的功能? [Print This Page]

Author: dark_moon    Time: 2005-1-26 11:03     Subject: 建议:是否可以实现将Form中的控件和事件改为private的功能?

设计器中添加的控件和事件缺省都是published,但在绝大多数情况下它们都应是private,这个功能我觉得对于团队开发好处多多。
Author: zjy    Time: 2005-1-26 11:59

设计器中添加的控件和事件缺省都是 published,这个是必须的。因为 Delphi 程序在实例化 Form 时,需要根据组件指定的事件名称去查找相应的事件方法进行事件赋值,而只有 published 的事件方法才会存在 RTTI 信息,如果把这些事件声明成 private,运行时就会报无效属性错了。
Author: dark_moon    Time: 2005-1-26 13:00     Subject: 楼上错了,试试下面的代码

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
  private
    btn1: TButton;
  public
    constructor Create(AOwner : TComponent);override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  btn1 := FindComponent('btn1') as TButton;
end;

initialization
  RegisterClass(TButton);
end.
Author: dark_moon    Time: 2005-1-26 13:04     Subject: 忘了事件了

将事件改为私有后,在设计其中将事件改为空,在FindComponent之后赋值如
private
  procedure btn1Click(Sender : TObject);

//
btn1.OnClick := btn1Click
Author: zjy    Time: 2005-1-26 14:23     Subject: 嗯,我前面没有正确理解你的意思

上面的代码确实可以实现对窗体上控件和事件的隐藏,不过个人认为这里涉及到的其实是设计问题。

一般说来,将窗体实例公开并不是一个好办法。虽然前面的方法可以在窗体定义中隐藏控件和事件,但是最坏情况下,其它人还是可以通过 Form1.FindComponent('btn1') as TButton 和 (Form1.FindComponent('btn1') as TButton).Click 来访问。除了控件,其它人还可以直接调用 Form1.Hide、Form1.Free 这些需要保护的方法。

要使用严格的访问约束,一个常用的方法是增加一个功能类来封装对窗体的访问,并把窗体变量 Form1: TForm1; 从单元中删除,放到封装类的私有域中。如果连类声明都不想给其它人知道,还可以定义一份公共的接口声明单元,在各个模块之间使用 interface 来交互。

如果要在 CnWizards 中提供这样一个工具,开发的优先级可能会比较低了,一来这个应该算与设计相关的特定需求,二来实现时需要以语法分析器为基础(对带条件编译的单元更为复杂),开发量不小。

如果您有什么其它想法,欢迎讨论!
Author: dark_moon    Time: 2005-1-26 16:17

就我个人的看法,不管是否封装对窗体访问或是否使用interface等手段,设计器生成的这些定义严重污染了类的接口。仅简单地将控件和事件改为private并不需要考虑语法分析等问题,目前我使用UltraEdit的macro来做这个工作,但很机械。
Author: dark_moon    Time: 2005-1-26 16:19

好像Delphi程序员很少在意类似的设计问题
Author: zjy    Time: 2005-1-26 17:30     Subject: 关于这个问题,我跟 Aimingoo 讨论了一下,聊天记录如下

[16:37:23] JingYu  : 有空吗?
[16:37:37] Aimingoo:
[16:37:43] JingYu  : 想讨论个问题
[16:38:03] Aimingoo: 可以呀。
[16:38:13] JingYu  : vcl 里头窗体上的控件和关联的事件都是 published 的,你觉得这样处理的理由是什么?
[16:38:20] JingYu  : 除了RTTI的需要。
[16:38:55] Aimingoo: 只是因为RTTI的需要。
[16:38:58] JingYu  : unit Unit1;
           
           interface
           
           uses
             Windows, Messages, SysUtils, Variants, Classes, Graphics,
           Controls, Forms,
             Dialogs, StdCtrls;
           
           type
             TForm1 = class(TForm)
             private
               btn1: TButton;
             public
               constructor Create(AOwner : TComponent);override;
             end;
           
           var
             Form1: TForm1;
           
           implementation
           
           {$R *.dfm}
           
           { TForm1 }
           
           constructor TForm1.Create(AOwner: TComponent);
           begin
             inherited Create(AOwner);
             btn1 := FindComponent('btn1') as TButton;
           end;
           
           initialization
             RegisterClass(TButton);
           end.
[16:39:06] Aimingoo: 我记得在什么资料里看到过。
[16:39:37] Aimingoo: 而且,如果需要在属性编辑器里处理,也是需要先声明成published的。
[16:39:51] JingYu  :
           有人给出这样的代码,认为“就我个人的看法,不管是否封装对窗体访问或是否使用interface等手段,设计器生成的这些定
           义严重污染了类的接口。”
[16:40:10] JingYu  : 对,RTTI的需要我们都知道。
[16:40:19] JingYu  : 我只是想从面向对象和设计的角度来看
[16:43:38] Aimingoo: 他这段代码,与他的观点,有什么关系吗?我看不出来~~
[16:44:04] JingYu  : 这段代码是把published的控件转化成private的实现
[16:44:40] JingYu  : 这个btn1估计还在窗体上,就是从申明中去掉了。
[16:46:29] JingYu  : 他认为窗体上的控件不应该被其它类访问到。
[16:46:58] JingYu  : http://bbs.cnpack.org/viewthread.php?tid=412
[16:48:28] Aimingoo: 是的。他这样声明,的确可以被form1访问到,但是,界面设计器可能不能处理了。
[16:48:43] JingYu  : 可以处理,我刚才试了下。
[16:48:56] JingYu  : 把btn1移到private下,设计器还可以访问
[16:48:57] Aimingoo: OH.
[16:49:03] JingYu  : 编译也能正常执行
[16:49:03] Aimingoo: 哈哈。
[16:49:35] Aimingoo: 因为我曾经把这个部分的代码做成.inc,结果是不能处理的,后来,我就只好做成了frame。哈哈。
[16:50:02] JingYu  : 不过必须要  RegisterClass(TButton);
           才能正确执行
[16:50:58] Aimingoo:
           在窗体中的界面元素,例如button,其实应该是可以被其它单元或外部的代码访问的。因为无论如何,代码可以通过compo
           nents来访问到指定的界面元素。
[16:51:13] JingYu  : 对,这也是我的想法。
[16:51:37] Aimingoo: 这种隐藏,是效率上的失败。——除了,interface上看起来让人心情好一些。
[16:52:20] JingYu  : 估计vcl在从流中创建窗体时,也是用RTTI的方法来设置published的控件名的。
[16:52:51] JingYu  : 抛开效率,这种方法还有其它问题吗?
[16:52:53] Aimingoo:
           然而,你想,你放在UI上,当然是用于被处理、被控制的。要不然,你就直接做create得了。——UI上的控件被放dfm里
           ,在初始form时可以被创建。而放在privited节里不能。
[16:53:09] Aimingoo: 这也是必须要RegisterClass()才能用的原因呢。
[16:53:31] JingYu  : 好象不太对吧。
[16:53:33] Aimingoo: 所以,从这里来看。也是Delphi窗体自动构建机制的需要。
[16:54:01] JingYu  :
           必须要RegisterClass的原因是因为只有注册了,流化的窗体创建时才能用FindClass根据名称找到类的引用吧
[16:54:08] Aimingoo: ——哈哈~~是这样的罢。
[16:54:32] JingYu  : 如果直接用 TForm1.Create,也应该是可以正常工作的。
[16:55:01] JingYu  : vcl在TCustomForm的构建器里头会自动从RCDATA资源里头读流化的窗体数据。
[16:55:09] Aimingoo: En...这个你需要测试。
[16:55:21] Aimingoo: 我的印象中是这样。
[16:55:34] JingYu  : 这也限制了用户直接从TCustomForm派生子类
[16:57:06] JingYu  : 从设计的角度考虑,前面的代码是否有问题呢?
[16:58:33] Aimingoo:
           不不。从设计的角度上来看,UI层是尽可能公开,而不是隐蔽。公开的组件信息有利于代码的编写,而且。EN...
[16:59:21] Aimingoo: 我想想。域占有的内存空间是一致的,无论它是public还是privated....
[16:59:56] JingYu  : 嗯,如果在published里头,窗体从流中加载时,会根据RTTI给属性赋值。
[17:00:05] JingYu  : private的话,得自己赋值。
[17:00:08] Aimingoo: 也就是说,他做了一件不必要做的事。
[17:00:34] JingYu  : 他的出发点是信息隐藏。
[17:01:55] Aimingoo: En.
[17:02:29] Aimingoo: 没必要呀。因为在这一个层面上的设计,基本的思路就是公开。而不是背其道而行之。
[17:06:34] JingYu  : 嗯。
[17:07:14] JingYu  : 窗体只是界面表现层的东西,每个窗体都应该是相对独立的。
[17:08:23] Aimingoo: En...
[17:09:24] Aimingoo:
           还有,界面层的hwnd都是可以通过api来遍历的,这是win32的问题。但这表明,对于这一层的隐藏,OS提供了机制来突
           码。但显然这是不方便的。
[17:10:49] Aimingoo:
           也没有必要做得如此复杂。在这一层面上,Delphi可能更偏向于开放的设计。而不是隐藏。——如果你有印象,你应该知道要在
           shell的dialog中加一个button,是非常困难的。这种API一层的隐藏是以易用性为代码的。
[17:11:17] JingYu  : 对。
[17:11:59] Aimingoo:
           如果从设计的角度上来看,这个dark_moon的思路,可能也是以易用性为代价,来换取接口的“纯净”,或者“漂亮”。这当
           然是不值得的。
[17:13:14] JingYu  : 从另一面考虑,即使published上什么也不加,窗体从 TForm
           继承来的属性和方法在一个接口封装的系统中也是不希望全部开放给其它用户的。
[17:13:34] Aimingoo:
[17:14:33] JingYu  : 既然VCL本身的设计是以这种机制来处理窗体的,硬要去封装,有些得不偿失了。
[17:14:41] Aimingoo: 是呀。
[17:15:41] Aimingoo: 是否要封装、封闭,取决于环境和设计目标,而不是反过来,以封装与封闭为设计目标。
[17:15:43] JingYu  : 我记得有些delphi编码规范上面是要把自动声明的窗体的变量去掉,改成手工创建的。
[17:15:47] Aimingoo: 对不对~
[17:16:01] JingYu  : 对。
[17:16:29] Aimingoo: JingYu   说:
           我记得有些delphi编码规范上面是要把自动声明的窗体的变量去掉,改成手工创建的。
           ---------
           这个倒是正常的。我也比较习惯这样做。因为~~这会加快初始化的速度。
[17:16:57] JingYu  : 呵呵,我指不不是AutoCreate,是指把 var Form1: TForm1 这个变量删除。
[17:17:14] JingYu  : 以防止其它用户通过这种全局变量来访问窗体。
[17:17:43] Aimingoo: 是呀。
[17:17:43] JingYu  : 好象《D5开发人员指南里头》也有提到。
[17:17:58] Aimingoo: 去掉这个变量,就不会自创建乐。哈哈~~
[17:18:32] JingYu  : 如果是多人开发的话,模块的接口应该单独设计,而不应该依赖于窗体类的接口定义吧。
[17:18:33] Aimingoo: Options里的选项是对应于这个变量的。
[17:18:54] JingYu  : Options里的选项还针对.dpr文件里的初始化代码呀
[17:19:36] JingYu  : 如果只删除变量,不从设置或.dpr里删除,会编译报错的,呵呵。
[17:19:39] Aimingoo: OH...是的。我当然都是两个都去掉。如果你把var
           form1的声明去掉了,哈,不改.dpr里的代码,就编译不过~~哈哈。
[17:19:43] Aimingoo: 哈哈哈~~
[17:19:46] JingYu  :
[17:19:54] Aimingoo: 这两句怎么打出来都一样啊。
[17:19:57] Aimingoo: 哈哈哈。
[17:20:00] JingYu  : 我的设置里头默认就是不创建,呵呵。
[17:21:38] JingYu  : 所以我主张,如果要考虑类和模块的接口封装,应该用类或接口来封装窗体,而不是去隐藏窗体上的控件。
[17:22:11] Aimingoo: 是的。
[17:22:41] Aimingoo: 要进一步的封装窗体。我建议用Frame. 这是我常用的简化代码的方法。
[17:23:18] JingYu  : 不过用Frame的话,封装的只是窗体中的一部分界面和功能,而不是接口吧。
[17:23:30] JingYu  : Form1.Frame1.Button1还是可以访问的。
[17:25:18] JingYu  : 聊天记录我整理下,帖上去,可以吧?
[17:26:24] Aimingoo:
Author: zjy    Time: 2005-1-26 17:31

欢迎大家继续讨论:)
Author: dark_moon    Time: 2005-1-27 09:48

楼上两位还没有明白我的意思,我不关心framework、不关心Win32、不关心rtti、更不关心某个api可以找到某个hwnd,我关心的是我的系统设计是否合理、我要尽可能的提高我的代码的可复用性和可维护性。
我想cnpack是否能考虑改变一下思路:不仅是提供工具,而是通过工具提供一些用object pascal进行ood的经验和模式
Author: dark_moon    Time: 2005-1-27 09:59

对于“[17:21:38] JingYu  : 所以我主张,如果要考虑类和模块的接口封装,应该用类或接口来封装窗体,而不是去隐藏窗体上的控件。”,请考虑下面的情况:
1.界面设计和界面处理逻辑分别进行
2.界面类可复用
Author: zjy    Time: 2005-1-27 10:09



QUOTE:
dark_moon  在 2005-1-27 09:48 发表:

我想cnpack是否能考虑改变一下思路:不仅是提供工具,而是通过工具提供一些用object pascal进行ood的经验和模式

这句话说得好!
Author: zjy    Time: 2005-1-27 10:19



QUOTE:
dark_moon  在 2005-1-27 09:59 发表:

对于“[17:21:38] JingYu  : 所以我主张,如果要考虑类和模块的接口封装,应该用类或接口来封装窗体,而不是去隐藏窗体上的控件。”,请考虑下面的情况:
1.界面设计和界面处理逻辑分别进行
2.界面类可复用

将窗体上的控件隐藏或开放,与这两点之间有什么必然的联系吗?

另外,对那个在 UltraEdit 中进行这种转换的宏很感兴趣,能不能帖上来看看啊:)
Author: zjy    Time: 2005-1-27 11:21

关于这类设计思想和实现方法的问题,事实上现在的程序界,设计方法、管理思想和框架的争论已经很多了,Java界尤甚。不同的设计方法应用在不同的场合不同的环境中,很难说谁对谁错,最终还是要以解决问题为目标。

VCL使用published来声明窗体上的控件和事件,肯定有其合理性。抛开RTTI的实现机制,设计期窗体可以认为是TForm的子类,窗体上的元素则是用户窗体相对于TForm类扩展的新的属性和方法。虽然对程序员来说,这些新的成员可能并不希望公开给其它对象,但也并不是都需要私有保护的。

从面向对象的角度来举个例子:继承自动物(TForm)的鸟(TForm1)有了一双翅膀(Button1),通常我们会希望可以直接访问 鸟.翅膀(Form1.Button1)和 鸟.翅膀.挥动(Form1.Button1.Click)。如果你一定要隐藏起 翅膀,改成用 鸟.挥动翅膀(Form1.MyButtonClick)也未尝不可,这只是思维方式的区别。

作为一种信息隐藏的设计方法,将Form上的控件移到private里头,在某些场合下可能有用,但方法本身与VCL的窗体继承思想是相悖的。CnWizards目前的定位是IDE辅助工具,以后也会提供更多的设计和重构辅助工具,但都会以成熟主流的设计思想和方法为基础,而不是开发专用工具。您如果习惯了这种设计方法,也可以自己编写一个工具来实现,后继版本的CnWizards将会做成插件形式的,到时候可以把这个工具作为一个独立的插件集成进来。如果有什么技术上的问题,欢迎讨论。

感谢您对CnPack的支持和建议!
Author: shenloqi    Time: 2005-1-27 13:50

我觉得没有必要,如果按照这种做法,当我需要在设计器重新增加Button的Event代码的时候必须在Create里补上相关的赋值,还要把新的方法移到private中,还要清空设计器的关联代码,以后要看控件的什么Event被改写了都很不方便。
要这么做我觉得还是用.net或java做比较好,这不是delphi程序员对什么模式不重视,而是得不偿失,而且本来UI层次的东西可以被访问也没有什么的。(不过.net的做法的后果就是设计器会经常丢失事件)
Author: zjy    Time: 2005-1-27 14:08     Subject: 还想到一点

把控件事件放到private,会导致继承出来的子窗体无法重载该事件,除非变成虚方法,这样就有些没必要了。

不过我这里倒是有一个源码,是用来把DFM窗体转成动态生成窗体的代码,还没有研究过,呵呵。
Author: dark_moon    Time: 2005-1-27 16:23



QUOTE:
yygw  在 2005-1-27 10:19 发表:

将窗体上的控件隐藏或开放,与这两点之间有什么必然的联系吗?

另外,对那个在 UltraEdit 中进行这种转换的宏很感兴趣,能不能帖上来看看啊:)

我也从不认为有“必然的联系”,但我觉得这样做更好。
宏无非就是把一些机械的操作记录下来而已,献丑:
设计器生成的代码不要改动,仅在private下增加CreateControls方法,添加空的实现,并增加initialization,然后用第1个宏将事件集中起来,再用第2个宏将控件改为private同时在CreateControls中添加findcomponent在initialization中添加registerclass

事件的赋值暂时只能手工了。

集中事件的宏:
InsertMode
ColumnModeOff
HexOff
UnixReOff
Top
EndSelect
Find MatchCase MatchWord "private"
StartSelect
Find MatchCase MatchWord Up "procedure"
EndSelect
Key HOME
Key HOME
StartSelect
Key DOWN ARROW
Cut
Find MatchCase MatchWord Up "class"
Key END
Key DOWN ARROW
Key HOME
Key HOME
Paste

---------------------------------------------------------
处理控件的宏
InsertMode
ColumnModeOff
HexOff
UnixReOff
Top
EndSelect
Find MatchCase MatchWord "private"
Key HOME
Key HOME
StartSelect
Key UP ARROW
Cut
Key DOWN ARROW
Key DOWN ARROW
Paste
Key UP ARROW
Key Ctrl+RIGHT ARROW
"F"
Key HOME
Key HOME
StartSelect
Key DOWN ARROW
Copy
EndSelect
Key DOWN ARROW
Find ".CreateControls;"
Key HOME
Key DOWN ARROW
Key DOWN ARROW
Paste
Key UP ARROW
Key Ctrl+RIGHT ARROW
Key Ctrl+RIGHT ARROW
Key RIGHT ARROW
"= FindComponent('"
Key Ctrl+LEFT ARROW
Key Ctrl+LEFT ARROW
Key Ctrl+LEFT ARROW
StartSelect
Key Ctrl+LEFT ARROW
Key RIGHT ARROW
Copy
EndSelect
Key Ctrl+RIGHT ARROW
Key Ctrl+RIGHT ARROW
Key Ctrl+RIGHT ARROW
Key RIGHT ARROW
Key RIGHT ARROW
Paste
"') as"
Key RIGHT ARROW
StartSelect
Key END
Key LEFT ARROW
Copy
Find "^pend."
EndSelect
Key HOME
"
"
Key UP ARROW
"  RegisterClass('"
Paste
"');"
Author: Passion    Time: 2005-1-27 21:04

dark_moon的意思像一个设置严密的防火墙,除非显式声明开放,否则全封闭。单就安全性而言,这样做是不错。不过从设计角度讲,如果不用VCL的开放与可视化机制,而自行重定义一套用于对外开放的接口,那么易用性与可理解性便会降低。
这样的思想在类设计上倒是可参考,但用在界面设计上就有些值得商榷了。
Author: leeon    Time: 2005-1-28 12:46

我不认为你这样认为更好有什么意义,有什么高深的面向对象的意义。

Delphi既然把这个暴露在外面自然有他自己的用处。你这样做自然有你自己道理。

但是,我个人认为,这种封装也不要太教条化了。

看看俄罗斯老毛子的控件写的,成员大部分都public出来,

但是人家的大型控件照样稳定的使用也没有什么重大的bug。

比如Business Skin,rx Libaray,Vclskin等等。看看代码你就知道,

代码设计的好坏,不是你把所有成员都隐藏才是好的。

关键还是功能上,和可维护性上。

人的精力是有限的,我想,我们的精力还是在实现一些好用的功能上。

还有,我们做的是通用工具,而不是类似给你用的专用工具。

如果我们提供一个工具,只有几个人用,那我们岂不是白忙了?

其实如果一个工具1/3的用户在用,我们就很欣慰了。

你的东西,完全可以用Vi或者Emac写一个简单的脚本来实现。

何必要写个专用的工具呢?要写,你自己也完全可以写一个批量修改的工具也不是很难。

[ Last edited by leeon on 2005-1-28 at 12:58 ]
Author: leeon    Time: 2005-1-28 12:55

而且,有些东西,本来就是公说公有理婆说婆有理的事情。

是否把控件published出来,个人认为大可不必这么深究。

有时间,还不如把自己的架构知识巩固一下。

如果是个注重实效的程序员,给自己写工具是很正常的事情。

何不自己动手打造工具?
Author: nanyu    Time: 2005-8-30 19:31     Subject: C#里的对应

在C#里, 一个控件 在设计期(设计期和运行期和Delphi相同名称完全相同),存在一个属性,我记不起名字了,“PublicScore”?? 可以指定这个控件放在Form上以后,是public?protected?或private。 我相信这是这是一种改进。Aimingoo 大可不必 先有结论然后再使劲论证它。
Author: jAmEs_    Time: 2005-9-1 22:46

dark_moon最后的意见是值得想想的,我也觉得应该朝这样方向发展。
对于提出的private问题我也挺赞成,理论上,一个类最好不要随便暴露太多的东西,应该一般情况下,多数数据是私有的。不过这个已经习惯,也不会造成很大问题,所以并没有考虑过要调整。




Welcome to CnPack Forum (http://bbs.cnpack.org/) Powered by Discuz! 5.0.0