실행 취소 / 다시 실행 구현
텍스트 편집기에서와 같이 실행 취소 / 다시 실행 기능을 구현하는 방법에 대해 생각해보십시오. 어떤 알고리즘을 사용해야하며 무엇을 읽을 수 있는지. 감사.
실행 취소 유형의 두 가지 주요 구분에 대해 알고 있습니다.
- 상태 저장 : 실행 취소의 한 범주는 실제로 기록 상태를 저장하는 곳입니다. 이 경우 발생하는 일은 모든 지점에서 상태를 메모리의 특정 위치에 저장하는 것입니다. 실행 취소를 원할 때 현재 상태를 교체하고 이미 메모리에 있던 상태로 교체하면됩니다. 예를 들어 Adobe Photoshop에서 History를 사용하거나 Google Chrome에서 닫힌 탭을 다시 여는 방법입니다.
- 상태 생성 : 다른 범주는 상태 자체를 유지하는 대신 작업이 무엇인지 기억하는 것입니다. 실행을 취소해야하는 경우 해당 특정 작업을 논리적으로 반대로 수행해야합니다. 간단한 예를 들어, 실행 취소를 지원하는 일부 텍스트 편집기에서 Ctrl+ 를 수행 B하면 굵은 작업으로 기억됩니다 . 이제 각 작업에는 논리적 반전 매핑이 있습니다. 따라서 Ctrl+ 를 수행하면 Z역 행동 테이블에서 조회하고 실행 취소 조치가 다시 Ctrl+ 임을 확인 B합니다. 그것이 수행되고 이전 상태를 얻습니다. 따라서 여기서 이전 상태는 메모리에 저장되지 않고 필요할 때 생성되었습니다.
텍스트 편집기의 경우 이러한 방식으로 상태를 생성하는 것은 계산 집약적이지 않지만 Adobe Photoshop과 같은 프로그램의 경우 계산 집약적이거나 불가능할 수 있습니다. 예를 들어 -Blur 작업의 경우 De-Blur 작업을 지정 하지만 데이터가 이미 손실 되었기 때문에 원래 상태 로 되돌릴 수 없습니다. 따라서 상황에 따라 논리적 역행 동의 가능성과 실행 가능성에 따라이 두 가지 범주 중에서 선택한 다음 원하는 방식으로 구현해야합니다. 물론 자신에게 적합한 하이브리드 전략을 가질 수 있습니다.
또한 Gmail과 마찬가지로 작업 (메일 보내기)이 처음에 수행되지 않기 때문에 시간 제한이있는 실행 취소가 가능한 경우도 있습니다. 그래서, 당신은 거기에서 "실행 취소"하는 것이 아니라 단지 액션 자체를 "하지 않는"것입니다.
필자는 처음부터 두 개의 텍스트 편집기를 작성했으며 둘 다 매우 원시적 인 형태의 실행 취소 / 다시 실행 기능을 사용합니다. "기본"이란 기능이 구현하기 매우 쉬웠지만 매우 큰 파일 (예 : >> 10MB)에서는 비 경제적이라는 것을 의미합니다. 그러나 시스템은 매우 유연합니다. 예를 들어 무제한 수준의 실행 취소를 지원합니다.
기본적으로 다음과 같은 구조를 정의합니다.
type
TUndoDataItem = record
text: /array of/ string;
selBegin: integer;
selEnd: integer;
scrollPos: TPoint;
end;
그런 다음 배열을 정의하십시오.
var
UndoData: array of TUndoDataItem;
그런 다음이 배열의 각 구성원은 텍스트의 저장된 상태를 지정합니다. 이제 텍스트를 편집 할 때마다 (문자 키 내리기, 백 스페이스 내리기, 키 내리기, 잘라 내기 / 붙여 넣기, 마우스로 이동 한 선택 등) 1 초의 타이머를 (다시) 시작합니다. 트리거되면 타이머는 현재 상태를 UndoData
배열 의 새 구성원으로 저장합니다 .
On undo (Ctrl+Z), I restore the editor to the state UndoData[UndoLevel - 1]
and decrease the UndoLevel
by one. By default, UndoLevel
is equal to the index of the last member of the UndoData
array. On redo (Ctrl+Y or Shift+Ctrl+Z), I restore the editor to the state UndoData[UndoLevel + 1]
and increase the UndoLevel
by one. Of course, if the edit timer is triggered when UndoLevel
is not equal to the length (minus one) of the UndoData
array, I clear all items of this array after UndoLevel
, as is common on the Microsoft Windows platform (but Emacs is better, if I recall correctly -- the disadvantage of the Microsoft Windows approach is that, if you undo a lot of changes and then accidentally edit the buffer, the previous content (that was undid) is permanently lost). You might want to skip this reduction of the array.
In a different type of program, for instance, an image editor, the same technique can be applied, but, of course, with a completely different UndoDataItem
structure. A more advanced approach, which does not require as much memory, is to save only the changes between the undo levels (that is, instead of saving "alpha\nbeta\gamma" and "alpha\nbeta\ngamma\ndelta", you could save "alpha\nbeta\ngamma" and "ADD \ndelta", if you see what I mean). In very large files where each change is small compared to the file size, this will greatly decrease the memory usage of the undo data, but it is trickier to implement, and possibly more error-prone.
There are several ways to do this, but you could start looking at the Command pattern. Use a list of commands to move back (Undo) or forward (redo) through your actions. An example in C# can be found here.
A bit late, but here goes: You specifically refer to text editors, what follow explains an algorithm that can be adapted to whatever it is you are editing. The principle involved is to keep a list of actions/instructions that can be automated to recreate each change you made. Do not make changes to the original file (if not empty), keep it as a back-up.
Keep a forward-backward linked-list of changes you make to the original file. This list is saved intermittently to a temporary file, until the user actually Saves the changes: when this happens you apply the changes to a new file, copying the old one and simultaneously applying the changes; then rename the original file to a backup, and change the new file's name to the correct name. (You can either keep the saved change-list, or delete it and replace with a subsequent list of changes.)
Each node in the linked-list contain the following info:.
- type of change: you either insert data, or you delete data: to "change" data means a
delete
followed by aninsert
- position in file: can be an offset or line/column-pair
- data buffer: this is the data involved with the action; if
insert
it is the data that was inserted; ifdelete
, the data that was deleted.
To implement Undo
, you work backward from the tail of the linked-list, using a 'current-node' pointer or index: where the change was insert
, you do a delete but without updating the linked-list; and where it was a delete
you insert the data from the data in the linked-list buffer. Do this for each 'Undo'-command from the user. Redo
moves the 'current-node' pointer forward and executes the change as per the node. Should the user make a change to the code after undoing, delete all nodes after the'current-node' indicator to the tail, and set tail equal to the 'current-node' indicator. The user's new changes are then inserted after the tail. And that's about it.
My only two cents is that you would want to use two stacks to keep track of the operations. Every time the user performs some operations, your program should put those operations on a "performed" stack. When user want to undo those operations, simply pop operations from the "performed" stack to a "recall" stack. When user wants to redo those operations, pop items from "recall" stack and push them back to "performed" stack.
Hope it helps.
You may study an example of an existing undo/redo framework, the first Google hit is on codeplex (for .NET). I don't know if that is better or worse than any other framework, there are lots of them.
If your goal is to have undo/redo functionality in your application you might as well just pick an existing framework that looks suitable to your kind of application.
If you want to learn how to build your own undo/redo you can download the source code and have a look at both patterns and the details how to wire things up.
If the actions are reversible. e.g Adding 1, make a player move etc see how to use the Command Pattern to implement undo/redo. Follow the link the you will find a detailed examples on how to do that.
If not, use Saved State as explained by @Lazer.
The Memento pattern was made for this.
이를 직접 구현하기 전에 이것은 매우 일반적이며 코드가 이미 존재합니다. 예를 들어 .Net에서 코딩하는 경우 IEditableObject 를 사용할 수 있습니다.
참고 URL : https://stackoverflow.com/questions/3541383/undo-redo-implementation
'Nice programing' 카테고리의 다른 글
C # 7.0은 .NET 4.5에서 작동합니까? (0) | 2020.11.27 |
---|---|
Windows에서 shutil.rmtree가 '액세스가 거부되었습니다'와 함께 실패합니다. (0) | 2020.11.27 |
블러 이벤트를 수동으로 추적하지 않고 Backbone.js의 모델에 양식 입력을 바인딩 할 수 있습니까? (0) | 2020.11.27 |
결과가 하나만있을 경우 목록 이해의 대안 (0) | 2020.11.27 |
서로 다른 데이터 프레임의 두 플롯을 결합하는 ggplot (0) | 2020.11.27 |