文章編號: 839076 - 上次校閱: 2005年10月28日 - 版次: 3.1

當您使用 Windows Form COM 用戶端應用程式中時,遇到未預期的行為

系統提示本文適用於您使用的作業系統之外的作業系統。與您不相關的文章內容已停用。

在此頁中

全部展開 | 全部摺疊

結論

當您開啟的 Microsoft Windows 表單執行個體從例如 Microsoft Visual Basic 6.0 應用程式一個 COM 用戶端應用程式或 Microsoft 基礎類別 (MFC) 應用程式時,.NET Windows 表單可能會意外行為。例如當您按 TAB 鍵時焦點不會變更從一個控制項到另一個控制項。或當指令按鈕具有焦點時,您可以按 ENTER,按鈕的 Click 事件也不會啟動。您也可能會遇到按鍵或滑鼠活動的未預期的行為。

早期的應用程式不會實作訊息迴圈支援.NET Framework Windows Form 必須具備能夠正確地運作,就會發生這些徵狀。

本文將告訴您,如何解決這些問題,藉由顯示在表單上您使用 Application.Run 方法來建立一個.NET Framework 訊息迴圈。

徵狀

當您從 COM 用戶端應用程式建立的 Microsoft Windows 表單執行個體時,表單可能會意外行為。例如當您建立表單的執行個體,從 Microsoft Visual Basic 6.0 應用程式或從 Microsoft 基礎類別 (MFC) 應用程式,焦點不會變更從一個控制項到另一個控制項當您按 TAB 鍵時。或如果您按下 ENTER,指令按鈕具有焦點時,按鈕的 Click 事件也不會啟動。您也可能會遇到按鍵或滑鼠活動的未預期的行為。

發生的原因

之所以發生這個問題,是因為 Windows Form 會使用訊息迴圈及 COM 用戶端應用程式提供的訊息迴圈都會不同。

