事情是這么來的,我開發的一個程序報了一個錯誤 “在創建窗口句柄之前,不能在控件上調用 Invoke 或 BeginInvoke錯誤”。
然后我在網上查資料,發現一個有意思的問題,文章出處為“在創建窗口句柄之前,不能在控件上調用 Invoke 或 BeginInvoke”錯誤。
問題
程序是如下這樣的。
Form1有Button1、Button2和Button3兩個按鈕,Button2是動態的new Form2窗體,Form2中注冊了Form1中的事件.
Button1的作用是關閉所有new出來的Form2窗體,並且把其資源釋放掉(Dispose()).
Button3是讓Form2中注冊Form1了的事件都發生。
操作:
先點Button2 new出幾個Form2的窗體,然后點擊Button1釋放掉所有的資源,然后再單擊Button2 new出幾個Form2的窗體,再單擊Button3問題就出現了。老是提示"在創建窗口句柄之前,不能在控件上調用 Invoke 或 BeginInvoke"。
作者給出的解析
“在Window窗體程序開發的時候,如果使用多線程編程,在子線程中訪問主線程窗體內的控件,就需要使用控件的Control.Invoke方法或者BeginInvoke方法。但是有時候因為Window執行速度太快,尤其是你寫代碼的時候在InitializeComponent();完成之前起了一個線程去執行某些操作,涉及到窗體控件的,當你在調用Control.Invoke的時候,就可能出現 “在創建窗口句柄之前,不能在控件上調用 Invoke 或 BeginInvoke”錯誤。
這個解釋我感覺十分有道理。
給出的解決方法
解決的辦法就是讓線程等待,直到窗口句柄創建完畢:
//防止在窗口句柄初始化之前就走到下面的代碼
while (!this.IsHandleCreated)
{
;
}
this.BeginInvoke(new ProListIndexChangedDelegate(GetProLyric));
//根據不同情況也可以:
if (this.IsHandleCreated)
BeginInvoke(new ProListIndexChangedDelegate(GetProLyric));
其它人(鑽葛格)給的解決方法:
一個更巧妙的方法,只要在BeginInvoke方法的調用語句前再加一句:IntPtr i = this.Handle;就OK了,這比死循環配合this.IsHandleCreated的判斷方法更簡潔,因為this.Handle這個屬性本身就對應一個方法,取不到句柄,程序就不會向下進行。
如果以上方式時,IntPtr IsHandleCreated = this.Handle;報錯:“從不是創建他的線程訪問”。則可把this換成你想提取的句柄所在的線程中的窗體類(或其中任一控件)的實例名。
本人也就是通過該方法最終解決問題的。也很有可能是作者給出的解釋的那種原因。
結果
作者的解決的結果
再次新建窗體的窗口句柄在Show()之后都通過IsHandleCreated屬性檢測過了,再調用Invoke()之前都創建了,還是會出錯。那問題出在哪里呢?一開始還以為單擊Button1沒有真正的釋放資源,然后我就強制GC了,然后檢測不到以前的窗體了,但是還是不行,還是有問題。
然后就想到了“事件”上面來了。是不是事件的原因呀?以前創建的窗體沒有注銷該事件,把之前所有的窗體都注銷該事件,問題就解決了......
有留言認為作者的意思是:根本不是沒創建的原因,而是沒有清空事件。
我覺得確實應該是訪問了沒有創建的UI資源造成的!作者的解決方法是對的,但是說法是錯的。
也就是作者在Button1中釋放(Close)了Form2了。而卻沒有取消Form2中所訂閱的Form1的事件。
此時當點擊了button3的話,將激發Form1中事件的委托鏈,而已經釋放掉的Form2對象的事件的響應,由於找到不到該對象資源了,造成了老是提示"在創建窗口句柄之前,不能在控件上調用 Invoke 或 BeginInvoke"。
解決辦法缺失如作者所說,去除已經被釋放對象的訂閱的事件即可。