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

10 c# user-interface multithreading winforms

我是C#的新手,我正在尝试创建一个简单的客户端服务器聊天应用程序.

我在我的客户端窗体上有RichTextBox,我试图从另一个类的服务器更新该控件.当我尝试这样做时,我得到错误:"跨线程操作无效:控制textBox1从其创建的线程以外的线程访问".

这里是我的Windows窗体的代码:

private Topic topic;  
public RichTextBox textbox1;  
bool check = topic.addUser(textBoxNickname.Text, ref textbox1, ref listitems);
Run Code Online (Sandbox Code Playgroud)

主题类:

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";  
}
Run Code Online (Sandbox Code Playgroud)

那怎么办呢?如何从另一个线程更新文本框控件?


我正在尝试使用.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();
            }
        }
    }

}  
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 32

你需要使用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);
}
Run Code Online (Sandbox Code Playgroud)

有几点需要注意:

  • 为了符合.NET约定,应该调用它 AddUser
  • 您无需通过引用传递文本框或列表框.我怀疑你不太明白究竟是什么ref意思 - 请参阅我关于参数传递的文章以获取更多细节.
  • Invoke和之间的区别在于BeginInvokeBeginInvoke不会等待在UI线程继续之前调用委托 - 因此AddUser可能在文本框实际更新之前返回.如果您不想要这种异步行为,请使用Invoke.
  • 在许多样本中(包括我的一些样本!)你会发现有人Control.InvokeRequired在看他们是否需要调用Invoke/ BeginInvoke.在大多数情况下,这实际上是矫枉过正 - 即使你不需要,调用/ 也没有真正的危害,并且通常只会从非UI线程调用处理程序.省略检查使代码更简单.InvokeBeginInvoke
  • 您也可以BackgroundWorker像我之前提到的那样使用; 这特别适用于进度条等,但在这种情况下,保持当前模型可能同样容易.

有关此主题和其他线程主题的更多信息,请参阅我的线程教程Joe Albahari的主题.


ama*_*int 10

使用Invoke方法

// Updates the textbox text.
private void UpdateText(string text)
{
  // Set the textbox text.
  yourTextBox.Text = text;
}
Run Code Online (Sandbox Code Playgroud)

现在,创建一个与先前定义的方法具有相同签名的委托:

public delegate void UpdateTextCallback(string text);
Run Code Online (Sandbox Code Playgroud)

在您的线程中,您可以在yourTextBox上调用Invoke方法,将委托传递给调用,以及参数.

yourTextBox.Invoke(new UpdateTextCallback(this.UpdateText), 
            new object[]{”Text generated on non-UI thread.”});
Run Code Online (Sandbox Code Playgroud)