在C#中,直接在子線程中對窗體上的控件操作是會出現異常,這是由於子線程和運行窗體的線程是不同的空間,因此想要在子線程來操作窗體上的控件,是不可能 簡單的通過控件對象名來操作,但不是說不能進行操作,微軟提供了Invoke的方法,其作用就是讓子線程告訴窗體線程來完成相應的控件操作。
要實現該功能,基本思路如下:
把想對另一線程中的控件實施的操作放到一個函數中,然后使用delegate代理那個函數,並且在那個函數中加入一個判斷,用 InvokeRequired 來判斷調用這個函數的線程是否和控件線程處於同一線程中,如果是則直接執行對控件的操作,否則利用該控件的Invoke或BeginInvoke方法來執 行這個代理。示例代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Windows.Forms; 4 5 using System.Threading; 6 7 namespace 子線程操作主線程窗體上的控件 8 { 9 public partial class frmMain : Form 10 { 11 /********************** 定義該類的私有成員 **************************/ 12 13 /// <summary> 14 /// 定義一個隊列,用於記錄用戶創建的線程 15 /// 以便在窗體關閉的時候關閉所有用於創建的線程 16 /// </summary> 17 private List<Thread> ChaosThreadList; 18 19 /********************** 該類的初始化相關函數 ************************/ 20 21 /// <summary> 22 /// 窗體的初始化函數,初始化線程隊列ChaosThreadList 23 /// </summary> 24 public frmMain() 25 { 26 InitializeComponent(); 27 ChaosThreadList = new List<Thread>(); 28 } 29 30 /// <summary> 31 /// 窗體的關閉事件處理函數,在該事件中將之前創建的線程全部終止 32 /// </summary> 33 /// <param name="sender"></param> 34 /// <param name="e"></param> 35 private void frmMain_FormClosed(object sender, FormClosedEventArgs e) 36 { 37 if (ChaosThreadList.Count > 0) 38 { 39 //編列自定義隊列,將所有線程終止 40 foreach (Thread tWorkingThread in ChaosThreadList) 41 { 42 tWorkingThread.Abort(); 43 } 44 } 45 } 46 47 /**************************** 定義該類的自定義函數 ***********************/ 48 49 /// <summary> 50 /// 定義一個代理 51 /// </summary> 52 /// <param name="index"></param> 53 /// <param name="MSG"></param> 54 private delegate void DispMSGDelegate(int index,string MSG); 55 56 /// <summary> 57 /// 定義一個函數,用於向窗體上的ListView控件添加內容 58 /// </summary> 59 /// <param name="iIndex"></param> 60 /// <param name="strMsg"></param> 61 private void DispMsg(int iIndex,string strMsg) 62 { 63 if (this.lstMain.InvokeRequired==false) //如果調用該函數的線程和控件lstMain位於同一個線程內 64 { 65 //直接將內容添加到窗體的控件上 66 ListViewItem lvi = new ListViewItem(); 67 lvi.SubItems[0].Text = iIndex.ToString(); 68 lvi.SubItems.Add(strMsg); 69 this.lstMain.Items.Insert(0, lvi); 70 } 71 else //如果調用該函數的線程和控件lstMain不在同一個線程 72 { 73 //通過使用Invoke的方法,讓子線程告訴窗體線程來完成相應的控件操作 74 DispMSGDelegate DMSGD = new DispMSGDelegate(DispMsg); 75 76 //使用控件lstMain的Invoke方法執行DMSGD代理(其類型是DispMSGDelegate) 77 this.lstMain.Invoke(DMSGD, iIndex, strMsg); 78 79 } 80 } 81 82 /// <summary> 83 /// 定義一個線程函數,用於循環向列表中添加數據 84 /// </summary> 85 private void Thread_DisplayMSG() 86 { 87 for (int i = 0; i < 10000; i++) 88 { 89 DispMsg(i + 1, "Welcome you : " + (i + 1).ToString()); 90 Thread.Sleep(10); 91 } 92 } 93 94 /******************************* 定義該類的事件處理函數 ********************************/ 95 96 /// <summary> 97 /// 【開始】按鈕的單擊事件處理函數,新建一個線程向窗體上的ListView控件填寫內容 98 /// </summary> 99 /// <param name="sender"></param> 100 /// <param name="e"></param> 101 private void btnBegin_Click(object sender, EventArgs e) 102 { 103 //創建一個新的線程 104 Thread tWorkingThread = new Thread(Thread_DisplayMSG); 105 106 //將新建的線程加入到自定義線程隊列中,以便在窗體結束時關閉所有的線程 107 ChaosThreadList.Add(tWorkingThread); 108 109 //開啟線程 110 tWorkingThread.Start(); 111 } 112 113 } 114 }
這樣子就可以實現用子線程去操作主線程窗體上的控件的內容,同時,又不影響主線程對窗體上其他控件的響應。程序運行截圖如下:
點擊[開始]按鈕,程序開啟一個新的線程,不斷向列表中添加新的數據,而同時不會影響主界面對其它控件(例如:文本框)的響應。
[P.S]:
INVOKE方法的作用:
它使該控件所在的線程執行Invoke方法參數中指定的代理,也就是使主線程執行我們想對控件進行的操作。