Как обновить текстовое поле на графическом интерфейсе из другого потока
Я новичок в С#, и я пытаюсь создать приложение для чата на клиентском сервере.
У меня есть RichTextBox в моей форме окон клиента, и я пытаюсь обновить этот элемент управления с сервера, который находится в другом классе. Когда я пытаюсь это сделать, я получаю сообщение об ошибке: "Работа с кросс-потоками недействительна: Control 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.
Я хочу, чтобы окна создавали клиентское приложение и консольное серверное приложение как отдельные .exe файлы. Здесь im пытается вызвать функцию сервера AddUser от клиента, и я хочу, чтобы функция AddUser обновила мой графический интерфейс. Ive модифицированный код, как вы предложили Jon, но теперь вместо исключения с перекрестными потоками у меня есть это исключение... "SerializationException: Type Topic в 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
Вам нужно либо использовать BackgroundWorker
, либо Control
. Invoke
/BeginInvoke
. Анонимные функции - анонимные методы (С# 2.0) или лямбда-выражения (С# 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
- см. мою статью о передаче параметров для более подробной информации.
- Разница между
Invoke
и BeginInvoke
заключается в том, что BeginInvoke
не будет ждать, пока делегат будет вызван в потоке пользовательского интерфейса до его продолжения - поэтому AddUser
может вернуться до фактического обновления текстового поля. Если вы не хотите этого асинхронного поведения, используйте Invoke
.
- Во многих образцах (включая некоторые из моих!) вы найдете людей, использующих
Control.InvokeRequired
, чтобы узнать, нужно ли им звонить Invoke
/BeginInvoke
. В большинстве случаев это на самом деле избыточно - нет никакого реального вреда при вызове Invoke
/BeginInvoke
, даже если вам это не нужно, и часто обработчик будет когда-либо вызываться из нити, отличной от UI. Опускание проверки делает код более простым.
- Вы также можете использовать
BackgroundWorker
, как я упоминал ранее; это особенно подходит для индикаторов выполнения и т.д., но в этом случае, возможно, так же легко сохранить текущую модель.
Для получения дополнительной информации об этой и других тематических разделах см. мой учебник по потокам или Джо Альбахари один.
Ответ 2
Использовать метод Invoke
// Updates the textbox text.
private void UpdateText(string text)
{
// Set the textbox text.
yourTextBox.Text = text;
}
Теперь создайте делегат, который имеет ту же подпись, что и ранее определенный метод:
public delegate void UpdateTextCallback(string text);
В вашем потоке вы можете вызвать метод Invoke на вашем TextBox, передать делегат для вызова, а также параметры.
yourTextBox.Invoke(new UpdateTextCallback(this.UpdateText),
new object[]{"Text generated on non-UI thread."});