Как оформить отмену и повтор в текстовом редакторе?
Часть моего проекта - это написать текстовый редактор, который использовался для ввода какого-то правила, а приложение компилировалось и запускалось. Написание компилятора было завершено, а выпуск бета-версии. В финальной версии мы должны добавить отмену и повтор в текстовом редакторе. Я использую файл и периодически сохраняю его для текстового редактора. Как оформить отмену и возврат в мой текстовый редактор? Что такое изменение в структуре персистента файла?
8 ответов
Вы можете смоделировать свои действия как команды, которые хранятся в двух стеках. Один для отмены, другой для повторения. Вы можете составлять свои команды для создания более высокоуровневых команд, например, когда вы хотите отменить действия макроса, например; или если вы хотите сгруппировать отдельные нажатия клавиш одного слова или фразы в одном действии.
Каждое действие в вашем редакторе (или действие повтора) генерирует новую команду отмены, которая входит в стек отмены (а также очищает стек отмены). Каждое действие отмены генерирует соответствующую команду повторения, которая входит в стек повторения.
Вы также можете, как упоминалось в комментариях derekerdmann, объединять команды отмены и повторения в один тип команды, которая знает, как отменить и повторить свое действие.
Есть два основных способа сделать это:
шаблон командования
использование только ОО над неизменяемыми объектами, где все является просто неизменяемыми объектами, сделанными из неизменных объектов, сделанными из неизменных объектов (это менее распространено, но удивительно элегантно, если все сделано правильно)
Преимущество использования ОО над неизменяемыми объектами над наивной командой или наивным отменой / повторением заключается в том, что вам не нужно много думать об этом: нет необходимости "отменять" эффект действия и не нужно "воспроизводить" все команды. Все, что вам нужно, это указатель на огромный список неизменяемых объектов.
Поскольку объекты являются неизменяемыми, все "состояния" могут быть невероятно легкими, потому что вы можете кэшировать / повторно использовать большинство объектов в любом состоянии.
"ОО над неизменными предметами" - это чистая жемчужина. Вероятно, не станет мейнстримом раньше, чем через 10 лет, что сказал;)
PS: выполнение OO над неизменяемыми объектами также удивительно упрощает параллельное программирование.
Если вы не хотите ничего необычного, вы можете просто добавить UndoManager. Ваш Document
уволит UndoableEdit
каждый раз, когда вы добавляете или удаляете текст. Чтобы отменить и повторить каждое изменение, просто вызовите эти методы в UndoManager.
Недостатком этого является то, что UndoManager добавляет новое редактирование каждый раз, когда пользователь что-то вводит, поэтому при вводе "apple" у вас останется 5 изменений, которые можно отменить по одному. Для моего текстового редактора я написал оболочку для правок, в которой хранится время, в которое он был сделан, помимо изменения и смещения текста, а также UndoableEditListener
это объединяет новые правки с предыдущими, если между ними есть только короткий промежуток времени (0,5 секунды хорошо для меня).
Это хорошо работает для общего редактирования, но вызывает проблемы, когда выполняется массовая замена. Если у вас был документ с 5000 экземплярами "apple", и вы хотели заменить его на "orange", то в итоге вы получили бы 5000 правок, хранящих "apple", "orange" и смещение. Чтобы уменьшить объем используемой памяти, я рассмотрел это как отдельный случай для обычных правок и вместо этого храню "яблоко", "апельсин" и массив из 5000 смещений. Я еще не удосужился применить это, но я знаю, что это вызовет некоторые головные боли, когда несколько строк соответствуют условию поиска (например, поиск без учета регистра, поиск по регулярному выражению).
Вы можете сделать это двумя способами:
- сохранить список состояний редактора и указатель в списке; отмена перемещает указатель назад и восстанавливает там состояние, вместо этого передвигается возврат, выполнение чего-либо отбрасывает все, что находится за указателем, и вставляет состояние в качестве нового верхнего элемента;
- не сохраняйте состояния, но действия, которые требуют, чтобы для каждого действия было противодействие отмене последствий этого действия
В моем (диаграмме) редакторе есть четыре уровня изменений состояния:
- фрагменты действий: они являются частью большого действия и не могут быть отменены или отменены отдельно (например, перемещение мыши)
- действия: один или несколько фрагментов действия, которые формируют значимое изменение, которое можно отменить или переделать, но которое не отражено в отредактированном документе, как изменение на диске (например, выбор элементов)
- изменения документа: одно или несколько действий, которые изменяют отредактированный документ так, как он будет сохранен на диск (например, изменение, добавление или удаление элементов)
- Сохранение документа: текущее состояние документа явно сохраняется на диск - в этот момент мой редактор выбрасывает историю отмен, поэтому вы не можете отменить сохранение
Вау, какое совпадение - буквально за последний час я реализовал отмену / повтор в моем текстовом редакторе WYSIWYG:
Основная идея - либо сохранить все содержимое текстового редактора в массиве, либо разницу между последним редактированием.
Обновляйте этот массив в значимых точках, т. Е. Через каждые несколько символов (проверяйте длину содержимого при каждом нажатии клавиши, если он отличается от 20 символов, то укажите точку сохранения). Также при изменениях стиля (если форматированный текст), добавления изображений (если это разрешено), вставки текста и т. Д. Вам также нужен указатель (просто переменная int), чтобы указать, какой элемент в массиве является текущим состоянием редактор)
Сделайте так, чтобы массив имел заданную длину. Каждый раз, когда вы добавляете точку сохранения, добавляйте ее в начало массива и перемещайте все остальные точки данных вниз на одну. (последний элемент в массиве будет забыт, когда у вас будет столько точек сохранения)
Когда пользователь нажимает кнопку отмены, проверьте, совпадает ли текущее содержимое редактора с последним сохранением (если это не так, то пользователь внес изменения с момента последней точки сохранения, поэтому сохраните текущее содержимое редактор (чтобы его можно было редактировать), сделайте редактор равным последней точке сохранения и установите переменную указателя = 1 (2-й элемент в массиве). Если они совпадают, то с момента последнего последнего изменения не производились. сохранить точку, поэтому вам нужно отменить до предыдущей точки. Для этого увеличьте значение указателя + 1 и сделайте содержимое редактора = значением указателя.
Чтобы повторить, просто уменьшите значение указателя на 1 и загрузите содержимое массива (убедитесь, что вы достигли конца массива).
Если пользователь вносит изменения после отмены, переместите ячейку массива указанных значений вверх в ячейку 0 и переместите остальные на ту же величину (вы не хотите возвращаться к другим материалам после того, как они внесли разные изменения).
Еще один важный момент - убедитесь, что вы добавляете точку сохранения только в том случае, если содержимое текстового редактора действительно изменилось (в противном случае вы получите дубликаты точек сохранения, и может показаться, что отмена ничего не делает для пользователя.
Я не могу помочь вам с особенностями Java, но я рад ответить на любые другие ваши вопросы,
Нико
Вот фрагмент, который показывает, как SWT поддерживает операции отмены / возврата. Возьмите его в качестве практического примера (или используйте его напрямую, если ваш редактор основан на SWT):
Прочитайте книгу " Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения". Насколько я помню, есть довольно хороший пример.