初学Vcl 的几点困扰(150分)

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

lanyun2

Unregistered / Unconfirmed
GUEST, unregistred user!
1、我想从Vcl源码中学点东西,在窗体上放了一个button。然后在源码中设置断点,在Tbutton.Create过程完成后,程序很奇怪地跳转到 Controls单元中的TControl. SetParentComponent过程中。问题:是谁调用了SetParentComponent过程呢?如果以后
还有类似的莫名其妙运行的程序段,我该怎样确定它的调用者呢?(30)
2、当我把一个button放到窗体中的时候,Delphi做了些什么(理论上应该是先创建一个有具体参数如大小,名称。形状等的一个实例,但为什么我放组件的时候他不调用Create过程,难道是delphi先在窗体上画个图片安慰安慰我,然后在实际运行的时候再调用Create?)?组件的运行态和设计态有什么区别?(80)
3、我真正学习Delphi不到半年,而其他编程语言也不过是听说过而已,谁能给我个比较详细的学习流程和给些指导性书籍//D4Unleashed 和D5编程人员指南我在看。(40)
(看了一段时间大富翁的离线数据包,对 里边的牛人如Dwwang,茶叶蛋,CAkk(哈,比较好记且易写的几个。)等等敬佩异常,希望有朝一日我也能这么牛。
初次进门,嘿小气点莫怪。废话不说了,洗耳恭听ing)
 
说实话,对于初学者,研究这些东西有些困难!建议你慢慢来,以后你会渐渐明白其中的道理的!
 
建议初学者先不要研究内部的东西
先开发普通程序
然后逐渐深入
用到什么学什么
这样会容易许多
 
这个帖子我反复进来4次,可是很难一下说清楚楼主的问题。
1.要解释这个问题首先得解释什么是属性,因为SetParentComponent是TControl.Parent属性的写方法(要解释什么是写方法就必须说一下类结构),实际上是TComponent.SetParentComponent(要解释为什么TButton.Create执行了TComponent.SetParentComponent方法必须解释继承和多态)调用的,要讲起来必须结合整个VCL架构和面向对象编程思想!估计要解释为什么可以写一本书了!所以实在不好用通俗简单又能让你非常容易理解的语言来解释这么一个复杂的机制。

2.对象的创建的确是调用了create,但是这是在IDE中的调用,调试程序并未调试IDE,故你看不到断点,同时对象能判断自己是处于设计阶段还是运行阶段(要解释为什么他能知道自己在哪个阶段也不是一两句话能说明白的),所以这时不会触发诸如OnCreate等事件的代码。

3.建议先学习基础的,语法,包括简单的类,然后学习对象,继承,多态等OO知识,最后学习VCL框架和设计模式等。
 
非常感谢各位的关注
To zqw0117:
:对于问题一,我也觉的言之过早,但我是紧紧想知道程序的执行流程罢了,
例如对于TButton类的Width属性,Tbutton的声明如下
property Width: Integer read FWidth write SetWidth;
而在TButton的创建过程中有如下代码
Width := 75;
由于涉及到对属性Width的写入,所以调用其写方法SetWidth。这样子很容易理解。
您能不能像TButton的例子一样给我讲讲TControl.SetParentComponent是在那里调用的?因为我发现TControl.SetParentComponent是在Tbutton的create过程完成以后立刻进入的,而我又没找到TControl.SetParentComponent或者是TComponent.SetParentComponent的调用。所以感到特别神奇,呵呵,对您的指导先记上30分,哈。
2 同样的,我发现我写的控件代码(抄书上的例子)都能够在单步跟踪的过程中进入,所以我很奇怪,在我往窗体上拖控件的时候,Delphi一定是收到了某种事件的触发消息,才做出了在窗体上绘出控件外形,单元中给出声明,Inspector中列出了属性等一系列动作,您能不能讲解一下,在创建一个组件的过程中,Delphi在背后作了哪些工作?
大概讲一下就可以了,详细了估计我也听不明白,呵呵。
3 我看D4 编程技术内幕上是在 基本语法、数据类型、对类的基本介绍、异常、多线程之后开始讲解 创建组件,D5指南中也基本上是如此,(好像多了对windows的一些讲解)那么我在看完这些内容后是应该巩固和强化这些知识还是该去学习组件呢?多谢多谢!
 
楼主学delphi不到半年? 能提这样的问题, 令人敬佩. 所以我想试着回答, 如有不对,请楼下高手们斧正.

