tags:

views:

3977

answers:

4

Hi, im new with c# and i am trying to make simple client server chat application.

Here is my problem...
I have RichTextBox on my client windows form and i am trying to update that control from server which is another class. When i try to do it i get error: "Cross-thread operation not valid: Control textBox1 accessed from a thread other than the thread it was created on".

Here is my code:
Windows Form:
private Topic topic;
public RichTextBox textbox1;
bool check = topic.addUser(textBoxNickname.Text, ref textbox1, ref listitems);

Topic class:
public class Topic : MarshalByRefObject
{
...
public bool addUser(string user, ref RichTextBox textBox1, ref List 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 to update textbox control from another thread?

thanks.

+4  A: 

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.

Jon Skeet
already posted an eg :)
amazedsaint
I prefer mine :)
Jon Skeet
so do I, but you don't need the points! ;)
Mitch Wheat
Yea, I know. When John Skeet write an example, the language will self adjust to accommodate that.
amazedsaint
+6  A: 

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(new UpdateTextCallback(this.UpdateText), 
            new object[]{”Text generated on non-UI thread.”});
amazedsaint
A: 

Thanks for fast reply guys.
I'll check your articles Jon.

So far ive tryed wiht your expample Jon and now i get new exception...
SerializationException: Type Topic in Assembly is not marked as serializable.

Ive also tryed to mark Topic class as Serializable but its not helping.

[Serializable]
public class Topic : MarshalByRefObject

Any idea ?

(It's better to edit your original question than to add a non-answer like this, btw.) What's trying to serialize anything? It sounds like this should really be an entirely separate question...
Jon Skeet
A: 

About answering my question ... Sorry, wasn't registered user at time i've made that post and now i don't have option to edit post. I'll keep that in mind next time i plan to type something. Because i don't see any other option right now ill answer again and try to explain what im trying to do.

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