Ласкаво просимо в захоплюючий світ UI-програмування! Вам доведеться трохи перевчитися: тут все не так, як в програмах командного рядка. Тут все фонові завдання, пов'язані з інтерфейсом, відбуваються не негайно, як тільки ви скомандували, а впереміш з вашою роботою. Все це відбувається в фіксованому потоці, так званому UI-потоці.
Що відбувається у вашому коді?
- Ви блокуєте UI-потік на півсекунди, потім встановлюєте текст кнопки. Встановлюється внутрішній прапор в графічній бібліотеці, який говорить «коли буде вільна хвилинка, треба оновити button».
- Ви негайно знову блокуєте UI-потік на півсекунди, і перевстановлюєте текст кнопки. Оскільки «вільна хвилинка» (вона ж «idle loop») так і не наступила, текст оновитися не встиг. Крім того, на час блокування нікому обробляти події від миші і клавіатури (бо UI-потік зайнятий sleep 'ом), так що програма виглядає зависла.
- Потім ви знову встановлюєте текст кнопки, це нічого не змінює, так як прапор, який вказує на необхідності оновлення, вже зведений.
- І так далі, 11 раз.
- В кінці-кінців, коли ваш цикл закінчився, UI-потік нарешті звільнився, і може отрисовать ваші зміни. Ви нарешті бачите 0.
У чому суть проблеми? Ви не повинні робити довгі операції в UI-потоці. Thread.sleep - одна з таких операцій, але багато ще і читають файли, вантажать дані з мережі або звертаються до бази даних. Ви маєте право завантажувати UI-потік роботою лише на кілька мілісекунд. На весь час довгої операції, що біжить в UI-потоці, додаток буде виглядати завислим, і обробники UI-подій викликатися не будуть. Така поведінка програми відразу перетворює її в студентську саморобку.
Оновлення UI, реакція на мишу, перерисовка контролів - все відбувається в UI-потоці, в той момент, коли цей потік нічим не зайнятий. Хороша програма з UI повинна бути подієво-орієнтованої, а не імперативної: ви повинні не самі рулити ходом програми, а лише реагувати на події короткими за часом обработчиками.
Що потрібно робити? Є два шляхи. Простий шлях, який цілком підходить для ваших цілей: чи не блокуєте UI-потік, а використовуєте таймер, який йде з вашої UI-бібліотекою. Він викличе ваш обробник в потрібний час, ви встановите там текст кнопки, і закінчите обробку. тим самим ви відпустіть UI-потік, щоб він зміг оновити UI.
Складний шлях, який застосовується, якщо вам дійсно потрібно виконати тривалу операцію. В цьому випадку ви створюєте окремий потік (thread), в якому і виконуєте потрібну операцію. Результати роботи ви в потрібний момент посилаєте в UI-потік, який повинен просто оновити UI. Зауважте, що в цьому випадку програмування стає набагато складніше, тому що вам доведеться піклуватися про синхронізацію, блокування і комунікації між потоками (просто глобальна змінна не котить). Але для вашого випадку це зайве, цілком вистачить простого таймера.