C# – How to update textbox on GUI from another thread

cmultithreadinguser interfacewinforms

I'm new with C# and I'm trying to make a simple client server chat application.

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".

Here the code of my Windows form:

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".

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:

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

Best Answer

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.

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 passing for more details.
  • The difference between Invoke and BeginInvoke is that BeginInvoke won't wait for the delegate to be called on the UI thread before it continues - so AddUser may return before the textbox has actually been updated. If you don't want that asynchronous behaviour, use Invoke.
  • In many samples (including some of mine!) you'll find people using Control.InvokeRequired to see whether they need to call Invoke/BeginInvoke. This is actually overkill in most cases - there's no real harm in calling Invoke/BeginInvoke even if you don't need to, and often the handler will only ever 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.

For more information on this and other threading topics, see my threading tutorial or Joe Albahari's one.