Глава 18 · История и документ

Отмена действий (Undo)

Один пользовательский жест может состоять из многих элементов Canvas — отменяться он должен целиком.

Отмена по действиям, а не по элементам Canvas

Наивная реализация Undo — просто удалить последний созданный элемент Canvas. Проблема: один карандашный штрих состоит из МНОГИХ отдельных отрезков (раздел 18.14) — «отменить» удалит только последний крошечный кусочек штриха, а не весь его целиком, чего пользователь точно не ожидает.

istoriya_deystviy.py
undo_stack = [
    [item_id_1, item_id_2, item_id_3],  # один карандашный штрих — три отрезка
    [item_id_4],                         # один прямоугольник — один элемент
]

Финальное приложение решает это через документ (раздел 18.13, 18.29): каждое пользовательское действие — один или несколько объектов Shape — целиком добавляется в документ и целиком же может быть отменено:

undo.py
def _commit_action(self, shapes):
    self.document.extend(shapes)
    self.undo_stack.append(shapes)   # ОДНО действие — даже если shapes из многих отрезков
    self.redo_stack.clear()
    self.render_document()

def undo(self):
    if not self.undo_stack:
        return
    shapes = self.undo_stack.pop()
    del self.document[len(self.document) - len(shapes):]
    self.redo_stack.append(shapes)
    self.render_document()
Реальное окно: холст с несколькими фигурами, включая последний нарисованный прямоугольник
Реальное окно: до Ctrl+Z — виден весь рисунок, включая последнее действие.
Реальное окно: тот же холст без последнего прямоугольника, остальные фигуры на месте
Реальное окно: после Ctrl+Z — последнее ДЕЙСТВИЕ убрано целиком, предыдущие фигуры не тронуты.
render_document() — то же render(), что и в главе 17
После отмены документ меняется, а холст просто ПЕРЕРИСОВЫВАЕТСЯ из него заново — не точечное удаление конкретных элементов Canvas. Тот же принцип «модель меняют действия, Canvas только отображает текущее состояние», что и render() в игре «Крестики-нолики».
Практика: стек отмены по действиям
Автоматическая проверка — undo убирает ВСЕ элементы одного действия, а не один
Открыть практику →