1: button在运行时, create之后,调用setparentcomponent的原因是, 首先,button是Tcomponent的子类, 我们知道, button不是一个容器类,它的存在必须在一个容器中, 那么它的爹, parent就是它的容器,所以创建之后,如果不指定它的parent,那么就看不到它. 不信你可以动态的写这样的代码:
var
b: Tbutton;
begin
b:= tbutton.create(nil);
b.left := 10;
b. top := 10;
b. width := 20;
b.height := 10;
b.caption := 'ok';
b.visible:= true;
end;
必须指定它的parent 为一个容器对象才可以看见, panel. form groupbox等 都是容器.
另外这个不是莫名其妙的 调用, 它是有规则的, 具体的详细规则, 我一时半会说不明白, 你可以看看李维的<inside vcl>. 对vcl的讲解比较透彻. vcl也算博大精深, 这种控件的封装是它的精髓之一.




2, 在设计期你拖一个button, 那不是图片, 也不是安慰你, 是真实的控件. 它不执行代码的原因是它在设计期,它所执行的是, 设计期的包. 这个包,已经经过编译. 就像有些三方控件, 你看不到它的源码, 即使在运行期也无法跟踪,那是它编译成了包.
如果你想了解为何在没有创建的设计期, 却能在界面上任你摆布呢, 也看看李维的<inside vcl>. 或者rtti相关的东西会告诉你他们是怎么做到的.
再了解了解什么是持久化类. 为什么要持久化.

3,对于初学者, 抱紧<d5开发指南>就是上策, 等你看完一两遍了, 就可以看看, 面向对象设计相关的书了.

蔽人愚见. 贻笑大方. 请各位斧正.
 
dohai_lee大虾风范哦!
真正应了那句名言:有困难要帮,没有困难创造困难也要帮!
(开个玩笑,别生气哦)
对您的动态创建一个button的例子,我也研究过了,呵呵,对指定空间的Parent和Owner也
有了模模糊糊的了解。恩,我就坦白了吧,您就给我指出到底button.create后或者期间谁
控制了程序使其走到了TControl. SetParentComponent的过程里边,例如button的
创建过程中有Width := 75;所以调用了SetWidth过程。那么谁调用了SetParentComponent过程呢?
另:李大侠(应该是吧?错了也不许怪我)的回答使我受益匪浅,我肯定不会让您白敲这么
多字的,呵呵
To Zql0117 :你曾提“到要解释为什么TButton.Create执行了TComponent.SetParentComponent方法必须解释继承和多态”,我也看过了继承,多态等
等,有一点点了解,您能不能提纲挈领的讲几句?
 
学了大半年了,也曾象楼主这样想过其中的问题,但就是没问!
我想这就是为什么我进步这么慢的原因了!

这个帖子里,我学习到了一种学习方法。非常感谢楼主!
 
明白其中的道理,和暂时不明白没什么差别!
 
高手来了就不要跑,底手也可以发表见解
虽然分不多但贵在参与嘛,
to royal1442:多明白点总是好的,能不能讲点?
TO sefeng1982 :哈哈,原来底手也有人夸奖,多谢多谢,共同进步,呵呵
 
说实话,我不是很明白,但是这不影响我用 OO 的方式来开发软件!
 
