Daj mi kilka przemyśleń, jak zaimplementować funkcję cofania / ponawiania - tak jak w przypadku edytorów tekstu. Jakich algorytmów powinienem używać i co mogę przeczytać. dzięki.
algorithm
design-patterns
user414352
źródło
źródło
Odpowiedzi:
Wiem o dwóch głównych działach typów cofnięć
W przypadku edytorów tekstu generowanie stanu w ten sposób nie wymaga zbyt wielu obliczeń, ale w przypadku programów takich jak Adobe Photoshop może być zbyt intensywne lub po prostu niemożliwe. Na przykład - w przypadku działania rozmycia określisz opcję usuwania rozmycia czynność , ale to nigdy nie doprowadzi Cię do pierwotnego stanu, ponieważ dane zostały już utracone. Tak więc, w zależności od sytuacji - możliwości logicznego odwrotnego działania i wykonalności tego, musisz wybrać jedną z tych dwóch szerokich kategorii, a następnie wdrożyć je tak, jak chcesz. Oczywiście możesz mieć strategię hybrydową, która będzie dla Ciebie odpowiednia.
Czasami, podobnie jak w Gmailu, możliwe jest cofnięcie ograniczone w czasie, ponieważ akcja (wysyłanie poczty) nigdy nie jest wykonywana w pierwszej kolejności. Zatem nie „cofasz” tego działania, po prostu „nie wykonujesz” samej czynności.
źródło
Napisałem od podstaw dwóch edytorów tekstu i oba wykorzystują bardzo prymitywną formę funkcji cofania / ponawiania. Przez „prymitywny” rozumiem, że funkcjonalność była bardzo łatwa do zaimplementowania, ale jest nieekonomiczna w bardzo dużych plikach (powiedzmy >> 10 MB). Jednak system jest bardzo elastyczny; na przykład obsługuje nieograniczone poziomy cofania.
Zasadniczo definiuję taką strukturę
a następnie zdefiniuj tablicę
Następnie każdy element tej tablicy określa zapisany stan tekstu. Teraz przy każdej edycji tekstu (klawisz znaku w dół, klawisz cofania w dół, klawisz usuwania, wycinanie / wklejanie, zaznaczenie przesuwane myszą itp.) Uruchamiam (ponownie) licznik czasu (powiedzmy) jednej sekundy. Po wyzwoleniu licznik czasu zapisuje bieżący stan jako nowy element członkowski
UndoData
tablicy.Po cofnięciu (Ctrl + Z) przywracam edytor do stanu
UndoData[UndoLevel - 1]
i zmniejszamUndoLevel
o jeden. DomyślnieUndoLevel
jest równy indeksowi ostatniego elementuUndoData
tablicy. Po ponownym wykonaniu (Ctrl + Y lub Shift + Ctrl + Z) przywracam edytor do stanuUndoData[UndoLevel + 1]
i zwiększamUndoLevel
o jeden. Oczywiście, jeśli licznik czasu edycji jest wyzwalany, gdyUndoLevel
nie jest równy długości (minus jeden)UndoData
tablicy, usuwam wszystkie elementy tej tablicy poUndoLevel
, jak to jest powszechne na platformie Microsoft Windows (ale Emacs jest lepszy, jeśli pamiętam poprawnie - wadą podejścia Microsoft Windows jest to, że jeśli cofniesz wiele zmian, a następnie przypadkowo edytujesz bufor, poprzednia zawartość (która została cofnięta) zostanie trwale utracona). Możesz pominąć tę redukcję tablicy.W innym typie programu, na przykład w edytorze obrazów, można zastosować tę samą technikę, ale oczywiście o zupełnie innej
UndoDataItem
strukturze. Bardziej zaawansowane podejście, które nie wymaga tak dużej ilości pamięci, polega na zapisywaniu tylko zmian między poziomami cofania (to znaczy zamiast zapisywania „alpha \ nbeta \ gamma” i „alpha \ nbeta \ ngamma \ ndelta”, możesz zapisz "alfa \ nbeta \ ngamma" i "DODAJ \ ndelta", jeśli widzisz, o co mi chodzi). W bardzo dużych plikach, w których każda zmiana jest niewielka w porównaniu z rozmiarem pliku, znacznie zmniejszy to użycie pamięci przez cofnięte dane, ale jest trudniejsze do zaimplementowania i prawdopodobnie bardziej podatne na błędy.źródło
Jest na to kilka sposobów, ale możesz zacząć przyglądać się wzorowi polecenia . Użyj listy poleceń, aby przejść wstecz (Cofnij) lub do przodu (ponów) podczas wykonywania czynności. Przykład w C # można znaleźć tutaj .
źródło
Trochę późno, ale proszę bardzo: Odnosisz się konkretnie do edytorów tekstu, poniżej wyjaśniono algorytm, który można dostosować do tego, co edytujesz. Zasadą jest przechowywanie listy czynności / instrukcji, które można zautomatyzować, aby odtworzyć każdą wprowadzoną zmianę. Nie wprowadzaj zmian w oryginalnym pliku (jeśli nie jest pusty), zachowaj go jako kopię zapasową.
Zachowaj połączoną listę zmian wprowadzonych w oryginalnym pliku do przodu i do tyłu. Lista ta jest zapisywana sporadycznie w pliku tymczasowym, dopóki użytkownik nie zapisze zmian: kiedy tak się stanie, zastosujesz zmiany do nowego pliku, kopiując stary i jednocześnie wprowadzając zmiany; następnie zmień nazwę oryginalnego pliku na kopię zapasową i zmień nazwę nowego pliku na poprawną. (Możesz zachować zapisaną listę zmian lub usunąć ją i zastąpić kolejną listą zmian.)
Każdy węzeł na liście połączonych zawiera następujące informacje:
delete
po którym następujeinsert
insert
to dane, które zostały wstawione; jeślidelete
, dane, które zostały usunięte.Aby zaimplementować
Undo
, pracujesz wstecz od końca listy połączonej, używając wskaźnika lub indeksu „bieżącego węzła”: tam, gdzie nastąpiła zmianainsert
, usuwasz, ale nie aktualizujesz listy połączonej; a gdzie to byłodelete
wstawiasz dane z danych do bufora listy połączonej. Zrób to dla każdego polecenia „Cofnij” użytkownika.Redo
przesuwa wskaźnik „bieżącego węzła” do przodu i wykonuje zmianę zgodnie z węzłem. Jeśli użytkownik dokona zmiany w kodzie po cofnięciu, usuń wszystkie węzły za wskaźnikiem „bieżący węzeł” do końca i ustaw koniec równy wskaźnikowi „bieżącego węzła”. Nowe zmiany użytkownika są następnie wstawiane za ogonem. I to wszystko.źródło
Moje jedyne dwa centy to to, że chciałbyś użyć dwóch stosów do śledzenia operacji. Za każdym razem, gdy użytkownik wykonuje jakąś operację, Twój program powinien umieszczać te operacje na stosie „wykonanym”. Gdy użytkownik chce cofnąć te operacje, po prostu przenieś operacje ze stosu „wykonanego” do stosu „przywracania”. Gdy użytkownik chce powtórzyć te operacje, zdejmij elementy ze stosu „przywołaj” i wrzuć je z powrotem do stosu „wykonanego”.
Mam nadzieję, że to pomoże.
źródło
Jeśli działania są odwracalne. np. dodanie 1, wykonanie ruchu gracza itp. zobacz, jak używać wzorca poleceń do implementacji cofania / ponawiania . Kliknij link, aby znaleźć szczegółowe przykłady, jak to zrobić.
Jeśli nie, użyj stanu zapisanego, jak wyjaśniono w @Lazer.
źródło
Możesz przestudiować przykład istniejącego frameworka cofania / ponawiania, pierwsze trafienie Google jest na codeplex (dla .NET) . Nie wiem, czy to jest lepsze, czy gorsze niż jakikolwiek inny framework, jest ich dużo.
Jeśli Twoim celem jest posiadanie funkcji cofania / ponawiania w aplikacji, równie dobrze możesz po prostu wybrać istniejący framework, który wygląda na odpowiedni dla twojego rodzaju aplikacji.
Jeśli chcesz dowiedzieć się, jak zbudować własne cofanie / ponawianie, możesz pobrać kod źródłowy i przyjrzeć się zarówno wzorcom, jak i szczegółom łączenia rzeczy.
źródło
Do tego został stworzony wzór Memento .
Zanim zaimplementujesz to samodzielnie, zwróć uwagę, że jest to dość powszechne, a kod już istnieje - na przykład, jeśli kodujesz w .Net, możesz użyć IEditableObject .
źródło
Dodając do dyskusji, napisałem wpis na blogu o tym, jak wdrożyć UNDO i REDO w oparciu o myślenie o tym, co jest intuicyjne: http://adamkulidjian.com/undo-and-redo.html
źródło
Jednym ze sposobów zaimplementowania podstawowej funkcji cofania / ponawiania jest użycie wzorców projektowania memento i poleceń.
Memento ma na celu na przykład zachowanie stanu obiektu, który ma być później przywrócony. Ta pamiątka powinna być jak najmniejsza w celu optymalizacji.
Na polecenie kapsułkowane substancje wzór w obiekt (polecenie) jakieś instrukcje do wykonania w razie potrzeby.
W oparciu o te dwie koncepcje możesz napisać podstawową historię cofania / ponawiania, taką jak następująca zakodowana w TypeScript ( wyodrębniona i dostosowana z biblioteki frontendowej Interacto ).
Taka historia opiera się na dwóch stosach:
W algorytmie znajdują się komentarze. Zwróć uwagę, że podczas operacji cofania stos powtórzeń musi zostać wyczyszczony! Powodem jest pozostawienie aplikacji w stabilnym stanie: jeśli cofniesz się w przeszłości, aby powtórzyć niektóre działania, które wykonałeś, poprzednie działania przestaną istnieć, ponieważ zmienisz przyszłość.
Undoable
Interfejs jest dość prosty:Możesz teraz pisać polecenia, które można cofnąć, które działają w Twojej aplikacji.
Na przykład (wciąż na podstawie przykładów Interacto) możesz napisać takie polecenie:
Możesz teraz wykonać i dodać polecenie do instancji UndoHistory:
Na koniec możesz przypisać przycisk cofania (lub skrót) do tej historii (to samo dotyczy ponawiania).
Takie przykłady są szczegółowo opisane na stronie dokumentacji Interacto .
źródło