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
, orControl
.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:
A few things to note:
AddUser
ref
really means - see my article on parameter passing for more details.Invoke
andBeginInvoke
is thatBeginInvoke
won't wait for the delegate to be called on the UI thread before it continues - soAddUser
may return before the textbox has actually been updated. If you don't want that asynchronous behaviour, useInvoke
.Control.InvokeRequired
to see whether they need to callInvoke
/BeginInvoke
. This is actually overkill in most cases - there's no real harm in callingInvoke
/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.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.