to lanyun2
关于你的第一个问题,涉及到VCL的持久化问题。其实你感到奇怪的是,为什么跟踪完Create后,直接就跳转到SetParentComponent方法了,而实际上源代码中没有任何地方给Button1.Parent赋值。对把?我根据刚刚的跟踪说一下加载的步骤
1. Application.CreateForm(Form1, TForm1);
2. TForm1.Create
3. TCustomForm.Create
4.TCustomForm.Create中调用了InitInheritedComponent,部分代码如下:
begin
GlobalNameSpace.BeginWrite;
try
CreateNew(AOwner);
if (ClassType <> TForm) and not (csDesigning in ComponentState) then //注意,这句判断控件是否在设计时期被创建,许多控件的许多代码都做过这样的判断,这其实就是你的第二个问题涉及的部分
begin
Include(FFormState, fsCreating);
try
[red]if not InitInheritedComponent(Self, TForm) then[/red]//加载dfm中定义的其它控件
raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
finally
Exclude(FFormState, fsCreating);
end;
if OldCreateOrder then DoCreate;
end;
finally
GlobalNameSpace.EndWrite;
end;
end;
5.加载控件的时候进入TResourceStream.Create中
6.执行TResourceStream.ReadComponent方法读取dfm中的控件
7.TResourceStream继承自TStream,ReadComponent方法属于TStream,于是进入TStream.ReadComponent中,在TStream.ReadComponent中,动态创建TReader,执行TReader.ReadRootComponent方法
8.在ReadRootComponent方法中,有这样一句:
FRoot.ReadState(Self);
这就跳转到TCustomForm.ReadState方法中
9.在TCustomForm.ReadState方法中,有下面的代码:
procedure TCustomForm.ReadState(Reader: TReader);
var
NewTextHeight: Integer;
Scaled: Boolean;
begin
DisableAlign;
try
FClientWidth := 0;
FClientHeight := 0;
FTextHeight := 0;
Scaled := False;
if ClassParent = TForm then
FOldCreateOrder := not ModuleIsCpp;
inherited ReadState(Reader);//注意这一句,跳转到父类方法中
10.执行TWinControl.ReadState方法,继续向上跳转,来到TControl.ReadState,代码如下:
procedure TControl.ReadState(Reader: TReader);
begin
Include(FControlState, csReadingState);
[red]if Reader.Parent is TWinControl then Parent := TWinControl(Reader.Parent);[/red]
inherited ReadState(Reader);
Exclude(FControlState, csReadingState);
if Parent <> nil then
begin
Perform(CM_PARENTCOLORCHANGED, 0, 0);
Perform(CM_PARENTFONTCHANGED, 0, 0);
Perform(CM_PARENTSHOWHINTCHANGED, 0, 0);
Perform(CM_SYSFONTCHANGED, 0, 0);
Perform(CM_PARENTBIDIMODECHANGED, 0, 0);
end;
end;
注意红色这行如果被读取者(Button1)是TWinControl的话,设置被读取者(这时就是Button1)的Parent为Reader.Parent,这里的Reaeder.Parent就是Form1。要解释更清楚必须引用更多代码,显然SetParentComponent就是在这里被触发的。我只是粗略的跟踪了一下源代码(跳转非常多,我已经吧能省略的都省略了,只说了几个重要的),现在你应该明白是什么调用了它吧?这其实涉及到继承多态和VCL的持久化等诸多方面!不是一两句能解释清楚的。建议您参考一下<Inside VCL>这本书,它会对你很有帮助的。
 
To :zqw0117
多谢指点迷津。现在正在消化中,若无意外,我会在后天结贴。这两天
也欢迎大家继续关注,有质量者会酌量给分,但zql0017 和duhai_lee得的会
比较多,大家没意见吧:)
 
zqw0117大侠:
您的说明很详尽,我从中得到不少知识 ,但我仍然有点疑问.
我的跟踪过程是这样的:
仅仅在Tbutton.Create过程中设置了一个断点,
在button的create完成后,立刻进入了TControl.SetParentConent过程中,
然后又是一个莫名其妙的跳转,至Tcontrol.Setname过程.然后是SetName过
程,在SetName执行完成后,又是一个奇怪的跳转跳至Tcontrol.Readstate,然
后程序流程才到达了您在第10条中列出的过程.很明显,是TControl.SetParentConent
调用了ReadState,而不是您说的Readstate调用SetParentComponent过程.我用的是D7.
是不是我现在不应该看vcl而是去做准备工作?如果是这样子的话要理解vcl的流程我应该从哪里着手呢?
To duhai_lee:
根据您的解释,每一个可视组件在放到窗体上的时候
都要指定其parent,我在动态创建组件的过程中,也知
道若不指定,就看不到他的显示,那么Delphi在创建组
件的时候,肯定也遵守这条规则咯,我就是想看看Delphi
自己的Button1.parent:=***这一句写在哪里,为什么我
看不到它。而Delphi却能够让程序以特定的顺序执行。
如果我们自己也能这样隐式控制程序流程。哈,到时候
我们发布的程序全是一段一段的函数,没其他人能够轻易
搞懂程序是怎么走的。太有意思了。大侠就指点指点吧
 
自己提前
声明:由于现在仍不能理解大虾们所讲解的内容,暂不结贴
 
学习学习。
不过我觉得对vcl首先要建立一个大概的框架所以
也提出自己的一点看法。
首先是类的概念,类简单来说就是形成一个对象的
代码的组合,类最重要的特点是可以重用。

