#c# #winforms
#c# #winforms
Вопрос:
У меня есть две формы: основная форма и та, которая появляется в виде модального диалогового окна. Из процесса, созданного в основной форме, я хочу динамически обновлять текст в модальном диалоговом окне. Вот что у меня есть:
В основной форме я делаю это:
// show the wait modal
var modal = new WaitDialog { Owner = this };
// thread the packaging
var thread = new Thread(() => Packager.PackageUpdates(clients, version, modal));
thread.Start();
// hopefully it worked ...
if (modal.ShowDialog() != DialogResult.OK)
{
throw new Exception("Something failed, miserably.");
}
PackageUpdates
Метод принимает модальный диалог и выполняет следующее:
// quick update and sleep for a sec ...
modal.SetWaitLabelText("Downloading update package...");
Thread.Sleep(2000);
modal.SetWaitLabelText("Re-packaging update...");
Чтобы быть потокобезопасным, я делаю это в модальном диалоговом окне:
public void SetWaitLabelText(string text)
{
if (lblWaitMessage.InvokeRequired)
{
Invoke(new Action<string>(SetWaitLabelText), text);
}
else
{
lblWaitMessage.Text = text;
}
}
Все работает отлично… большую часть времени. Каждые три или четыре раза, когда появляется модальное сообщение, я получаю исключение lblWaitMessage.Text = text;
, и оно не вызывает команду.
Я что-то упускаю в этой настройке?
Комментарии:
1. Вы запускаете поток слишком рано. Дождитесь модального. Событие загрузки.
2. Как сказал Ханс Пассант, я не уверен, что поток. Start() можно безопасно использовать перед модальным. ShowDialog() . Что именно является исключением?
3. Какое именно исключение вы получаете. Вполне вероятно, что, как сказал Ганс, поток выполняет первый вызов
SetWaitLabelText
до того, как форма будет полностью сконструирована.4. С тех пор, как я опубликовал это, я больше не получал исключения. Я продолжу пытаться и опубликую, что такое исключение, а также попробую решение Ганса.
Ответ №1:
Как указал @Hans Passant, вам следует дождаться модального.Событие загрузки. Одним из хороших вариантов является использование ManualResetEvent для информирования вашего потока о необходимости подождать, пока это не произойдет.
Метод WaitOne блокирует поток до тех пор, пока не будет вызван метод Set . Вот очень простая настройка, которая должна сработать.
public partial class Form1 : Form
{
ManualResetEvent m_ResetEvent;
public Form1()
{
InitializeComponent();
m_ResetEvent = new ManualResetEvent(false);
}
private void button1_Click(object sender, EventArgs e)
{
Dialog d = new Dialog { Owner = this, ResetEvent = m_ResetEvent };
var thread = new Thread(new ParameterizedThreadStart(DoSomething));
thread.Start(d);
if (d.ShowDialog() != System.Windows.Forms.DialogResult.OK)
{
throw new Exception("Something terrible happened");
}
}
private void DoSomething(object modal)
{
Dialog d = (Dialog)modal;
// Block the thread!
m_ResetEvent.WaitOne();
for (int i = 0; i < 1000; i )
{
d.SetWaitLabelText(i.ToString());
Thread.Sleep(1000);
}
}
}
И вот модальная форма
public partial class Dialog : Form
{
public Form Owner { get; set; }
public ManualResetEvent ResetEvent { get; set; }
public Dialog()
{
InitializeComponent();
}
public void SetWaitLabelText(string text)
{
if (label1.InvokeRequired)
{
Invoke(new Action<string>(SetWaitLabelText), text);
}
else
{
label1.Text = text;
}
}
private void Dialog_Load(object sender, EventArgs e)
{
// Set the event, thus unblocking the other thread
ResetEvent.Set();
}
}
Ответ №2:
Я думаю, вам следует переписать код, чтобы разрешить поток.Start() не вызывается перед модальным.ShowDialog() .
В качестве обходного пути вы можете попробовать следующее:
public void SetWaitLabelText(string text) {
Invoke(new Action<string>(SetWaitLabelText2), text);
}
void SetWaitLabelText2(string text) {
lblWaitMessage.Text = text;
}
Первый метод всегда использует Invoke , независимо от значения InvokeRequired . Второй метод действительно делает это. Этот шаблон можно использовать, когда вы всегда вызываете функцию из другого потока.
Комментарии:
1. Это грязное решение … 🙂