如何从另一个线程更新GUI上的文本框

我是C#的新手,我正在尝试创建一个简单的客户端服务器聊天应用程序。

我在我的客户端窗体上有RichTextBox,我正在尝试从另一个类的服务器更新该控件。 当我尝试这样做时,我得到错误: “跨线程操作无效:控制textBox1从其创建的线程以外的线程访问”。

这是我的Windows窗体的代码:

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

主题类:

 public class Topic : MarshalByRefObject { //Some code public bool addUser(string user, ref RichTextBox textBox1, ref List listBox1) { //here i am trying to update that control and where i get that exception textBox1.Text += "Connected to server... \n"; } 

那怎么办呢? 如何从另一个线程更新文本框控件?


我正在尝试使用.net远程处理来创建一些基本的聊天客户端/服务器应用程序。 我想将Windows窗体客户端应用程序和控制台服务器应用程序作为单独的.exe文件。 在这里我试图从客户端调用服务器函数AddUser,我想要AddUser函数更新我的GUI。 我已经修改了代码,因为你建议使用Jon但是现在没有跨线程exception我得到了这个exception…… “SerializationException:Assembly中的类型主题没有被标记为可序列化”

生病了我的整个代码,尽量保持简单。
任何建议都是受欢迎的。 非常感谢。

服务器:

  namespace Test { [Serializable] public class Topic : MarshalByRefObject { public bool AddUser(string user, RichTextBox textBox1, List listBox1) { //Send to message only to the client connected MethodInvoker action = delegate { textBox1.Text += "Connected to server... \n"; }; 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窗体客户端:

 // 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); 

您需要使用BackgroundWorkerControlInvoke / BeginInvoke 。 匿名函数 – 匿名方法(C#2.0)或lambda表达式(C#3.0)使这比以前更容易。

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

 public bool AddUser(string user, RichTextBox textBox1, List listBox1) { MethodInvoker action = delegate { textBox1.Text += "Connected to server... \n"; }; textBox1.BeginInvoke(action); } 

有几点需要注意:

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

有关此主题和其他线程主题的更多信息,请参阅我的线程教程或Joe Albahari的主题。

使用Invoke方法

 // Updates the textbox text. private void UpdateText(string text) { // Set the textbox text. yourTextBox.Text = text; } 

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

 public delegate void UpdateTextCallback(string text); 

在您的线程中,您可以在yourTextBox上调用Invoke方法,将委托传递给调用,以及参数。

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