none
为何自定义事件的处理程序里面,仍然不能够直接对窗体的控件操作? RRS feed

  • 问题

  • 我的程序有一个主窗体,运行之后启动一个线程,线程一直在后台运行。我还定义了几个事件,当线程运行到某些状态的时候就触发这些事件,这些事件在主窗体中处理。主要就是用来更新主窗体上的一些控件,用来显示线程的执行状态。

    原本以为使用了事件机制之后,在事件处理程序里面,就可以直接对控件操作了,但实际不是,我仍然需要用BeginInvoke来异步执行。

    我想如果这样,我倒不如直接在线程里面调用BeginInvoke,用不着事件机制。

    但是使用事件的话,我可以将线程和前端界面分开,效果很好,所以仍然倾向于使用事件,请教各位高手,这样设计可以使得在事件处理程序中能够直接对窗体中的控件操作?
    da jia hao!
    2009年11月19日 6:27

答案

  • 你好!
         需要把那个控件的实例通过事件参数传递给事件处理程序,然后才能操作这个控件!
    周雪峰
    • 已标记为答案 KeFang Chen 2009年11月25日 4:10
    2009年11月19日 7:00
    版主
  • 对于楼主将工作代码与前台界面分开的设想,先赞一个!我也参与一下讨论:
    通过调用控件的Invoke(或BeginInvoke)方法,实现一个与线程无关的界面函数(如楼主所言),我先写下来:
            delegate void NewDelegate(string str);
            private void SetText(string str)
            {
                if (this.textBox1.InvokeRequired)
                {
                    NewDelegate nd = new NewDelegate(SetText);
                    this.textBox1.Invoke(nd,str);
                }
                else
                {
                    this.textBox1.Text = str;
                }
            }
    但就像楼主所说的,在工作代码中调用此函数时,确实就有点“职责不单一”的嫌疑了。用事件的解决方式看起来好一些,但是又无法操控这个TextBox1控件,对此我建议一个方案(传递主线程的同步上下文到工作线程中去),这个想法有利也有弊,而且从设计角度看,一定层度上解决职责问题的同时,也增加了蔓延的风险。代码提供如下,供讨论批评:
            private void button1_Click(object sender, EventArgs e)
            {
                Thread t = new Thread(o => DoWork(o));
                t.Start(SynchronizationContext.Current);            
            }
            void Form1_mEvent1(object sender, EventArgs e)
            {
                (sender as SynchronizationContext).Send(c => this.textBox1.Text = "aaa", null);
            }
            void Form1_mEvent2(object sender, EventArgs e)
            {
                (sender as SynchronizationContext).Send(c => this.textBox1.Text = "bbb", null);
            }
            private void DoWork(object o)
            {
                Thread.Sleep(1000);         //工作代码1
                if (mEvent1 != null)        //事件1
                {
                    this.mEvent1(o,null);   //null 的位置为其他需要传递的参数准备
                }
                Thread.Sleep(1000);         //工作代码2          
                if (mEvent2 != null)        //事件2
                {
                    this.mEvent2(o, null);
                }
            } 
    上述省去了事件声明及绑定部分。
    最后,倒是建议在事件响应函数中直接调用SetText,这样可省去同步上下文,也许更好

    thinkbank
    • 已建议为答案 angelo.yee 2009年11月24日 9:25
    • 已标记为答案 KeFang Chen 2009年11月25日 4:11
    2009年11月19日 8:45

全部回复

  • 你好!
         需要把那个控件的实例通过事件参数传递给事件处理程序,然后才能操作这个控件!
    周雪峰
    • 已标记为答案 KeFang Chen 2009年11月25日 4:10
    2009年11月19日 7:00
    版主
  • 对于楼主将工作代码与前台界面分开的设想,先赞一个!我也参与一下讨论:
    通过调用控件的Invoke(或BeginInvoke)方法,实现一个与线程无关的界面函数(如楼主所言),我先写下来:
            delegate void NewDelegate(string str);
            private void SetText(string str)
            {
                if (this.textBox1.InvokeRequired)
                {
                    NewDelegate nd = new NewDelegate(SetText);
                    this.textBox1.Invoke(nd,str);
                }
                else
                {
                    this.textBox1.Text = str;
                }
            }
    但就像楼主所说的,在工作代码中调用此函数时,确实就有点“职责不单一”的嫌疑了。用事件的解决方式看起来好一些,但是又无法操控这个TextBox1控件,对此我建议一个方案(传递主线程的同步上下文到工作线程中去),这个想法有利也有弊,而且从设计角度看,一定层度上解决职责问题的同时,也增加了蔓延的风险。代码提供如下,供讨论批评:
            private void button1_Click(object sender, EventArgs e)
            {
                Thread t = new Thread(o => DoWork(o));
                t.Start(SynchronizationContext.Current);            
            }
            void Form1_mEvent1(object sender, EventArgs e)
            {
                (sender as SynchronizationContext).Send(c => this.textBox1.Text = "aaa", null);
            }
            void Form1_mEvent2(object sender, EventArgs e)
            {
                (sender as SynchronizationContext).Send(c => this.textBox1.Text = "bbb", null);
            }
            private void DoWork(object o)
            {
                Thread.Sleep(1000);         //工作代码1
                if (mEvent1 != null)        //事件1
                {
                    this.mEvent1(o,null);   //null 的位置为其他需要传递的参数准备
                }
                Thread.Sleep(1000);         //工作代码2          
                if (mEvent2 != null)        //事件2
                {
                    this.mEvent2(o, null);
                }
            } 
    上述省去了事件声明及绑定部分。
    最后,倒是建议在事件响应函数中直接调用SetText,这样可省去同步上下文,也许更好

    thinkbank
    • 已建议为答案 angelo.yee 2009年11月24日 9:25
    • 已标记为答案 KeFang Chen 2009年11月25日 4:11
    2009年11月19日 8:45
  • 使用事件和事件响应的执行线程是两码事。
    事件的响应代码是在触发事件的线程中执行的,你自己定义的事件应该是你在后台线程中触发的。
    因此,要使响应在窗体线程中执行,仍然需要使用Invoke。
    2009年11月22日 13:30
  • 谢谢!先学习下。


    da jia hao!
    2009年11月24日 9:31