Событие, переменная и изменение состояния

Главная мысль: событие запускает действие программы, переменная хранит текущее значение, а экран показывает результат.

Экран уже есть: что заставляет его работать?

В предыдущей теме появилась надпись 0 и две кнопки. Пользователь видит возможные действия, но расположение компонентов само по себе не описывает их поведение. Чтобы число менялось, программе нужно правило: когда запускать действие, какие данные использовать и что показать после выполнения.

От нажатия к новому значению на экране

В этой цепочке четыре разных роли. Событие сообщает о произошедшем действии. Обработчик выполняет связанные с ним команды. Состояние хранит актуальные данные. Отображение делает эти данные видимыми пользователю. Если пропустить одну часть, приложение может выглядеть готовым, но вести себя неправильно.

Событие и обработчик

Для нашего примера событие — нажатие кнопки. Обработчик — правило, выполняемое при этом событии. Его можно прочитать словами: «Когда нажали “Добавить”, увеличить число и обновить надпись».

Определение обработчика не означает, что команды уже выполнились. Правило ждёт соответствующего события. При двух нажатиях оно выполняется дважды, каждый раз используя текущее значение.

Добавляем событие в App Inventor

Откройте «Блоки» кнопкой в правом верхнем углу редактора. Слева выберите ButtonAdd. Перетащите шаблон «когда ButtonAdd.Щелчок — делать» из открывшегося набора в свободную рабочую область.

Пустой обработчик события ButtonAdd.Щелчок

Рисунок 1. Обработчик уже добавлен в программу, но его область «делать» пока пуста. Нажатие ещё не содержит команд увеличения.

Переменная и начальное состояние

Переменная связывает имя с текущим значением. В примере имя — count, начальное значение — 0. Имя помогает программе и человеку обращаться к данным; само значение может изменяться.

Состояние счётчика сейчас описывается одним числом. После двух увеличений оно равно 2; после сброса — 0. Состояние более сложного приложения может включать несколько значений, но для первой модели этого не требуется.

Начальное состояние задаётся до обработки нажатий. В нашей учебной модели при новом запуске устанавливается 0. Это выбранное правило примера; долговременное сохранение пока не реализуется. Нельзя обещать восстановление прошлого числа только потому, что оно хранилось в переменной во время работы.

Состояние и надпись: два разных места в модели

Надпись LabelCount показывает значение, а count хранит его. Пока надпись правильно обновляется, они согласованы. Но программа может изменить переменную и забыть обновить экран — тогда пользователь увидит старое число.

Создаём глобальную переменную

В категории «Переменные» возьмите блок «инициализировать глобальную … в». Разместите его отдельно от обработчиков. Измените имя на count. В категории «Математика» возьмите числовой блок, задайте 0 и вставьте в гнездо начального значения.

Глобальная переменная count с начальным значением 0

Рисунок 2. Имя count и число 0 относятся к одному объявлению. Блок не нужно вставлять внутрь каждого нажатия: иначе возникнет риск сбрасывать состояние вместо его последовательного изменения.

Вычисление и присваивание

Для объяснения используем псевдокод, а не синтаксис конкретного языка:

count ← count + 1

Стрелка означает «записать новое значение». Сначала читается текущее значение справа, затем вычисляется сумма, после этого результат записывается в count. При исходном 0 получаем 1; при исходном 1 получаем 2.

Это не математическое равенство. Запись не утверждает, что число равно самому себе плюс единица. Она описывает изменение данных во времени. Именно поэтому важно проговаривать действие: «взять старое значение, прибавить единицу, сохранить новое».

В существующей вводной записи использовалось count = 0. Здесь специально применяем стрелку, чтобы не смешивать учебное присваивание с проверкой равенства или обозначениями разных языков.

Два обработчика счётчика

В начале работы:
    count ← 0
    текст LabelCount ← count

Когда нажали ButtonAdd:
    count ← count + 1
    текст LabelCount ← count

Когда нажали ButtonReset:
    count ← 0
    текст LabelCount ← count

Это смысловая модель для объяснения на доске. Она не является готовым текстом для вставки в Blocks Editor. В конструкторе те же роли будут представлены блоками.

Оба обработчика заканчиваются обновлением надписи. Увеличение зависит от прежнего значения; сброс устанавливает 0 независимо от того, сколько было раньше. Поэтому два сброса подряд не должны увеличивать число или давать отрицательное значение.

Разобранный пример. В начале count равно 0. «Добавить» записывает 1 и показывает 1. Второе «Добавить» записывает 2 и показывает 2. «Сбросить» записывает 0 и показывает 0. Следующее «Добавить» снова использует 0 и получает 1.

Собираем команды увеличения

В «Переменных» возьмите блок «присвоить … в» и вставьте в область «делать» обработчика ButtonAdd.Щелчок. В выпадающем списке переменной выберите global count.