Microsoft 進行設計變更解決這個問題。
如需詳細資訊,請按一下下列的文件編號,檢視 「 Microsoft 知識庫 」 中的文件:
885446? (http://support.microsoft.com/kb/885446/ ) FIX: 您可能會遇到未預期的行為,當您按下 TAB 或 ENTER 在 Windows Form 中正在執行.NET Framework 1.1 S 的電腦上
本文將告訴您,如何解決此問題,請在顯示表單上您使用 Application.Run 方法來建立一個.NET Framework 訊息迴圈。

解決方案

要.NET Framework 訊息迴圈使用下列方法之一:

使用 ShowDialog 方法來顯示 Windows 表單

這是最簡單的解決方案,來顯示表單上.NET Framework 訊息迴圈。若要執行此動作取代 ShowDialog 方法在.NET Framework 元件的呼叫中的 顯示 方法的呼叫。

ShowDialog 方法會暫停早期的應用程式訊息迴圈,並顯示表單為對話方塊。因為已擱置主機應用程式訊息迴圈,ShowDialog 方法會建立新的.NET Framework 訊息迴圈來處理表單的訊息。這是最簡單的解決方案,因為它需要最少的程式碼來實作。不過,這種方式缺點就是表單將會以強制回應方式開啟。這種情形時 Windows 表單是開啟任何使用者介面 (UI) 封鎖呼叫的應用程式上。當使用者關閉 Windows Form 時,訊息迴圈結束的.NET Framework 和早期的應用程式訊息迴圈繼續執行。

在新的執行緒上顯示每個 Windows 表單

修改.NET Framework 元件來使用它自己的訊息迴圈在自己的執行緒上顯示表單的每個執行個體。您不能執行每個執行緒的多個訊息迴圈。因此,您不能變更用戶端應用程式訊息迴圈。但是,您可以修改.NET Framework 元件來啟動新的執行緒會使用它自己的訊息迴圈。

附註多執行緒的程式設計是需要仔細考慮實作之前的進階的概念。是特別如此,如果多個執行緒必須存取共用的資源。如果多個執行緒必須在同一時間存取共用的資源,請確定您設計您的.NET Framework 元件的執行緒安全。如需詳細資訊按一下 [下面的文件編號,檢視 「 Microsoft 知識庫 」 中的發行項]:
316136? (http://support.microsoft.com/kb/316136/ ) 如何使用 Visual Basic.NET 同步處理對共用資源在多執行緒環境中存取
如果要在新的執行緒上建立 Windows Form 的每個執行個體,請依照下列步驟執行:
  1. 啟動 Microsoft Visual Studio.NET 2003年。
  2. 建立類別庫。要這麼做,請您執行下列步驟:
    1. 使用 Visual Basic.NET 2003年來建立新的類別庫專案。專案 COMWinform 的名稱。
    2. 刪除預設 Class1.vb 檔案。
    3. 按一下 [專案] 功能表 加入類別
    4. 選取 [COM 類別 樣板。
    5. 在 [名稱] 方塊中輸入 COMForm.vb,然後按一下 [開啟舊檔]。
    6. 貼上下列程式碼陳述式,頂端的在類別定義
      Imports System.Windows.Forms
      Imports System.Runtime.InteropServices
      之前的 COMForm.vb 檔案
    7. COMForm 類別定義中貼上下列程式碼下建立 class.
          Private frmManager As FormManager
      
          Public Sub ShowForm1()
              ' Call the StartForm method by using a new instance
              ' of the Form1 class.
              StartForm(New Form1)
          End Sub
      
          Private Sub StartForm(ByVal form As Form)
              ' This procedure is used to show all forms
              ' that the client application requests.
              
              ' Later forms will appear on a new message loop.
              If IsNothing(frmManager) Then
                  frmManager = New FormManager
              End If
              frmManager.ShowForm(form)
          End Sub
      
          Protected Overrides Sub Finalize()
              frmManager = Nothing
              MyBase.Finalize()
          End Sub
    8. 按一下 [專案] 功能表 加入類別
    9. 按一下 [類別]。
    10. 在 [名稱] 方塊中輸入 FormManager.vb,],然後再按一下 [確定]]。
    11. Replace the contents of the FormManager.vb file with the following code.
      Imports System.Runtime.InteropServices
      Imports System.Threading
      Imports System.Windows.Forms
      
      <ComVisible(False)> _
      Friend Class FormManager
          Public Sub ShowForm(ByVal form As Form)
              Dim thrd As Thread
              Dim wrapper As FormWrapper
              wrapper = New FormWrapper(form)
              thrd = New Thread(AddressOf wrapper.ShowFormOnNewThread)
              thrd.IsBackground = True
              thrd.ApartmentState = ApartmentState.STA
              thrd.Start()
          End Sub
      
          Private Class FormWrapper
              Private WithEvents appContext As ApplicationContext
              Public Sub New(ByVal form As Form)
                  MyBase.New()
                  appContext = New ApplicationContext(form)
              End Sub
      
              Public Sub ShowFormOnNewThread()
                  Application.Run(appContext)
              End Sub
      
              Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit
                  appContext.MainForm.Dispose()
                  appContext.MainForm = Nothing
                  appContext.Dispose()
                  appContext = Nothing
              End Sub
          End Class
      End Class
    12. 在 [專案] 功能表上按一下 [加入 Windows Form,] 然後按一下 [開啟]。
    13. 將一些 TextBox 控制項和命令按鈕加入至表單。
    14. 將下列程式碼加入 Click 事件處理常式的命令] 按鈕。
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
         MessageBox.Show("Clicked button")
      End Sub
      
    15. 建置方案。這個步驟也註冊 COM Interop 這台電腦上的專案。
  3. 建立可執行檔。要這麼做,請您執行下列步驟:
    1. 啟動 Microsoft Visual Basic 6.0。
    2. 建立新的標準 EXE 專案。
    3. 按一下 [專案] 功能表 引用項目
    4. 加入 COMWinform 型別程式庫建置 Visual Basic.NET 解決方案時所產生的參考。如果您做不到清單中,按一下 [瀏覽] 來手動尋找型別程式庫 (.tlb) 檔案]。
    5. 新增指令按鈕至表單。
    6. 在 [檢視] 功能表上按一下 [程式碼,] 然後將下列程式碼加入至
      Option Explicit
      
      Private Sub Command1_Click()
          Dim frm As COMWinform.COMForm
          Set frm = New COMWinform.COMForm
          frm.ShowForm1
      End Sub
      
      表單模組。
    7. 按一下 [檔案] 功能表 建立 EXE 來編譯專案。
  4. 執行已編譯的 Visual Basic 6.0 中可執行檔。
  5. 按一下按鈕。從您稍早建立的類別程式庫的 Windows Form 會出現。
  6. 將焦點設定上的其中一個 Windows] 表單上 文字方塊 控制項,然後按下 TAB 控制項之間移動。
請注意 TAB 鍵運作順利控制項之間移動。另外而且,請注意當您按下 ENTER 鍵時,會引發指令按鈕的 Click 事件。

在.NET Framework 元件中的新執行緒上建立共用的訊息迴圈

