Winforms имеет модель «собственный поток».
Что это значит?
Эта модель не позволяет получить доступ к компоненту пользовательского интерфейса из другого потока, а не из того, который его создал.
Почему?
Поскольку компоненты графического интерфейса не являются поточно-ориентированными.и не должно быть, так как они будут намного медленнее.Итак, WinForms выдает подобное исключение, когда вы пытаетесь получить доступ к компоненту графического интерфейса из другого потока, а не из принадлежащего потока .
Но почему это происходит, вы?
Поскольку System.Timers.Timer
выполняет свой обратный вызов в своем собственном потоке, который не является потоком, создавшим GUI (основной поток приложения).Таким образом, вы не можете получить доступ из его обратного вызова к любому компоненту графического интерфейса.
Какое решение?
Вы можете получить доступ к компоненту графического интерфейса из другого потока с помощью инструмента под названием Диспетчер .Но если вам нужен простой таймер, у вас есть более удачное решение.
Просто используйте System.Windows.Forms.Timer
вместо System.Timers.Timer
.Этот таймер специфичен для WinForms и обрабатывает всю черную работу с диспетчером за вас.(Примечание: WPF имеет System.Windows.Threading.DispatcherTimer
для той же цели).
Но есть один подводный камень: у этого таймера нет свойства AutoReset
.Итак, вы должны удалить событие вручную после одного запуска, например:
private void T_Elapsed(object sender, EventArgs e)
{
myFunction();
t.Stop();
this.Close();
}
Поскольку вы закрываете окно, это на самом деле не нужно, но для безопасности ...
Кроме того, обратите внимание, что вам не нужны оба Stop()
и Enabled = false
вместе, они идентичны (лично я предпочитаю Stop()
, я думаю, что это более читабельно).
В вашем примере (с * 1042)*) вам вообще не нужно было Stop()
- AutoReset = false
запускать обратный вызов только один раз.
Редактировать:
Хотя в вашем случае это не требуется, я добавляюобъяснение о том, «как использовать диспетчер».
Каждая форма WinForms имеет Dispatcher и некоторые методы, связанные с ним.Наиболее важными являются Invoke()
и BeginInvoke()
(две перегруженные версии, я говорю о первой, которая занимает System.Delegate
).
Эти методы позволяют вам получить доступ к двум компонентам графического интерфейса из не принадлежащего потока,только из метода, переданного в качестве параметра (в большинстве случаев вы должны сначала привести его к System.Delegate
).
Разница в том, что Invoke()
возвращается только после вызова метода, тогда как BeginInvoke()
асинхронный;он возвращается немедленно.
Итак, вы можете переписать свой код следующим образом:
private System.Timers.Timer t = new System.Timers.Timer();
public Form1()
{
InitializeComponent();
t.Elapsed += T_Elapsed;
t.Interval = int.Parse(textBox1.Text);
t.AutoReset = false;
t.Start();
}
private void T_Elapsed(object sender, EventArgs e)
{
this.Invoke((Action)(() => // You can use `BeginInvoke()` as well
{
this.Close();
}));
// Or
// this.Invoke(new Action(() => // You can use `BeginInvoke()` as well
// {
// this.Close();
// }));
}
Примечание: Никогда не помещайте долгосрочные задачи в Invoke()
или BeginInvoke()
!поскольку они выполняются в собственном потоке, а не в вызываемом потоке, они замораживают графический интерфейс - гораздо проще вообще не использовать потоки ... Поместите вычисления в поток и вызывайте эти методы только для обновленияGUI!
Edit 2:
После того, как я увидел, что вы сделали с моим ответом, я был шокирован ... Кажется, вы даже не читали его!Вы выбрали оба решения: таймер winforms (хороший) и диспетчер (в нашем случае, bas)!упростить вам Tick
событие так:
private void T_Elapsed(object sender, EventArgs e)
{
myFunction();
Close();
}
Кроме того, в вашем myFunction()
вы показываете свою вторую форму в модальной форме .Это говорит о том, что метод не вернется после закрытия второй формы.См. В чем разница между функциями Show (), ShowDialog () и Application.Run ()? для получения более подробной информации.Я думаю, что вы хотите показать свою вторую форму немодальной.