今天收到一名名為“三十而悟”的中專學校的計算機教師同行的郵件:
=====================================================
我最近正在把我掌握的有關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
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
return new Size(this.width, this.height);
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);
在窗體過程WndProc中,響應窗體建立消息(WM_CREATE)時,就會調用WmCreate()方法。WM_CREATE消息在窗體建立時由Windows負責發送給程序。
是以,經過刨根問底的一番尋根之旅,我們弄明白了産生這一現象的根本原因。
感想:
微軟平台的技術大都有這樣的一個特點:抽象層次較高,使用簡易。但封裝層次很多,表面的“簡單”是由背後的“複雜”所支撐。很多使用微軟技術的程式員習慣了“隻管用,不管為什麼”,其實這種工作與學習方式并不利于技術的進一步提升。
就本文所讨論的問題而言,雖然Resize事件激發順序問題在實際開發中可能并不十分重要與關鍵,但這種技術探索精神很可貴。如果學技術、教技術、用技術的人都有這種探索勁頭,杜絕浮燥的急功近利的風氣,相信國内軟體開發者的總體水準絕不會差。
本文轉自 wws5201985 51CTO部落格,原文連結:http://blog.51cto.com/wws5201985/777172,如需轉載請自行聯系原作者