這個方法很類似 [顯示新的執行緒上的每個 Windows Form 」 一節。不過,代替顯示在它自己的執行緒上的每個表單使用自己的訊息迴圈,這個方法會建立在.NET Framework 元件中只能有一個新的執行緒執行的共用的訊息迴圈。

這個方法更精確地表示如果您正在執行標準的 Windows Form 應用程式,則會有的行為。這項設計也容易共用多個表單間的資源,因為所有表單相同的執行緒上都執行。在 [顯示新的執行緒上的每個 Windows Form 」 一節的解決方案建立新的執行緒,為每個表單。該解決方案需要額外的執行緒同步處理程式碼,以不同的表單之間共用資源。

因為這個方法是多類似,比其他方法,Windows Form 應用程式的行為,請注意.NET Framework 訊息迴圈停止時,將會關閉所有用戶端應用程式開啟的.NET Framework 表單。當使用者關閉的 ApplicationContext 指定為主要表單的表單時,就會發生這種行為。ApplicationContext 用來開始訊息迴圈。

在下列的程式碼範例的 ApplicationContext 主要表單設定為第一個用戶端應用程式開啟的表單。因此,當使用者關閉該表單執行個體、 在.NET Framework 訊息迴圈結束,以及其他所有的 Windows Form 會關閉。

若要在所有表單來使用,請依照下列步驟執行新的執行緒上建立共用的訊息迴圈:
  1. 啟動 Microsoft Visual Studio.NET 2003年。
  2. 建立類別庫。要這麼做,請您執行下列步驟:
    1. 使用 Visual Basic.NET 2003年來建立新的類別庫專案,名為 COMWinform。
    2. 刪除預設 Class1.vb 檔案。
    3. 按一下 [專案] 功能表 加入類別
    4. 選取 [COM 類別 樣板。
    5. 在 [名稱] 方塊中輸入 COMForm.vb,然後按一下 [開啟舊檔]。
    6. 貼上下列程式碼陳述式,頂端的在類別定義
      Imports System.Windows.Forms
      Imports System.Runtime.InteropServices
      之前的 COMForm.vb 檔案
    7. 類別定義中的 COMForm,貼上下列程式碼下建立 class.
      
          Private WithEvents frmManager As FormManager
      
          Public Sub ShowForm1()
              ' Call the StartForm method by using a new instance
              ' of the Form1 class.
              StartForm(New Form1)
          End Sub
      
          Private Sub StartForm(ByVal frm As Form)
      
              ' This procedure is used to show all forms
              ' that the client application requests. When the first form
              ' is displayed, this code will create a new message
              ' loop that runs on a new thread. The new form will
              ' be treated as the main form.
      
              ' Later forms will be shown on the same message loop.
              If IsNothing(frmManager) Then
                  frmManager = New FormManager(frm)
              Else
                  frmManager.ShowForm(frm)
              End If
          End Sub
      
          Private Sub frmManager_MessageLoopExit() Handles frmManager.MessageLoopExit
              'Release the reference to the frmManager object.
              frmManager = Nothing
          End Sub
      
    8. 按一下 [專案] 功能表 加入類別
    9. 按一下 [類別]。
    10. 在 [名稱] 方塊中輸入 FormManager.vb,],然後再按一下 [確定]]。
    11. 下列程式碼以取代 FormManager.vb 檔案的內容
      Imports System.Runtime.InteropServices
      Imports System.Threading
      Imports System.Windows.Forms
      
      <ComVisible(False)> _
      Friend Class FormManager
          ' This class is used so that you can generically pass any
          ' form that you want to the delegate.
      
          Private WithEvents appContext As ApplicationContext
          Private Delegate Sub FormShowDelegate(ByVal form As Form)
          Event MessageLoopExit()
      
          Public Sub New(ByVal MainForm As Form)
              Dim t As Thread
              If IsNothing(appContext) Then
                  appContext = New ApplicationContext(MainForm)
                  t = New Thread(AddressOf StartMessageLoop)
                  t.IsBackground = True
                  t.ApartmentState = ApartmentState.STA
                  t.Start()
              End If
          End Sub
      
          Private Sub StartMessageLoop()
              ' Call the Application.Run method to run the form on its own message loop.
              Application.Run(appContext)
          End Sub
       
           Public Sub ShowForm(ByVal form As Form)
      
              Dim formShow As FormShowDelegate
      
              ' Start the main form first. Otherwise, focus will stay on the 
              ' calling form.
              appContext.MainForm.Activate()
      
              ' Create a new instance of the FormShowDelegate method, and
              ' then invoke the delegate off the MainForm object.
              formShow = New FormShowDelegate(AddressOf ShowFormOnMainForm_MessageLoop)
              appContext.MainForm.Invoke(formShow, New Object() {form})
          End Sub
      
          Private Sub ShowFormOnMainForm_MessageLoop(ByVal form As Form)
              form.Show()
          End Sub
       
          Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit
              appContext.MainForm.Dispose()
              appContext.MainForm = Nothing
              appContext.Dispose()
              appContext = Nothing
              RaiseEvent MessageLoopExit()
          End Sub
        End Class
      End Class
    12. 在 [專案] 功能表上按一下 [加入 Windows Form,] 然後按一下 [開啟]。
    13. 將一些 TextBox 控制項和命令按鈕加入至表單。
    14. 將下列程式碼加入 Click 事件處理常式的命令] 按鈕。
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
         MessageBox.Show("Clicked button")
      End Sub
      
    15. 建置方案。這個步驟也會註冊 COM Interop 專案在這台電腦上。
  3. 建立可執行檔。要這麼做,請您執行下列步驟:
    1. 啟動 Microsoft Visual Basic 6.0。
    2. 建立新的標準 EXE 專案。
    3. 按一下 [專案] 功能表 引用項目
    4. 加入 COMWinform 型別程式庫建置 Visual Basic.NET 解決方案時所產生的參考。如果您做不到清單中,按一下 [瀏覽] 來手動尋找型別程式庫 (.tlb) 檔案]。
    5. 新增指令按鈕至表單。
    6. 在 [檢視] 功能表上按一下 [程式碼,] 然後將下列程式碼加入至
      Option Explicit
      
      Private Sub Command1_Click()
          Dim frm As COMWinform.COMForm
          Set frm = New COMWinform.COMForm
          frm.ShowForm1
      End Sub
      
      表單模組。
    7. 按一下 [檔案] 功能表 建立 EXE 來編譯專案。
  4. 執行已編譯的 Visual Basic 6.0 中可執行檔。
  5. 按一下按鈕。從您稍早建立的類別程式庫的 Windows Form 會出現。
  6. 將焦點設定上的其中一個 Windows] 表單上 文字方塊 控制項,然後按下 TAB 控制項之間移動。
