技术开发 频道

.NET探寻之路:窗体Resize事件调用之谜

        【IT168 技术】今天收到一名名为“三十而悟”的中专学校的计算机教师同行的邮件:

  =====================================================

  我最近正在把我掌握的有关Visual Basic的一些教学和开发经验写成一本书,目前写了20多万字了。在写的过程中,发现存在一个很有意思的问题,一直百思不解。特向您请教一下。

  问题描述:在Visual Basic 2005的IDE环境下,我使用的IDE是Visual Studio 2005 Team Suite,环境设置选的是“Visual Basic开发设置”。在窗体的两个事件Resize和Load出现的先后顺序中出现的问题。按理说,这两个事件,Load一定是发生于Resize事件之前的。但是,我在编写代码过程中,有一次偶然的机会,发现Resize事件是发生在Load之前,我百思不得其解。在花了一天的时间找原因时,才重现了这种Resize发生于Load之前的情境。我发现,如果在窗体设计阶段,重新调整一下窗体的大小不按默认的300*300的大小设置,则Resize事件必发生在Load事件之前。我虽然重现了这种情况,但我找不出这种情况出现的原因。

  当然如果一定要在Resize事件和Load事件中写代码且要区分他们的先后,用一个开关量是可以解决的。只是问题放在心里特不好受,网上也找了很多资料,就是没办法解决。只能向您发送一个请教了,您毕竟是同行中专家。打扰之处,请原谅!

  来自一个中专学校的计算机教师同行

  敬上,谢谢!

  =================================================

  我打开Visual Studio,检测了一下,真的如他所说。这也引发了我的兴趣,产生这种现象的原因何在?

  查询MSDN未获直接答案,我觉得必须到.NET源代码中去找谜底。

  以下是我的追寻之路:

  首先,我发现Form类定义了一个DefaultSize属性:

protected override Size DefaultSize

  {

  
get

  {

  return
new Size(300, 300);

  }

  }

  

  可以看到,它设定的默认属性就是300*300.

  然后,我在Form类的基类Control的构造函数中找到以下代码:

internal Control(bool autoInstallSyncContext)

  {

  ……

  Size defaultSize
= this.DefaultSize;

  this.width
= defaultSize.Width;

  this.height
= defaultSize.Height;

  ……

  }

 

  可以看到,在构造函数中窗体的高度和宽度被定义为默认值。

  打开Visual studio自动生成的的Form1.Designer.vb,可以看到以下代码:

Private Sub InitializeComponent()

  Me.SuspendLayout()

  
'

  
'Form1

  
'

  Me.AutoScaleDimensions
= New System.Drawing.SizeF(6.0!, 12.0!)

  Me.AutoScaleMode
= System.Windows.Forms.AutoScaleMode.Font

  Me.ClientSize
= New System.Drawing.Size(562, 415)

  Me.Name
= "Form1"

  Me.Text
= "Form1"

  Me.ResumeLayout(
False)

  
End Sub

 

  这说明窗体的ClientSize属性已经被改为默认值(300,300)之外的数值 。

  ClientSize是一个属性,给它赋值时会调用SetClientSizeCore方法。

public Size ClientSize

  {

  
get

  {

  return
new Size(this.clientWidth, this.clientHeight);

  }

  
set

  {

  this.SetClientSizeCore(value.Width, value.Height);

  }

  }

 

  SetClientSizeCore方法内部又调用SizeFromClientSize方法设置窗体的Size属性:

protected virtual void SetClientSizeCore(int x, int y)

  {

  this.Size
= this.SizeFromClientSize(x, y);

  this.clientWidth
= x;

  this.clientHeight
= y;

  this.OnClientSizeChanged(EventArgs.Empty);

  }

 

  Size属性内部调用SetBounds方法

public Size Size

  {

  
get

  {

  return
new Size(this.width, this.height);

  }

  
set

  {

  this.SetBounds(this.x, this.y, value.Width, value.Height, BoundsSpecified.Size);

  }

  }

 

  SetBounds方法又经过几个方法调用(不再赘述),最终调用UpdateBounds方法。

  在Form类的UpdateBounds方法中,可以看到以下代码:

protected void UpdateBounds(int x, int y, int width, int height, int clientWidth, int clientHeight)

  {

  。。。

  bool flag2
= (((this.Width != width) || (this.Height != height)) || (this.clientWidth != clientWidth)) || (this.clientHeight != clientHeight);

  this.x
= x; this.y = y; this.width = width; this.height = height;

  this.clientWidth
= clientWidth; this.clientHeight = clientHeight;

  。。。

  
if (flag2) {

  this.OnSizeChanged(EventArgs.Empty);

  。。。

  }

  。。。

 

  可以看到,只要尺寸不等于300*300,就会调用OnSizeChanged方法,而此方法负责激发Resize事件。

  那么,UpdateBounds方法在什么情况被调用?

  使用Reflector继续跟踪下去,可以看到,位于最顶层的并且间接调用它的语句在WmCreate()方法中,而此方法又位于窗体过程WndProc内。

protected virtual void WndProc(ref Message m)

  {

  
if ((this.controlStyle & ControlStyles.EnableNotifyMessage) == ControlStyles.EnableNotifyMessage)

  {

  this.OnNotifyMessage(m);

  }

  switch (m.Msg)

  {

  
case 1:

  this.WmCreate(ref m);

  return;

  
case 2:

  this.WmDestroy(ref m);

  return;

  。。。

  }

  。。。

  }

 

  在窗体过程WndProc中,响应窗体创建消息(WM_CREATE)时,就会调用WmCreate()方法。WM_CREATE消息在窗体创建时由Windows负责发送给进程。

  所以,经过刨根问底的一番寻根之旅,我们弄明白了产生这一现象的根本原因。

  感想:

  微软平台的技术大都有这样的一个特点:抽象层次较高,使用简易。但封装层次很多,表面的“简单”是由背后的“复杂”所支撑。很多使用微软技术的程序员习惯了“只管用,不管为什么”,其实这种工作与学习方式并不利于技术的进一步提升。

  就本文所讨论的问题而言,虽然Resize事件激发顺序问题在实际开发中可能并不十分重要与关键,但这种技术探索精神很可贵。如果学技术、教技术、用技术的人都有这种探索劲头,杜绝浮燥的急功近利的风气,相信国内软件开发者的总体水平绝不会差。

0
相关文章