Выполнение отмены/повтора

Дайте мне некоторые мысли о том, как реализовать функциональность отмены/повтора - как в текстовых редакторах. Какие алгоритмы я должен использовать и что я могу прочитать. спасибо.

Ответ 1

Я знаю о двух основных делениях типов undo

  • СОХРАНЕНИЕ СОСТОЯНИЯ: Одна категория отмены - это то, где вы фактически сохраняете состояния истории. В этом случае происходит то, что в каждой точке вы сохраняете состояние в каком-то месте памяти. Когда вы хотите сделать отмену, вы просто замените текущее состояние и замените его в состоянии, которое уже было в памяти. Вот как это делается с историей в Adobe Photoshop или, например, при открытии закрытых вкладок в Google Chrome.

alt text

  • ГЕНЕРАЛЬНОЕ СОСТОЯНИЕ: Другая категория - это то, где вместо того, чтобы поддерживать сами государства, вы просто помните, какими были действия. когда вам нужно отменить, вам нужно сделать логическое обратное от этого конкретного действия. Для простого примера, когда вы делаете Ctrl + B в некотором текстовом редакторе, который поддерживает отмену, он запоминается как действие Жирный. Теперь каждое действие является отображением его логических обращений. Итак, когда вы делаете Ctrl + Z, он ищет из таблицы обратных действий и обнаруживает, что действие отмены является Ctrl + B снова. Это выполняется, и вы получаете свое предыдущее состояние. Итак, здесь ваше предыдущее состояние не было сохранено в памяти, но сгенерировано, когда вам это нужно.

Для текстовых редакторов создание состояния таким образом не слишком интенсивно вычисляется, но для таких программ, как Adobe Photoshop, это может быть слишком вычислительно интенсивным или просто невозможным. Например, для действия Размытие вы укажете действие de-Blur, но никогда не сможете получить исходное состояние, потому что данные уже потеряны. Таким образом, в зависимости от ситуации - возможности логического обратного действия и ее осуществимости, вам нужно выбирать между этими двумя широкими категориями, а затем реализовать их так, как вы хотите. Конечно, возможно иметь гибридную стратегию, которая работает для вас.

Кроме того, иногда, как и в Gmail, ограничение по времени ограничено, потому что действие (отправка почты) никогда не выполняется в первую очередь. Итак, вы не "отменяете" там, вы просто "не делаете" самого действия.

Ответ 2

Я написал два текстовых редактора с нуля, и они оба используют очень примитивную форму функциональности отмены/возврата. Под "примитивным" я подразумеваю, что функциональность была очень проста в реализации, но она неэкономична в очень больших файлах (скажем, >> 10 МБ). Тем не менее, система очень гибкая; например, он поддерживает неограниченные уровни отмены.

По сути, я определяю структуру как

type
  TUndoDataItem = record
    text: /array of/ string;
    selBegin: integer;
    selEnd: integer;
    scrollPos: TPoint;
  end;

а затем определить массив

var
  UndoData: array of TUndoDataItem;

Затем каждый член этого массива указывает сохраненное состояние текста. Теперь при каждом редактировании текста (нажатие клавиши вниз, возврат на клавишу вниз, удаление клавиши вниз, вырезание/вставка, выделение, перемещаемое мышью и т.д.), Я (пере) запускаю таймер, скажем, одну секунду. При срабатывании таймер сохраняет текущее состояние как новый элемент массива UndoData.

При отмене (Ctrl + Z) я возвращаю редактор в состояние UndoData[UndoLevel - 1] и уменьшаю UndoLevel на единицу. По умолчанию UndoLevel равен индексу последнего члена массива UndoData. При возврате (Ctrl + Y или Shift + Ctrl + Z) я возвращаю редактор в состояние UndoData[UndoLevel + 1] и увеличиваю UndoLevel на единицу. Конечно, если таймер редактирования срабатывает, когда UndoLevel не равен длине (минус один) массива UndoData, я очищаю все элементы этого массива после UndoLevel, как это обычно происходит на платформе Microsoft Windows. (но Emacs лучше, если я правильно помню - недостатком подхода Microsoft Windows является то, что, если вы отмените много изменений, а затем случайно отредактируете буфер, предыдущий контент (который был отменен) будет навсегда потерян). Возможно, вы захотите пропустить это сокращение массива.

