Здравствуйте. Недавно начал изучать C#. До этого работал в Delphi и в Qt (C++). Во время тренировки столкнулся с большой проблемой, которая может показаться очень глупой.
Это очень длинная история, я постараюсь описать всё кратко и точно.
Рассматриваем три объекта: 1) TextBox; 2) МойКласс с таймером внутри; 3) форму главного окна, которая их инкапсулирует.
МойКласс запускает таймер, который генерирует событие UpdateDuration с интервалом примерно два раза в секунду.
// Обработчик события таймера
private void TickerCheck(Object source, System.Timers.ElapsedEventArgs e)
{
if (Animation) { Invalidate(); }
if (Field.GameIsRunning) {
if (Updater == TimerUpdateTicks) {
Updater = 0;
GameDuration = Field.RunningGameDuration;
OnUpdateDuration();
}
++Updater;
}
}
// внутренний обработчик события UpdateDuration
protected void OnUpdateDuration()
{
UpdateDurationEventArgs ud = new UpdateDurationEventArgs();
EventHandler<UpdateDurationEventArgs> h = UpdateDuration;
if (h != null) {
ud.Duration = GameDuration;
h(this, ud);
}
System.Diagnostics.Debug.WriteLine("Посылка отправлена: " + ud.Duration.ToString());
}
Подписанты события UpdateDuration получают аргумент, содержащий внутри TimeSpan
public class UpdateDurationEventArgs : EventArgs
{
private TimeSpan duration;
public UpdateDurationEventArgs() { duration = TimeSpan.Zero; }
public TimeSpan Duration {
get { return duration; }
set { duration = value; }
}
}
Я собирался использовать этот TimeSpan для обновления текста в TextBox. Этот TextBox выглядит вот так: Думаю, вам уже понятно, что я хочу сделать :) В нём должно обновляться текущее время игры. Для пользователя он играет роль секундомера.
Подписантом события UpdateDuration является форма главного окна. Она принимает событие и должна (по задумке) обновлять текст внутри TextBox
void UpdateTimer(object sender, UpdateDurationEventArgs ud)
{
System.Diagnostics.Debug.WriteLine("Посылка получена: " + ud.Duration.ToString());
uint t_Seconds = (uint)ud.Duration.TotalSeconds,
t_Minutes = t_Seconds/60,
t_Hours = t_Minutes/60;
t_Seconds = t_Seconds%60;
t_Minutes = t_Minutes%60;
t_Hours = t_Hours %100;
string newText = t_Hours.ToString("D2") + ':' + t_Minutes.ToString("D2") + ':' + t_Seconds.ToString("D2");
textBox_timer.Text = newText; // вот здесь она должна обновлять его
System.Diagnostics.Debug.WriteLine("Посылка распакована: " + ud.Duration.ToString());
}
Но! Не тут-то было. Кто-нибудь уже видит ошибку? Нет? Я не вижу :Т
Когда я запускал таймер, TextBox всегда оставался в нулях. Тогда я создал кнопку и попробовал запустить обработчик события вручную:
void Button_Rec_Click(object sender, System.EventArgs e)
{
UpdateDurationEventArgs ud = new UpdateDurationEventArgs();
ud.Duration = System.TimeSpan.FromSeconds(new System.Random().Next(8000));
UpdateTimer(this, ud);
}
И вот, по кнопке всё прекрасно работает: в TextBox рандомно выставляется разное время. До этого я ещё не пробовал ловить исключения в C#. Порылся в MSDN и сделал так:
void UpdateTimer(object sender, UpdateDurationEventArgs ud)
{
System.Diagnostics.Debug.WriteLine("Посылка получена: " + ud.Duration.ToString());
uint t_Seconds = (uint)ud.Duration.TotalSeconds,
t_Minutes = t_Seconds/60,
t_Hours = t_Minutes/60;
t_Seconds = t_Seconds%60;
t_Minutes = t_Minutes%60;
t_Hours = t_Hours %100;
string newText = t_Hours.ToString("D2") + ':' + t_Minutes.ToString("D2") + ':' + t_Seconds.ToString("D2");
try { // добавил блок try catch
textBox_timer.ResetText(); // и подставил строчки для дебага
System.Diagnostics.Debug.WriteLine(" resetText");
textBox_timer.Text = newText;
System.Diagnostics.Debug.WriteLine(" newText");
} catch (System.Exception) {
System.Diagnostics.Debug.WriteLine("Звездец, граждане!");
}
System.Diagnostics.Debug.WriteLine("Посылка распакована: " + ud.Duration.ToString());
}
Что же я увидел?
Звездец Фрустрацию в чистом виде. Посылка отправляется (генерируется событие, см. код OnUpdateDuration), принимается подписантом, но выбрасывается исключение, причём даже при ресете текста. При этом, по той же кнопке, вручную, всё прекрасно работает:
Вы уже видите ошибку? Что я сделал не так? :Т
Я закликал эту бедную кнопку до смерти (более 10 кликов в секунду), но "звездец" в консоль так и не вывелся - обработка прекрасно проходит.
Тут вы вправе предположить, что МойКласс отправляет какой-то инвалидный TimeSpan. Смею заверить, что это совсем не так: по тому же таймеру, в дебажную консоль обработчик выводит время без проблем:
Интересный момент: я сделал так, чтобы при ручной остановке таймера МойКласс отправлял событие UpdateDuration ещё один раз, уже после остановки таймера, чтобы передать подписантам нулевой TimeSpan.Zero. Так я намеревался сбрасывать время на TextBox. Так вот, это завершающее событие принимается тоже безо всяких проблем.
Забавно, но если заменить текстбокс на лейбл, ситуация не меняется. И только если использовать таймер, напрямую принадлежащий форме, всё хорошо работает. Но разве таймер, работающий в другом классе, нарушает какие-то принципы ООП? Ведь объекты МойКласс, форма и TextBox находятся все в одном главном потоке! Я в недоумении.
Программа делает то, что написал программист, а не то что он хотел. Где же у меня ошибка? Я угробил вчера несколько часов на поиски решения, до рези в глазах. Даже мютекс в обработчик ставил, хотя он тут вроде и ни к чему. Помогите, пожалуйста.
WindowsXP, NetFramework 4, среда SharpDevelop.
Вы заработали достижение! Вы прочитали мой вопрос полностью! Спасибо :)
Вы не написали, каким именно из многочисленных таймеров, доступных в .NET и его фреймворках, вы пользуетесь. Некоторые из них доставляют сообщения в UI-потоке, некоторые — в фоновом потоке. (А некоторые по-разному, в зависимости от того, как вы их отконфигурировали.)
Судя по всему, вы выбрали таймер, который доставляет сообщения в фоновом потоке. При этом попытка обратиться к контролам из кода таймера приводит к исключению: из фоновых потоков работать с контролами нельзя.
Какие есть пути решения?
System.Windows.Forms.Timer
.Если по каким-то очень уважительным причинам вы не можете воспользоваться подходящим таймером («ну типа я попробовал, но чёта не работает кароч» не считается уважительной причиной), то вам придётся смаршаллировать вызов в UI-поток. Для Winforms это делается так:
if (textBox_timer.InvokeRequired)
{
textBox_timer.Invoke((MethodInvoker)(() => { textBox_timer.Text = что-то; }));
}
else
{
textBox_timer.Text = что-то;
}
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Хочу попробовать сделать календарь похожий внешне на гугловыйЕсли я все правильно понимаю, то необходимо использовать DataGridView и ,например,...
Достал ресурсы программы (редактировать) через ilasm, но вот проблема файлы resources бинарныЛадно установил telerik diasembler и достал resx и блин опять...