以lz的button为例,要形成一个button,就要
给他创建的方法代码,设置父类的方法代码,
但是我们看vcl的源码的时候经常看到的是inherited,
inherit英文就是继承的意思,所以button会去继承
它父类的create的代码,如果跟踪就会出现跳转到其
父类代码段中了。
vcl在内存中的运作
vcl说到底就是一个指针的集合,还是以button为例,
创建的时候D7首先创建一个Tlist,然后找它的方法代码,
放入代码块,并将指针指向它。
D7一个button.create,cpu就在内存中找button的tlist
指针,找到了就在去找函数create指向的代码块,然后再
载入执行。
这就是vcl的大概框架,当然由于vcl还要考虑更多的东西
所以还要复杂,但是从这个框架考虑会容易理解一些。
个人认为,不对请大家帮忙更正。
 
delphi从入门到精通
面向对象程序设计(刘艺)
面向对象设计实践之路-DELPHI(李维)
Inside VCL(李维)
设计模式delphi版(刘艺)
-------------
建议初学者按以上顺序来阅读这几本书,从编码到项目开发,你会慢慢的成长起来
千万不要好高骛远
 
你的断点下错了位置,不应该在TButton.Create里面下,原因很简单,TButton是在它的父组建(TForm)创建后,从dfm中读出来创建Button1的,那么在它读取完了Button1后(也就是Create了TButton后,紧接着要做的是设置Button1的Caption和它的Parent,所以设置的代码是在TForm.Create里面完成的!而你把断点下在里面,自然里面处理完就跳出来了!你看不到它在那里调用的。
简单的举个例子:

function B: Integer;
begin
[blue]ShowMessage('OK');[/blue]
end;

function LoadDLL: Boolean;
begin
[red]Beep;[/red]
end;

function A: Integer;
begin
B:
LoadDll;
end;

你把断点下在蓝色位置,当然执行完B后就立刻跳到红色部分执行(你就迷惑了,怎么会跳到红色部分呢?蓝色部分里面没有调用红色部分啊?实际上是函数A调用了B和LoadDLL而你下错了断点。你要全面跟踪就把断点下在dpr的Application.CreatForm(Form1, TForm1)这句,然后跟。。。。。

VCL的持久化机制是很令人兴奋的,希望你能仔细琢磨,明白其中的道理:)
 
我把这段写完:
procedure TControl.ReadState(Reader: TReader);
begin
Include(FControlState, csReadingState);
[red]if Reader.Parent is TWinControl then Parent := TWinControl(Reader.Parent);[/red]
[blue]inherited ReadState(Reader);[/blue]
Exclude(FControlState, csReadingState);
if Parent <> nil then
begin
Perform(CM_PARENTCOLORCHANGED, 0, 0);
Perform(CM_PARENTFONTCHANGED, 0, 0);
Perform(CM_PARENTSHOWHINTCHANGED, 0, 0);
Perform(CM_SYSFONTCHANGED, 0, 0);
Perform(CM_PARENTBIDIMODECHANGED, 0, 0);
end;
end;
红色部分完成后,注意,调用了TControl父类的ReadStates,看看父类里面怎么写的:
procedure TComponent.ReadState(Reader: TReader);
begin
Reader.ReadData(Self);
end;
只有一句,再看TReader.ReadData是什么:
procedure TReader.ReadData(Instance: TComponent);
begin
if FFixups = nil then
begin
FFixups := TList.Create;
try
[red]ReadDataInner(Instance);[/red]
DoFixupReferences;
finally
FreeFixups;
end;
end else
ReadDataInner(Instance);
end;
来到红色部分,再看里面:
procedure TReader.ReadDataInner(Instance: TComponent);
var
OldParent, OldOwner: TComponent;
begin
while not EndOfList do ReadProperty(Instance);
ReadListEnd;
OldParent := Parent;
OldOwner := Owner;
Parent := Instance.GetChildParent;
try
Owner := Instance.GetChildOwner;
if not Assigned(Owner) then Owner := Root;
while not EndOfList do [red]ReadComponent(nil);[/red]
ReadListEnd;
finally
Parent := OldParent;
Owner := OldOwner;
end;
end;
注意红色部分,来到:
function TReader.ReadComponent(Component: TComponent): TComponent;
var
CompClass, CompName: string;
Flags: TFilerFlags;
Position: Integer;
OldParent, OldLookupRoot: TComponent;
SubComponents: array of TComponent;

procedure AddSubComponentsToLoaded(Component: TComponent);
var
I: Integer;
begin
for I := 0 to Length(SubComponents) - 1 do
FLoaded.Add(SubComponents);
end;

procedure CheckSubComponents(Component: TComponent);
var
I: Integer;
begin
for I := 0 to Component.ComponentCount - 1 do
if csSubComponent in Component.Components.FComponentStyle then
begin
SetLength(SubComponents, Length(SubComponents) + 1);
SubComponents[Length(SubComponents) - 1] := Component.Components;
end;
end;

procedure SetSubComponentState(State: TComponentState; Add: Boolean = True);
var
I: Integer;
begin
for I := 0 to Length(SubComponents) - 1 do
if Add then
SubComponents.FComponentState := SubComponents.FComponentState + State
else
SubComponents.FComponentState := SubComponents.FComponentState - State;
end;

function ComponentCreated: Boolean;
begin
Result := not (ffInherited in Flags) and (Component = nil);
end;

function Recover(var Component: TComponent): Boolean;
begin
Result := False;
if not (ExceptObject is Exception) then Exit;
if ComponentCreated then Component.Free;
Component := nil;
SkipComponent(False);
Result := Error(Exception(ExceptObject).Message);
end;

procedure CreateComponent;
var
ComponentClass: TComponentClass;
begin
try
ComponentClass := FindComponentClass(CompClass);
Result := nil;
if Assigned(FOnCreateComponent) then
FOnCreateComponent(Self, ComponentClass, Result);
if Result = nil then
begin
Result := TComponent(ComponentClass.NewInstance);
if ffInline in Flags then
begin
Include(Result.FComponentState, csLoading);
Include(Result.FComponentState, csInline);
end;
try
Result.Create(Owner);
except
Result := nil;
raise;
end;
end;
Include(Result.FComponentState, csLoading);
except
if not Recover(Result) then raise;
end;
end;

procedure SetCompName;
begin
try
[red]Result.SetParentComponent(Parent);[/red]
SetName(Result, CompName);
if (csDesigning in Result.ComponentState) and (FindGlobalComponent(CompName) = Result) then
Include(Result.FComponentState, csInline);
except
if not Recover(Result) then raise;
end;
end;

procedure FindExistingComponent;
begin
try
Result := FindAncestorComponent(CompName, FindComponentClass(CompClass));
Parent := Result.GetParentComponent;
if Parent = nil then Parent := Root;
except
if not Recover(Result) then raise;
end;
end;

var
I: Integer;
begin
ReadPrefix(Flags, Position);
CompClass := ReadStr;
CompName := ReadStr;
OldParent := Parent;
OldLookupRoot := FLookupRoot;
try
Result := Component;
if Result = nil then
if ffInherited in Flags then
FindExistingComponent else
CreateComponent;
if Result <> nil then
try
CheckSubComponents(Result);
Include(Result.FComponentState, csLoading);
SetSubComponentState([csLoading]);
[blue]if not (ffInherited in Flags) then SetCompName;[/blue]
if Result = nil then Exit;
if csInline in Result.ComponentState then
FLookupRoot := Result;
Include(Result.FComponentState, csReading);
SetSubComponentState([csReading]);
Result.ReadState(Self);
Exclude(Result.FComponentState, csReading);
SetSubComponentState([csReading], False);
if ffChildPos in Flags then Parent.SetChildOrder(Result, Position);
if (ffInherited in Flags) or (csInline in Result.ComponentState) then
begin
if FLoaded.IndexOf(Result) < 0 then
begin
AddSubComponentsToLoaded(Result);
FLoaded.Add(Result);
end;
end
else
begin
AddSubComponentsToLoaded(Result);
FLoaded.Add(Result);
end;
except
if ComponentCreated then Result.Free;
raise;
end;
finally
Parent := OldParent;
FLookupRoot := OldLookupRoot;
end;
end;
代码执行到蓝色部分,调用了SetCompName,于是在SetCompName中看到了红色的:
Result.SetParentComponent(Parent);
OK,这里就是整个流程了!就在这里调用了SetParentCOmponent,注意,这就是VCL的持久化机制完成的,一步一步达到的。[:D]
 
这次我不省略代码了:)楼主慢慢消化把。
 
后退
顶部