В другом типе программы, например, в редакторе изображений, может применяться та же методика, но, конечно, с совершенно другой структурой UndoDataItem. Более продвинутый подход, который не требует большого количества памяти, состоит в том, чтобы сохранить только изменения между уровнями отмены (то есть вместо сохранения "alpha\nbeta\gamma" и "alpha\nbeta\ngamma\ndelta", вы могли бы сохраните "alpha\nbeta\ngamma" и "ADD\ndelta", если вы понимаете, о чем я). В очень больших файлах, где каждое изменение невелико по сравнению с размером файла, это значительно уменьшит использование памяти для отмененных данных, но это сложнее в реализации и, возможно, более подвержено ошибкам.

Ответ 3

Существует несколько способов сделать это, но вы можете начать поиск Command pattern. Используйте список команд для возврата (Отмена) или переадресации (повтора) через свои действия. Пример в С# можно найти здесь.

Ответ 4

Немного поздно, но здесь идет речь: вы конкретно обращаетесь к текстовым редакторам, что объясняет алгоритм, который можно адаптировать к тому, что вы редактируете. Основной принцип заключается в том, чтобы сохранить список действий/инструкций, которые могут быть автоматизированы, чтобы воссоздать каждое сделанное вами изменение. Не вносите изменения в исходный файл (если он не пуст), сохраните его как резервную копию.

Сохраните связанный список с обратной перестановкой, внесенный в исходный файл. Этот список периодически сохраняется во временный файл, пока пользователь не сохранит изменения: когда это произойдет, вы применяете изменения к новому файлу, копируете старый и одновременно применяете изменения; затем переименуйте исходный файл в резервную копию и измените имя нового файла на правильное имя. (Вы можете сохранить сохраненный список изменений или удалить его и заменить с последующим списком изменений.)

Каждый node в связанном списке содержит следующую информацию:.

  • тип изменения: вы либо вставляете данные, либо удаляете данные: для "изменения" данных используется delete, за которым следует insert
  • позиция в файле: может быть смещением или парой строк/столбцов
  • буфер данных: это данные, связанные с действием; если insert - это данные, которые были вставлены; если delete, данные, которые были удалены.

Чтобы реализовать Undo, вы работаете назад из хвоста связанного списка, используя указатель или индекс "current-node": где изменение было insert, вы делаете удаление, но не обновляете связанный список; и где он был delete, вы вставляете данные из данных в буфер связанного списка. Сделайте это для каждой команды "Отменить" от пользователя. Redo перемещает указатель 'current-node' вперед и выполняет изменение в соответствии с node. Если пользователь внесет изменения в код после отмены, удалите все узлы после индикатора "текущий - node" в хвост и установите хвост, равный индикатору "current-node". Затем пользовательские изменения вставляются после хвоста. И об этом.

Ответ 5

Мои два цента - это то, что вы хотите использовать два стека для отслеживания операций. Каждый раз, когда пользователь выполняет некоторые операции, ваша программа должна помещать эти операции в "выполненный" стек. Когда пользователь хочет отменить эти операции, просто поп-операции из "выполненного" стека в стек "отзыва". Когда пользователь хочет повторить эти операции, поп-элементы из "отзыва" стека и верните их обратно в "выполненный" стек.

Надеюсь, что это поможет.

Ответ 6

Вы можете изучить пример существующей рамки отмены/повтора, первый хит Google находится на codeplex (для .NET). Я не знаю, лучше или хуже, чем в любой другой структуре, их много.

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

Ответ 8

Для этого был создан Memento pattern.

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