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

Posted

技术标签:

【中文标题】如何从另一个线程更新 GUI 上的文本框 [重复]【英文标题】:How to update textbox on GUI from another thread [duplicate] 【发布时间】:2010-11-11 07:20:43 【问题描述】:

我是 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<string> 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 的建议修改了代码,但现在我得到了这个异常,而不是跨线程异常……“SerializationException:Assembly 中的类型主题未标记为可序列化”

我会在下面发布我的整个代码,尽量保持简单。 欢迎任何建议。非常感谢。

服务器:

  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 窗体客户端:

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

【问题讨论】:

【参考方案1】:

您需要使用BackgroundWorkerControl.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 的真正含义 - 请参阅 my article on parameter passing 了解更多详情。 InvokeBeginInvoke 之间的区别在于BeginInvoke 在继续之前不会等待在 UI 线程上调用委托 - 所以AddUser 可能会在文本框真正具有之前返回 已更新。如果您不希望这种异步行为,请使用Invoke。 在许多样本(包括我的一些样本!)中,您会发现人们使用Control.InvokeRequired 来查看他们是否需要致电Invoke/BeginInvoke。在大多数情况下,这实际上是矫枉过正 - 即使您不需要,调用 Invoke/BeginInvoke 也没有真正的危害,而且处理程序通常无论如何都要从非 UI 线程调用。省略检查使代码更简单。 你也可以使用我之前提到的BackgroundWorker;这特别适合进度条等,但在这种情况下,保留当前模型可能同样容易。

有关此主题和其他线程主题的更多信息,请参阅 my threading tutorial 或 Joe Albahari's one。

【讨论】:

【参考方案2】:

使用调用方法

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

【讨论】:

以上是关于如何从另一个线程更新 GUI 上的文本框 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何从另一个线程更新 GUI?

如何从另一个线程更新 GUI?

使用不同的线程更新 GUI (WPF)

避免多次调用 Invoke 以从另一个线程更新 GUI

从另一个线程写入文本框? [复制]

如何从 Java 中的另一个线程更新 SWT GUI