关于c#:如何从另一个线程更新GUI上的文本框

How to update textbox on GUI from another thread

本问题已经有最佳答案,请猛点这里访问。

我是C的新手,我正在尝试制作一个简单的客户机-服务器聊天应用程序。

我的客户机Windows窗体上有RichTextBox,我正在尝试从另一个类中的服务器更新该控件。当我尝试这样做的时候,我得到了一个错误:"跨线程操作无效:从创建它的线程以外的线程访问控件textbox 1"。

这里是我的Windows窗体的代码:

1
2
3
private Topic topic;  
public RichTextBox textbox1;  
bool check = topic.addUser(textBoxNickname.Text, ref textbox1, ref listitems);

主题类:

1
2
3
4
5
6
7
8
9
public class Topic : MarshalByRefObject  
{  
    //Some code
 public  bool addUser(string user, ref RichTextBox textBox1, ref List<string> listBox1)  
 {  
     //here i am trying to update that control and where i get that exception  
     textBox1.Text +="Connected to server...
"
;  
}

那么怎么做呢?如何从其他线程更新文本框控件?

我正在尝试使用.NET远程处理创建一些基本的聊天客户端/服务器应用程序。我要将Windows窗体客户端应用程序和控制台服务器应用程序设置为单独的.exe文件。在这里,我试图从客户端调用服务器函数adduser,我想要adduser函数更新我的GUI。我按照您的建议修改了代码,但是现在我得到了这个异常,而不是跨线程异常……"序列化异常:程序集中的类型主题未标记为可序列化"。

我会把我的全部代码贴在下面,尽量保持简单。欢迎提出任何建议。非常感谢。

服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  namespace Test
{
    [Serializable]
    public class Topic : MarshalByRefObject
    {
        public bool AddUser(string user, RichTextBox textBox1, List<string> listBox1)
        {
            //Send to message only to the client connected
            MethodInvoker action = delegate { textBox1.Text +="Connected to server...
"
; };
            textBox1.BeginInvoke(action);
            //...
            return true;
        }

        public class TheServer
        {
            public static void Main()
            {

                int listeningChannel = 1099;

                BinaryServerFormatterSinkProvider srvFormatter = new BinaryServerFormatterSinkProvider();
                srvFormatter.TypeFilterLevel = TypeFilterLevel.Full;

                BinaryClientFormatterSinkProvider clntFormatter = new BinaryClientFormatterSinkProvider();

                IDictionary props = new Hashtable();
                props["port"] = listeningChannel;

                HttpChannel channel = new HttpChannel(props, clntFormatter, srvFormatter);
                // Register the channel with the runtime            
                ChannelServices.RegisterChannel(channel, false);
                // Expose the Calculator Object from this Server
                RemotingConfiguration.RegisterWellKnownServiceType(typeof(Topic),
                                                   "Topic.soap",
                                                    WellKnownObjectMode.Singleton);
                // Keep the Server running until the user presses enter
                Console.WriteLine("The Topic Server is up and running on port {0}", listeningChannel);
                Console.WriteLine("Press enter to stop the server...");
                Console.ReadLine();
            }
        }
    }

}

Windows窗体客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Create and register a channel to communicate to the server
        // The Client will use the port passed in as args to listen for callbacks

        BinaryServerFormatterSinkProvider srvFormatter = new BinaryServerFormatterSinkProvider();
        srvFormatter.TypeFilterLevel = TypeFilterLevel.Full;
        BinaryClientFormatterSinkProvider clntFormatter = new BinaryClientFormatterSinkProvider();
        IDictionary props = new Hashtable();
        props["port"] = 0;

        channel = new HttpChannel(props, clntFormatter, srvFormatter);
        //channel = new HttpChannel(listeningChannel);

        ChannelServices.RegisterChannel(channel, false);
        // Create an instance on the remote server and call a method remotely
        topic = (Topic)Activator.GetObject(typeof(Topic), // type to create
       "http://localhost:1099/Topic.soap" // URI
        );


        private Topic topic;
        public RichTextBox textbox1;
        bool check = topic.addUser(textBoxNickname.Text,textBox1, listitems);


您需要使用BackgroundWorkerControl.InvokeBeginInvoke。匿名函数-无论是匿名方法(C 2.0)还是lambda表达式(C 3.0),都比以前更容易实现。

在您的情况下,您可以将代码更改为:

1
2
3
4
5
6
7
public bool AddUser(string user, RichTextBox textBox1, List listBox1)
{
    MethodInvoker action = delegate
         { textBox1.Text +="Connected to server...
"
; };
    textBox1.BeginInvoke(action);
}

需要注意的几点:

  • 为了符合.NET惯例,这应该称为AddUser
  • 您不需要通过引用传递文本框或列表框。我怀疑你不太明白ref的真正含义——更多细节请参阅我关于参数传递的文章。
  • InvokeBeginInvoke的区别在于BeginInvoke在继续之前不会等待在UI线程上调用委托,因此AddUser可能会在实际更新文本框之前返回。如果您不希望使用异步行为,请使用Invoke
  • 在许多样品中(包括我的一些样品!)你会发现人们使用Control.InvokeRequired来查看是否需要呼叫InvokeBeginInvoke。在大多数情况下,这实际上是杀伤力过大的——即使您不需要调用Invoke/BeginInvoke,也不会造成真正的伤害,而且通常只从非UI线程调用处理程序。省略检查使代码更简单。
  • 您也可以像我前面提到的那样使用BackgroundWorker;这特别适合进度条等,但在这种情况下,保持当前模型可能同样容易。

有关此主题和其他线程主题的详细信息,请参阅我的线程教程或JoeAlbahari的教程。


使用调用方法

1
2
3
4
5
6
// Updates the textbox text.
private void UpdateText(string text)
{
  // Set the textbox text.
  yourTextBox.Text = text;
}

现在,创建一个与先前定义的方法具有相同签名的委托:

1
public delegate void UpdateTextCallback(string text);

在线程中,可以在rtextbox上调用invoke方法,传递要调用的委托以及参数。

1
2
yourTextBox.Invoke(new UpdateTextCallback(this.UpdateText),
            new object[]{"Text generated on non-UI thread."});