</>
Тема 6 из 6

Холст, таймеры и калькулятор

Холст Canvas — виджет, на котором рисуют фигуры: линии, прямоугольники, круги, текст. А функция after позволяет делать что-то по таймеру — так получаются анимации и часы.

Рисование на Canvas

canvas = tk.Canvas(root, width=400, height=300, bg="black")
canvas.pack()

canvas.create_line(0, 0, 400, 300, fill="white", width=2)
box = canvas.create_rectangle(50, 50, 150, 120, fill="green", outline="")
ball = canvas.create_oval(200, 100, 240, 140, fill="yellow")   # круг вписан в квадрат
canvas.create_text(200, 20, text="Счёт: 0", fill="white", font=("Segoe UI", 14))

Каждая create_* возвращает номер фигуры. По нему фигуру меняют потом:

canvas.move(ball, 5, 0)                    # сдвинуть на (dx, dy)
canvas.coords(ball, 10, 10, 50, 50)        # задать новые координаты
canvas.itemconfig(box, fill="red")         # поменять цвет
canvas.delete(box)                         # удалить

Координаты считаются от левого верхнего угла холста, ось y направлена вниз.

after — таймер

root.after(мс, функция) вызывает функцию один раз через указанное время. Для повторения функция ставит себя снова:

def frame():
    canvas.move(ball, 4, 0)
    root.after(16, frame)     # примерно 60 кадров в секунду

frame()
root.mainloop()

Никогда не делайте time.sleep или долгий цикл внутри обработчика: пока он работает, mainloop стоит, и окно «зависает».

Отражение от стенок

Если за кадр мячик пролетает за стенку на d пикселей, честное отражение — поставить его на d пикселей перед стенкой и развернуть скорость: x' = 2 · hi - x. Если просто развернуть скорость, мячик останется за стенкой и в следующем кадре может развернуться снова — «застрять».

Время для секундомера

Таймер after неточен: вызов может опоздать. Поэтому время берут не из счётчика вызовов, а из часов: time.monotonic() в начале и сейчас. А показывают его форматом с ведущими нулями: f"{minutes:02d}".

Как калькулятор понимает порядок действий

Посчитать 2+3*4 строго слева направо нельзя — получится 20. Классическое решение — рекурсивный спуск: по функции на каждый уровень приоритета.

expr   = term   { (+|-) term }       сумма слагаемых
term   = factor { (*|/) factor }     произведение множителей
factor = число | -factor | ( expr )

expr собирает слагаемые, каждое слагаемое считает term, а term перемножает множители из factor. Поэтому умножение «связывает» сильнее сложения. Скобки работают сами: factor, встретив (, снова вызывает expr.

Действия одного уровня идут в цикле слева направо: 8-3-2 = (8-3)-2. Если вместо цикла написать рекурсию value - self.expr(), получится 8-(3-2) — ошибка, которую легко не заметить на сложении.

eval для калькулятора не используют: он выполнит любой код Python, который пользователь введёт в поле.

Итог

  • Canvas: create_line/rectangle/oval/text → номер фигуры → move, coords, itemconfig, delete;
  • after(мс, f) — один отложенный вызов, повтор — цепочкой; никакого sleep;
  • отражение: 2 · hi - x и смена знака скорости;
  • порядок действий — рекурсивный спуск expr → term → factor, одинаковые приоритеты — циклом слева направо.