.NET寻根问底之窗体Resize事件

时间:2023-02-10 23:44:49

.NET寻根问底之窗体Resize事件


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

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

我最近正在把我掌握的有关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事件激发顺序问题在实际开发中可能并不十分重要与关键,但这种技术探索精神很可贵。如果学技术、教技术、用技术的人都有这种探索劲头,杜绝浮燥的急功近利的风气,相信国内软件开发者的总体水平绝不会差。