В «Математике» возьмите блок сложения + и подключите его к значению присваивания. В первый аргумент вставьте блок «получить» из «Переменных» и выберите global count. Во второй аргумент вставьте числовой блок со значением 1.

Присваивание count результата count плюс 1

Рисунок 3. Событие уже изменяет состояние. Обновления текста надписи в этой промежуточной цепочке ещё нет.

Теперь откройте набор блоков LabelCount. Возьмите команду присваивания свойства, в выпадающем списке свойства выберите «Текст». Поставьте эту команду ниже изменения count в том же обработчике. В гнездо значения вставьте «получить global count».

Завершённый обработчик увеличения и обновления надписи

Рисунок 4. Сначала вычисляется и записывается новое число. Следующая команда берёт уже обновлённый count и передаёт его в LabelCount.Текст.

На промежуточных этапах редактор может показывать предупреждения об отсутствующем аргументе или ошибку, если переменная ещё не выбрана. Закончите соединение и проверьте индикаторы. Отсутствие предупреждений само по себе не доказывает правильность поведения.

Собираем команды сброса

Выберите ButtonReset слева и добавьте отдельный обработчик «когда ButtonReset.Щелчок — делать». Вставьте «присвоить global count в» с числом 0, затем «присвоить LabelCount.Текст в» со значением «получить global count».

Последнюю команду можно собрать заново или скопировать из обработчика увеличения: нажмите на зелёный блок правой кнопкой, выберите «Создать копию» и перетащите копию под присваивание нуля. Проверьте, что она действительно вошла в область «делать» нужного события.

Обработчик сброса состояния и обновления экрана

Рисунок 5. Сброс меняет и переменную, и отображение. Прежнее значение count не участвует в вычислении нового: оно заменяется нулём.

Полный набор блоков счётчика

Рисунок 6. Полный пример содержит одно объявление глобальной переменной и два обработчика. Оба заканчиваются обновлением надписи.

Трассировка: проследим каждый шаг

Трассировка — последовательная запись изменения данных при выполнении действий. Она помогает проверить алгоритм ещё до запуска приложения и найти момент расхождения с ожиданием.

Разобранная последовательность действий счётчика

Шаг Событие До действия После действия На экране
Начало Запуск — 0 0
1 Добавить 0 1 1
2 Добавить 1 2 2
3 Сбросить 2 0 0
4 Добавить 0 1 1

Важно объяснять не только последнюю строку. Правильный итог может получиться случайно, а промежуточные значения показывают понимание алгоритма. После каждой команды различайте новое состояние и видимый результат.

Типичные ошибки и проверка гипотез

Всегда появляется 1. Возможно, обработчик каждый раз задаёт готовую единицу вместо увеличения текущего значения. Первое нажатие не выявляет проблему: нужен повторный шаг. Это гипотеза; подтвердите её просмотром правила и наблюдением результата.

Внутри число растёт, экран остаётся прежним. Возможна пропущенная команда обновления надписи. Проверяем состояние и отображение отдельно. Повторные нажатия сами по себе не исправляют отсутствующую связь.

Сброс меняет только надпись. Если в переменной осталось 2, следующий «Добавить» может показать 3. Пользователь ожидал 1 после сброса. Проверка «Добавить — Добавить — Сбросить — Добавить» помогает обнаружить рассогласование.

После перезапуска снова 0. Для нашей модели это ожидаемое поведение. Сохранение между запусками является другим требованием и потребует отдельного механизма. Не объявляйте его ошибкой, если оно не входило в задание.

Во всех случаях сначала записываем ожидание, затем наблюдаем результат и проверяем конкретную гипотезу. Случайная перестановка команд не объясняет причину ошибки.

Вопросы и упражнения

  1. Назовите событие, обработчик, состояние и отображение в счётчике.
  2. Объясните, почему запись присваивания не является математическим равенством.
  3. Заполните трассировку для последовательности «Сбросить — Добавить — Добавить — Добавить».
  4. Проследите «Добавить — Сбросить — Сбросить — Добавить». Записывайте состояние и надпись после каждого шага.
  5. В обработчике сброса меняется только надпись. Предложите проверку, выявляющую ошибку, и объясните её.
  6. Какое дополнительное требование нужно сформулировать, чтобы после повторного запуска восстановилось прошлое значение?
  7. Что увидит пользователь, если переменная изменена, а надпись не обновлена?

Самопроверка

Для новой последовательности действий составьте таблицу «событие — значение до — значение после — надпись». Объясните каждый переход. Если состояние и надпись расходятся, найдите команду, которая должна была их согласовать.

Алгоритм можно проверить на бумаге, раздельно записывая переменную и текст надписи. При запуске приложения сравнивают эту модель с наблюдаемым поведением. В следующей теме «Проектирование визуального интерфейса мобильного приложения» работа продолжится с экранными формами. Самостоятельная реализация обработчиков в ближайшую практику не входит.

Связанные материалы

Интерфейс MIT App Inventor и подключение телефона; Собираем первый экран.