請注意 TAB 鍵在控制項之間移動順利運作。另外而且,請注意當您按下 ENTER 鍵時,會引發指令按鈕的 Click 事件。

狀況說明

這種行為是經過設計規劃的。

其他相關資訊

應用程式的訊息迴圈是從執行緒的訊息佇列擷取訊息的內部程式迴圈會轉譯它們,然後將它們傳送至應用程式,以處理。Windows Form 的訊息迴圈不具有相同的架構作為如 Visual Basic 6.0 應用程式和 MFC 應用程式的舊版應用程式提供的訊息迴圈。非 Windows Form 預期張貼到訊息迴圈的視窗訊息可能會以不同方式處理。因此,可能會發生未預期的行為。某些按鍵組合可能無法運作、 一些滑鼠活動可能無法運作,或如預期般運作,不可能會引發某些事件。

附註.NET Framework 訊息迴圈是唯一支援的架構對於顯示和使用 [Windows 表單。在 < 解決方案 > 一節中所述的解決方案只會套用至顯示 Windows Form。來裝載 (Host) 透過 COM 可呼叫包裝函式 (CCW) 的.NET Framework 控制項只支援的容器是 Microsoft Internet Explorer。因為控制項必須在容器中,所以該控制項必須使用容器提供的訊息迴圈。因此,您無法使用 < 解決方案 > 一節中所述的解決方案為.NET Framework 控制項。

如需詳細資訊按一下 [下面的文件編號,檢視 「 Microsoft 知識庫 」 中的發行項]:
311334? (http://support.microsoft.com/kb/311334/ ) 支援.NET 控制項的 ActiveX 控制項容器
317346? (http://support.microsoft.com/kb/317346/ ) 原生 COM 可呼叫.NET 與控制在 Internet Explorer 中

重現這個問題的步驟

  1. 啟動 Microsoft Visual Studio.NET 2003年。
  2. 建立類別庫。要這麼做,請您執行下列步驟:
    1. 使用 Visual Basic.NET 2003年來建立新的類別庫專案,名為 COMWinform。
    2. 刪除預設 Class1.vb 檔案。
    3. 按一下 [專案] 功能表 加入類別
    4. 選取 [COM 類別 樣板。
    5. 在 [名稱] 方塊中輸入 COMForm.vb,然後按一下 [開啟舊檔]。
    6. 貼上下列程式碼陳述式,頂端的在類別定義
      Imports System.Windows.Forms
      Imports System.Runtime.InteropServices
      之前的 COMForm.vb 檔案
    7. 類別定義中的 COMForm,貼上下列程式碼下建立 class.時插入的程式碼
          Public Sub ShowForm1()
              ' Create an instance of the .NET Windows Form (Form1),
              ' and then display it
      
              Dim frm As Form1
              frm = New Form1
              frm.Show
          End Sub
      End Class
    8. 在 [專案] 功能表上按一下 [加入 Windows Form,] 然後按一下 [開啟]。
    9. 將一些 TextBox 控制項和命令按鈕加入至表單。
    10. 將下列程式碼加入 Click 事件處理常式的命令] 按鈕。
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
         MessageBox.Show("Clicked button")
      End Sub
      
    11. 建置方案。這個步驟也會註冊 COM Interop 專案在這台電腦上。
  3. 建立可執行檔。要這麼做,請您執行下列步驟:
    1. 啟動 Microsoft Visual Basic 6.0。
    2. 建立新的標準 EXE 專案。
    3. 按一下 [專案] 功能表 引用項目
    4. 加入 COMWinform 型別程式庫建置 Visual Basic.NET 解決方案時所產生的參考。如果您做不到清單中,按一下 [瀏覽] 來手動尋找型別程式庫 (.tlb) 檔案]。
    5. 新增指令按鈕至表單。
    6. 在 [檢視] 功能表上按一下 [程式碼,] 然後將下列程式碼加入至
      Option Explicit
      
      Private Sub Command1_Click()
          Dim frm As COMWinform.COMForm
          Set frm = New COMWinform.COMForm
          frm.ShowForm1
      End Sub
      
      表單模組。
    7. 按一下 [檔案] 功能表 建立 EXE 來編譯專案。
  4. 執行已編譯的 Visual Basic 6.0 中可執行檔。
  5. 按一下按鈕。從您稍早建立的類別程式庫的 Windows Form 會出現。
  6. 將焦點設定上的其中一個 Windows] 表單上 文字方塊 控制項,然後按下 TAB 控制項之間移動。請注意 TAB 鍵不會移動焦點在控制項之間如預期般運作。
  7. 將焦點設定在 [Windows] 表單上的 [命令] 按鈕,然後按下 ENTER。請注意按鈕沒有回應,而不會啟動 Click 事件。

?考

如需詳細資訊按一下 [下面的文件編號,檢視 「 Microsoft 知識庫 」 中 「 文件]:
316422? (http://support.microsoft.com/kb/316422/ ) 在 Visual Basic.NET 執行緒的藍圖
315577? (http://support.microsoft.com/kb/315577/ ) 如何在 Visual Basic.NET 中建立的執行緒

這篇文章中的資訊適用於:
  • Microsoft .NET Framework 1.1
  • Microsoft Visual Basic Enterprise Edition for Windows 6.0
  • Microsoft Foundation Class Library 4.2
關鍵字:?
kbmt kbprb kbwindowsforms kbinfo KB839076 KbMtzh
機器翻譯機器翻譯
重要:本文是以 Microsoft 機器翻譯軟體翻譯而成,而非使用人工翻譯而成。Microsoft 同時提供使用者人工翻譯及機器翻譯兩個版本的文章,讓使用者可以依其使用語言使用知識庫中的所有文章。但是,機器翻譯的文章可能不盡完美。這些文章中也可能出現拼字、語意或文法上的錯誤,就像外國人在使用本國語言時可能發生的錯誤。Microsoft 不為內容的翻譯錯誤或客戶對該內容的使用所產生的任何錯誤或損害負責。Microsoft也同時將不斷地就機器翻譯軟體進行更新。
按一下這裡查看此文章的英文版本:839076? (http://support.microsoft.com/kb/839076/en-us/ )
Microsoft及(或)其供應商不就任何在本伺服器上發表的文字資料及其相關圖表資訊的恰當性作任何承諾。所有文字資料及其相關圖表均以「現狀」供應,不負任何擔保責任。Microsoft及(或)其供應商謹此聲明,不負任何對與此資訊有關之擔保責任,包括關於適售性、適用於某一特定用途、權利或不侵權的明示或默示擔保責任。Microsoft及(或)其供應商無論如何不對因或與使用本伺服器上資訊或與資訊的實行有關而引起的契約、過失或其他侵權行為之訴訟中的特別的、間接的、衍生性的損害或任何因使用而喪失所導致的之損害、資料或利潤負任何責任。