C# 如何从另一个线程更新 GUI 上的文本框
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1136399/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to update textbox on GUI from another thread
提问by
I'm new with C# and I'm trying to make a simple client server chat application.
我是 C# 新手,我正在尝试制作一个简单的客户端服务器聊天应用程序。
I have RichTextBox on my client windows form and I am trying to update that control from server which is in another class. When I try to do it I get the error: "Cross-thread operation not valid: Control textBox1 accessed from a thread other than the thread it was created on".
我的客户端 Windows 窗体上有 RichTextBox,我正在尝试从另一个类中的服务器更新该控件。当我尝试这样做时,我收到错误消息:“跨线程操作无效:从创建它的线程以外的线程访问控制 textBox1”。
Here the code of my Windows form:
这是我的 Windows 窗体的代码:
private Topic topic;
public RichTextBox textbox1;
bool check = topic.addUser(textBoxNickname.Text, ref textbox1, ref listitems);
Topic class:
主题类:
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... \n";
}
So how to do that? How can I update the textbox control from another thread?
那么怎么做呢?如何从另一个线程更新文本框控件?
I'm trying to make some basic chat client/server application using .net remoting. I want to make windows form client application and console server application as separate .exe files. Here im trying to call server function AddUser from client and i want to AddUser function update my GUI. Ive modified code as you suggested Jon but now instead of cross-thread exception i've got this exception ... "SerializationException: Type Topic in Assembly is not marked as serializable".
我正在尝试使用 .net 远程处理制作一些基本的聊天客户端/服务器应用程序。我想将 Windows 窗体客户端应用程序和控制台服务器应用程序制作为单独的 .exe 文件。我在这里尝试从客户端调用服务器函数 AddUser,我想 AddUser 函数更新我的 GUI。我已经按照你的建议修改了 Jon 的代码,但现在我得到了这个异常,而不是跨线程异常...... “SerializationException: Type Topic in Assembly 未标记为可序列化”。
Ill post my whole code bellow, will try to keep it simple as possible.
Any suggestion is welcome. Many thanks.
我将在下面发布我的整个代码,将尽量保持简单。
欢迎任何建议。非常感谢。
Server:
服务器:
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... \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 form client:
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);
回答by Jon Skeet
You need to either use BackgroundWorker
, or Control
.Invoke
/BeginInvoke
. Anonymous functions - either anonymous methods (C# 2.0) or lambda expressions (C# 3.0) make this easier than it was before.
您需要使用BackgroundWorker
, 或Control
。Invoke
/ BeginInvoke
. 匿名函数 - 匿名方法 (C# 2.0) 或 lambda 表达式 (C# 3.0) 使这比以前更容易。
In your case, you can change your code to:
在您的情况下,您可以将代码更改为:
public bool AddUser(string user, RichTextBox textBox1, List listBox1)
{
MethodInvoker action = delegate
{ textBox1.Text += "Connected to server... \n"; };
textBox1.BeginInvoke(action);
}
A few things to note:
需要注意的几点:
- To conform with .NET conventions, this should be called
AddUser
- You don't need to pass the textbox or listbox by reference. I suspect you don't quite understand what
ref
really means - see my article on parameter passingfor more details. - The difference between
Invoke
andBeginInvoke
is thatBeginInvoke
won't wait for the delegate to be called on the UI thread before it continues - soAddUser
may return before the textbox has actuallybeen updated. If you don't want that asynchronous behaviour, useInvoke
. - In many samples (including some of mine!) you'll find people using
Control.InvokeRequired
to see whether they need to callInvoke
/BeginInvoke
. This is actually overkill in most cases - there's no real harmin callingInvoke
/BeginInvoke
even if you don't need to, and often the handler will onlyever be called from a non-UI thread anyway. Omitting the check makes the code simpler. - You can also use
BackgroundWorker
as I mentioned before; this is particularly suited to progress bars etc, but in this case it's probably just as easy to keep your current model.
- 为了符合 .NET 约定,这应该被称为
AddUser
- 您不需要通过引用传递文本框或列表框。我怀疑你不太明白什么是
ref
真正的意思 -有关更多详细信息,请参阅我关于参数传递的文章。 - 之间的区别
Invoke
和BeginInvoke
是BeginInvoke
不会等待UI线程上调用它继续之前的委托-这样AddUser
的文本框以前可能会返回实际被更新。如果您不想要这种异步行为,请使用Invoke
. - 在许多示例中(包括我的一些示例!),您会发现人们使用
Control.InvokeRequired
来查看他们是否需要调用Invoke
/BeginInvoke
。在大多数情况下,这实际上是矫枉过正 -调用/没有真正的危害,即使您不需要,而且通常处理程序只会从非 UI 线程中调用。省略检查使代码更简单。Invoke
BeginInvoke
- 你也可以
BackgroundWorker
像我之前提到的那样使用;这特别适合进度条等,但在这种情况下,保留当前模型可能同样容易。
For more information on this and other threading topics, see my threading tutorialor Joe Albahari's one.
有关此主题和其他线程主题的更多信息,请参阅我的线程教程或Joe Albahari 的教程。
回答by amazedsaint
Use Invoke method
使用调用方法
// Updates the textbox text.
private void UpdateText(string text)
{
// Set the textbox text.
yourTextBox.Text = text;
}
Now, create a delegate that has the same signature as the method that was previously defined:
现在,创建一个与之前定义的方法具有相同签名的委托:
public delegate void UpdateTextCallback(string text);
In your thread, you can call the Invoke method on yourTextBox, passing the delegate to call, as well as the parameters.
在您的线程中,您可以在 yourTextBox 上调用 Invoke 方法,传递要调用的委托以及参数。
yourTextBox.Invoke(new UpdateTextCallback(this.UpdateText),
new object[]{”Text generated on non-UI thread.”});