如何从另一个线程更新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);
您需要使用BackgroundWorker
或Control
。 Invoke
/ 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
真正含义 – 请参阅我关于参数传递的文章以获取更多细节。 -
Invoke
和BeginInvoke
之间的区别在于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.”});