在C#中子线程怎样操作主线程中窗口上控件
在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 /// 【開始】button的单击事件处理函数,新建一个线程向窗口上的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 }
这样子就能够实现用子线程去操作主线程窗口上的控件的内容,同一时候,又不影响主线程对窗口上其他控件的响应。程序运行截图例如以下:
点击[開始]button,程序开启一个新的线程,不断向列表中加入新的数据。而同一时候不会影响主界面对其他控件(比如:文本框)的响应。
[P.S]:
INVOKE方法的作用:
它允许在那里运行的线程控制Invoke在代理指定的方法参数。这是我们要控制的操作执行的主线程运行。