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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 09:05:38  来源:igfitidea点击:

How to update textbox on GUI from another thread

c#multithreadingwinformsuser-interface

提问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, 或ControlInvoke/ 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 refreally means - see my article on parameter passingfor more details.
  • The difference between Invokeand BeginInvokeis that BeginInvokewon't wait for the delegate to be called on the UI thread before it continues - so AddUsermay return before the textbox has actuallybeen updated. If you don't want that asynchronous behaviour, use Invoke.
  • In many samples (including some of mine!) you'll find people using Control.InvokeRequiredto see whether they need to call Invoke/BeginInvoke. This is actually overkill in most cases - there's no real harmin calling Invoke/BeginInvokeeven 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 BackgroundWorkeras 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真正的意思 -有关更多详细信息,请参阅我关于参数传递的文章
  • 之间的区别InvokeBeginInvokeBeginInvoke不会等待UI线程上调用它继续之前的委托-这样AddUser的文本框以前可能会返回实际被更新。如果您不想要这种异步行为,请使用Invoke.
  • 在许多示例中(包括我的一些示例!),您会发现人们使用Control.InvokeRequired来查看他们是否需要调用Invoke/ BeginInvoke。在大多数情况下,这实际上是矫枉过正 -调用/没有真正的危害,即使您不需要,而且通常处理程序只会从非 UI 线程中调用。省略检查使代码更简单。InvokeBeginInvoke
  • 你也可以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.”});