Отменить / Повторить реализацию - PullRequest
60 голосов
/ 22 августа 2010

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

Ответы [ 8 ]

70 голосов
/ 22 августа 2010

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

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

alt text

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

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

Кроме того, иногда, как в Gmail, отмена с ограниченным временем возможна, потому что действие (отправка почты) никогда не выполняется в первую очередь.,Таким образом, вы не «отменяете» там, вы просто «не делаете» само действие.

14 голосов
/ 22 августа 2010

Я написал два текстовых редактора с нуля, и они оба используют очень примитивную форму функциональности отмены / возврата. Под «примитивным» я подразумеваю, что функциональность была очень проста в реализации, но она неэкономична в очень больших файлах (скажем, >> 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 ", если вы понимаете, о чем я). В очень больших файлах, где каждое изменение мало по сравнению с размером файла, это значительно уменьшит использование памяти для отмененных данных, но это сложнее в реализации и, возможно, более подвержено ошибкам.

12 голосов
/ 22 августа 2010

Есть несколько способов сделать это, но вы можете начать смотреть на Шаблон команды .Используйте список команд для перемещения назад (отменить) или вперед (повторить) в ваших действиях.Пример в C # можно найти здесь .

6 голосов
/ 06 декабря 2012

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

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

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

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

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

5 голосов
/ 15 октября 2015

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

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

2 голосов
/ 18 мая 2015

Если действия обратимы. Например, добавив 1, заставьте игрока двигаться и т.д. см. , как использовать шаблон команд для реализации отмены / повторения . Перейдя по ссылке, вы найдете подробные примеры того, как это сделать.

Если нет, используйте Сохраненное состояние , как объяснено @Lazer.

2 голосов
/ 14 ноября 2010

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

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

1 голос
/ 24 марта 2015

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...