如何从另一个线程更新GUI上的文本框
如何从另一个线程更新GUI上的文本框
这个问题已经有答案了:
我是C#的新手,我正在努力制作一个简单的客户端服务器聊天应用程序。
我在客户端窗体上有一个RichTextBox控件,我正在尝试从另一个类中的服务器更新该控件。 当我尝试执行此操作时,我会收到错误:“跨线程操作无效:从创建它的线程以外的线程访问Control textBox1。”
这是我Windows窗体的代码:
private Topic topic; public RichTextBox textbox1; bool check = topic.addUser(textBoxNickname.Text, ref textbox1, ref listitems);
Topic类:
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 remoting制作一些基本的聊天客户端/服务器应用程序。
我想将Windows窗体客户端应用程序和控制台服务器应用程序作为单独的.exe文件。在这里,我正在尝试从客户端调用服务器函数AddUser,并且我希望AddUser函数更新我的GUI。我按照您建议的Jon进行了修改,但现在我没有跨线程异常,我得到了这个异常...“SerializationException:程序集中的Topic类型未标记为可序列化”。
我将我的整个代码张贴在下面,将尽可能简单。
任何建议都受欢迎。非常感谢。
服务器:
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);
admin 更改状态以发布 2023年5月24日
使用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.”});
您需要使用 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的线程教程。