[Alg] Undo/redo implementeren: hoe?

Pagina: 1
Acties:
  • 232 views sinds 30-01-2008
  • Reageer

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 09:25

Tomatoman

Fulltime prutser

Topicstarter
Al verschillende malen ben ik tegen het probleem aangelopen dat ik graag een undo-/redo-functie in een applicatie zou willen implementeren, maar niet zo best weet hoe ik dat moet aanpakken.

In edit controls is standaard een undo-mogelijkheid aanwezig in de vorm van de WM_CANUNDO en WM_UNDO messages. Als je een tweede keer een WM_UNDO message zendt naar een edit control, wordt de laatste undo-opdracht ongedaan gemaakt, waarmee dit hetzelfde is als redo. Nu zijn edit controls helaas de enige controls die standaard een undo-functie kennen en dan nog niet eens als een multi-level undo (dus meerdere bewerkingen ongedaan maken). Dat lijkt in de verste verte niet op de mogelijkheden in bijvoorbeeld Microsoft Excel. Nu hoeft het niet zo prachtig te zijn als Excel het doet, maar basic undo-functionaliteit lijkt me toch wel prettig.

De meest logische oplossing die ik kan verzinnen om undo/redo te implementeren is een soort stack of FIFO-buffer optuigen waar iedere bewerking die de gebruiker uitvoert in wordt bijgehouden. Voor iedere bewerking moet een inverse bewerking zijn gedefinieerd, die overeenkomt met het ongedaan maken van de bewerking. Wil de gebruiker drie keer undo doen, dan voer je de inverse bewerking uit van de laatste drie bewerkingen in de undo-buffer.

Klinkt eenvoudig, maar in de praktijk is het volgens mij heel lastig. Hoe doe je bijvoorbeeld een ‘undo’ bij een zoek-en-vervangactie in een tekstdocument? En hoe maak je een databasebewerking ongedaan als er ook nog andere gebruikers in die database actief zijn? :?

Hoe pakken jullie dit aan?

Een goede grap mag vrienden kosten.


  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 07-04 13:41
Het Command pattern werd geloof ik in het GoF boek omschreven als een manier om dit probleem aan te pakken.

  • Varienaja
  • Registratie: Februari 2001
  • Laatst online: 14-06-2025

Varienaja

Wie dit leest is gek.

Multi-user database transacties undo-en lijkt mij volledig onmogelijk.

Ik werk zelf aan een db-applicatie, en het enige dat onze applicatie ondersteunt is het wel of niet posten van wijzigingen naar de database.

In applicaties als Excel en Word is een undo implementeren vrij gemakkelijk. Je weet vast dat Word de mogelijkheid heeft om bestanden incrementeel te saven. Dat duidt er volgens mij op dat een word-document intern bestaat uit een enorme lijst met acties. En inderdaad: zolang iedere actie een inverse heeft is een undo erg gemakkelijk te implementeren.
Het undo-en van een zoek-en-vervang actie is in mijn aanname ook goed te doen, door alle acties totaan de zoek-en-vervang gewoon overnieuw te doen.

Maarja.. queries (db-transacties) undo-en.. ik heb nu al minstens 5 scenario's in m'n hoofd waarbij dat gewoon niet gaat lukken. (Ik neem aan dat jij die ook zo uit je mouw schudt.) Je kan wel aannames doen, en bepaalde queries toch ongedaan proberen te maken, maar ik vrees dat je daarmee alleen maar ervoor zorgt dat je de kans vergroot dat je de databse inconsistent achterlaat.

[ Voor 7% gewijzigd door Varienaja op 27-06-2005 17:11 ]

Siditamentis astuentis pactum.


  • chris
  • Registratie: September 2001
  • Laatst online: 11-03-2022
Je moet een stack bijhouden (dat is LIFO), en in de stack zet je acties. Een actie kan zijn "Vervang x door y", of "Verwijder "abcdefgh" beginnend bij regel 15 kolom 3. Je moet dus niet alleen de actie bijhouden, maar ook het origineel. Bij zoeken/vervangen moet je dus bijhouden wat er allemaal veranderd is. Je zou zoiets met diff kunnen oplossen ofzo.

Het is in ieder geval niet simpel. Bij google weten ze er trouwens ook wel wat van af ;)

  • Gerco
  • Registratie: Mei 2000
  • Laatst online: 04-05 22:29

Gerco

Professional Newbie

chris schreef op maandag 27 juni 2005 @ 17:11:
Je moet een stack bijhouden (dat is LIFO), en in de stack zet je acties. Een actie kan zijn "Vervang x door y"
Zo simpel is het helaas niet :)

Als ik een string heb: "x y y" en ik doe "vervang x door y", kan ik dat niet meer ongedaan maken als ik niet opsla welke character ik heb vervangen. Nog moeilijker wordt het als je gaat toestaan om acties out-of-order te undo'en (zoals Photoshop kan volgens mij).

Ik heb het boek niet bij de hand, maar "het boek" (Design Patterns in Enterprise Applications) heeft hier ook een hoofdstukje over, interessante materie idd.

edit:
Gevonden: Het "Memento" pattern

[ Voor 49% gewijzigd door Gerco op 27-06-2005 17:40 ]

- "Als ik zou willen dat je het begreep, legde ik het wel beter uit!" | All number systems are base 10!


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Op zich kun je xyy->yyy niet zomaar omdraaien. Dat is ook niet nodig als je de originele file nog niet hebt overschreven. Dan bewaar je de sustitute(x,y) actie. Bij een undo tot punt X herhaal je alle commando's tot punt X. Dit lijkt misschien langzaam, maar undo's zijn zeldzaam. BIj heel erg lange operaties kun je overwegen om een savepoint te introduceren.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 02-05 01:32
Wat ik wel eens gedaan heb is er voor zorgen dat elke actie zijn eigen inverse actie aanmaakt. Sowieso ga je uit van een ontwerp waarbij de staat van je document gescheiden is van de rest (het 'model' in model/view/controller of het 'document' in document/view).

Een 'actie' is dan een object dat je uit kunt voeren op een document en een nieuwe actie oplevert die zichzelf ongedaan maakt (wanneer 'ie wordt toegepast op hetzelfde document). Samen met twee stacks (voor undo en redo) is de werking triviaal:
code:
1
2
3
4
5
undo = []
redo = []
execute(a) = undo.push( a.execute() ), redo=[]
undo() = redo.push( undo.pop().execute() )
redo() = undo.push( redo.pop().execute() )

Het spreekt vanzelf dat je alleen kunt undo/redo'en als er iets op die stack staat. Merk op dat een actie altijd toegepast wordt op het document in de staat waarin de actie gegenereerd werd; je hoeft er dus geen rekening te houden met wijzigingen in het document!

De truc is nu dat je die acties en execute-operatie goed implementeert, maar dat is simpel. Een search/replace dialoog voert bijvoorbeel de actie 'vervang karakters 12-15 door "bar"' uit (en niet iets documentonafhankelijks als 'vervang "foo" door "bar"') en de inverse daarvan is natuurlijk zo te bepalen op het moment dat je de actie uitvoert.

edit:
De mensen boven mij doen het imo dus verkeerd; je moet niet highlevel dingen willen opslaan; dat heb je niet nodig, want je hoeft mutaties nooit op een ander document toe te passen! Je moet alleen opslaan wat je precies in een document moet wijzigen om 'm terug te krijgen in de oorspronkelijke staat.

[ Voor 20% gewijzigd door Soultaker op 27-06-2005 18:20 ]


  • Reptile209
  • Registratie: Juni 2001
  • Laatst online: 15:00

Reptile209

- gers -

Gerco schreef op maandag 27 juni 2005 @ 17:24:
Nog moeilijker wordt het als je gaat toestaan om acties out-of-order te undo'en (zoals Photoshop kan volgens mij).
Als je een implementatie maakt zoals Soultaker voorstelt, dit ook niet moeilijk meer. Je popt gewoon alle undo's van je stack totdat je op het gewenste punt bent aangekomen.
Het enige waar je dan nog over na moet denken zijn dingen als bijvoorbeeld het typen van een lap tekst als één actie zien (à la "tekst invoegen ongedaan maken" in Word), of het "per letter" laten opslaan (" 'a' invoegen ongedaan maken"). Dat laatste is natuurlijk fnuikend voor je stack: als je 20 undo-posities hebt, ben je met een halve zin al door je acties heen en kom je niet meer bij de actie(s) daarvoor.
Als je nou in je document ook nog eens de beide stacks opslaat, kan je zelfs binnen meerdere sessies zaken ongedaan maken! Hoeft volgens mij niet eens zo belachelijk veel ruimte in te nemen (krijg je alleen problemen met bijv. het invoegen van plaatjes die bij de nieuwe sessie niet meer bestaan, maar da's wel af te vangen).

Zo scherp als een voetbal!


  • Gerco
  • Registratie: Mei 2000
  • Laatst online: 04-05 22:29

Gerco

Professional Newbie

Reptile209 schreef op maandag 27 juni 2005 @ 19:11:
Als je een implementatie maakt zoals Soultaker voorstelt, dit ook niet moeilijk meer. Je popt gewoon alle undo's van je stack totdat je op het gewenste punt bent aangekomen.
Dat lijkt me niet te kunnen, correct me if I'm wrong:

Gegeven acties A1, A2 en document D. Actie A uitvoeren op D geeft een nieuw document D1 en een inverse actie IA1. Vervolgens voer je actie A2 uit op D1 en krijg je D2 en IA2.

IA2 kun je toepassen op D2 en dan krijg je D1 weer terug, maar je kunt toch niet als je D2 hebt, daar IA1 op uitvoeren? Daarmee kun je alleen D1 naar D veranderen, maar niets anders. Dat zou alleen kunnen als je kunt garanderen dat de twee acties niets met elkaar te maken hebben, iets als een zin twee keer bewerken kun je dus niet out of order undo-en.

offtopic:
Sorry voor het moeilijke gedoe, maar ik zie niet echt een manier om het handiger uit te drukken.


Misschien is het wel helemaal niet mogelijk en undo't Photoshop ook wel alle latere acties als je iets dieper in de chain wilt undo'en. Het lijkt me ook behoorlijk moeilijk te voorspellen wat het resultaat moet zijn als je een willekeurige actie ongedaan wilt maken en de rest (voor en na) niet.
Dat laatste is natuurlijk fnuikend voor je stack: als je 20 undo-posities hebt, ben je met een halve zin al door je acties heen en kom je niet meer bij de actie(s) daarvoor.
Dat kun je oplossen door de stack een (minimaal x items of ykB) limit te geven. Dan kun je gewoon per letter undo'en en toch bij grotere items een redelijk aantal undo stappen bieden.

[ Voor 30% gewijzigd door Gerco op 27-06-2005 20:41 ]

- "Als ik zou willen dat je het begreep, legde ik het wel beter uit!" | All number systems are base 10!


  • Reptile209
  • Registratie: Juni 2001
  • Laatst online: 15:00

Reptile209

- gers -

Gerco schreef op maandag 27 juni 2005 @ 20:37:
[...]

Dat lijkt me niet te kunnen, correct me if I'm wrong:

Gegeven acties A1, A2 en document D. Actie A uitvoeren op D geeft een nieuw document D1 en een inverse actie IA1. Vervolgens voer je actie A2 uit op D1 en krijg je D2 en IA2.

IA2 kun je toepassen op D2 en dan krijg je D1 weer terug, maar je kunt toch niet als je D2 hebt, daar IA1 op uitvoeren? Daarmee kun je alleen D1 naar D veranderen, maar niets anders. Dat zou alleen kunnen als je kunt garanderen dat de twee acties niets met elkaar te maken hebben, iets als een zin twee keer bewerken kun je dus niet out of order undo-en.

offtopic:
Sorry voor het moeilijke gedoe, maar ik zie niet echt een manier om het handiger uit te drukken.
Hieronder zeg je het zelf ook al: gewoon de hele chain doorlopen. In jouw voorbeeld: op D2 voer je de inverse van A2 (IA2) uit, zodat je D1 weer hebt. Daar laat je IA1 op los, en je bent weer bij D. Je kunt inderdaad niet IA1 op D2 loslaten.
Voorbeeldje om het één en ander toch wat tastbaarder te maken:
Document: D
Inhoud: xyz
Undo-stack: <geen>

Verandering A1: wijzig 'y' (abstracter: teken 2) naar 'x'
Document: D1
Inhoud: xxz
Undo:
1. teken 2 was 'y'

Verandering A2: wijzig 'x' naar 'z'
Document: D2
Inhoud: zzz
Undo:
1. teken 1 was 'x', teken 2 was 'x'
2. teken 2 was 'y'

Om dit nu allemaal weer terug te draaien ("undo all"), begin je bovenaan de stack: de eerste 2 tekens terugzetten naar 'x', om dan vervolgens teken 2 door 'y' te vervangen. Done. :)
Misschien is het wel helemaal niet mogelijk en undo't Photoshop ook wel alle latere acties als je iets dieper in de chain wilt undo'en. Het lijkt me ook behoorlijk moeilijk te voorspellen wat het resultaat moet zijn als je een willekeurige actie ongedaan wilt maken en de rest (voor en na) niet.
Je kan idd niet zeggen dat je alleen actie 1 ongedaan wil maken, en de acties 2-10 die daarna kwamen zo wilt laten.
Hoewel een algoritme zou kunnen checken of het mogelijk is dat:
a) de te undo-en actie los staat van de andere undo's, en/of
b) alles undo-en tot de gevraagde positie, om daarna alles te redo-en op de ongedaan te maken actie na [moet a) ook voor kloppen, je kan niet een tekst bold maken die er niet meer is bijvoorbeeld, en relatieve documentposities zouden kunnen wijzigen, maar da's implementatie :)]
[...]
Dat kun je oplossen door de stack een (minimaal x items of ykB) limit te geven. Dan kun je gewoon per letter undo'en en toch bij grotere items een redelijk aantal undo stappen bieden.
Klopt ook ja.

Zo scherp als een voetbal!


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 09:25

Tomatoman

Fulltime prutser

Topicstarter
Er zijn al heel wat zinnige suggesties voorbijgekomen. Met name het idee om alle commando's vanaf de uitgangssituatie te herhalen vind ik interessant, daar was ik zelf niet opgekomen. :o

Het probleem bij het bepalen van een inverse van veel acties is dat zo'n inverse actie vaak lastig te implementeren is. In het voorbeeld van Reptile209:
Reptile209 schreef op maandag 27 juni 2005 @ 20:59:
[...]
Voorbeeldje om het één en ander toch wat tastbaarder te maken:
Document: D
Inhoud: xyz
Undo-stack: <geen>

Verandering A1: wijzig 'y' (abstracter: teken 2) naar 'x'
Document: D1
Inhoud: xxz
Undo:
1. teken 2 was 'y'
De verandering A1 is in veel talen eenvoudigweg geïmplementeerd als een functie die er in pseudocode als volgt uitziet:
code:
1
D1 := ReplaceAll(D, 'y' --> 'x')
Die functie maakt zich er absoluut niet druk om hoe je hem weer ongedaan maakt. Dat je de y die oorspronkelijk op positie 2 stond vervangt, valt met deze functie niet terug te vinden, het enige wat je ziet is het eindresultaat. Het gevolg is dat je de standaardfunctie ReplaceAll() die in vrijwel iedere programmeertaal aanwezig is niet kunt gebruiken. Je zult er een eigen implementatie voor moeten schrijven die ook een undo-record opstelt. Meestal niet zo moeilijk, meestal wel zeer arbeidsintensief. Het is daarom volgens mij vooral heel bewerkelijk om al die inverse acties te programmeren. Dat maakt het idee om alle commando's vanaf de uitgangssituatie te herhalen des te interessanter.

Een goede grap mag vrienden kosten.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
tomatoman schreef op dinsdag 28 juni 2005 @ 18:52:
D1 := ReplaceAll(D, 'y' --> 'x')Die functie maakt zich er absoluut niet druk om hoe je hem weer ongedaan maakt. Dat je de y die oorspronkelijk op positie 2 stond vervangt, valt met deze functie niet terug te vinden, het enige wat je ziet is het eindresultaat. Het gevolg is dat je de standaardfunctie ReplaceAll() die in vrijwel iedere programmeertaal aanwezig is niet kunt gebruiken.
Vaak gebruik je dat soort functies toch al niet omdat je bijvoorbeeld ook wil weergeven hoe vaak de replaces zijn uitgevoerd.

Ik zat net te denken over undo en redo. In SVN kun je een commit (niet eenvoudig) undo-en, maar je kunt wel de inverse uitvoeren.
Het 'gevaar' met undo/redo is dat je veranderingen kwijt kunt raken.
Je zou undo dus ook kunnen implementeren als het toevoegen van de inverse actie (aan de undo stack) in plaats van het poppen van de laatste actie.

[ Voor 49% gewijzigd door Olaf van der Spek op 28-06-2005 22:54 ]


  • MBV
  • Registratie: Februari 2002
  • Laatst online: 14:20

MBV

Tipje: in het boek Design Patterns (Gamma, Helm, Johnson en Vlissides, uitgegeven door Addison-Wesley) staat in hoofdstuk 2.7.3 een voorbeeld hoe ze het hebben geimplementeerd voor een tekstverwerker. Evt kan ik een foto maken van die pagina's (heb geen scanner, wel een Nikon D70). Stuur maar een mailtje.
Zoieso een goed boek :)

[ Voor 28% gewijzigd door MBV op 28-06-2005 23:12 ]


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 09:25

Tomatoman

Fulltime prutser

Topicstarter
MBV schreef op dinsdag 28 juni 2005 @ 23:11:
Tipje: in het boek Design Patterns (Gamma, Helm, Johnson en Vlissides, uitgegeven door Addison-Wesley) staat in hoofdstuk 2.7.3 een voorbeeld hoe ze het hebben geimplementeerd voor een tekstverwerker. Evt kan ik een foto maken van die pagina's (heb geen scanner, wel een Nikon D70). Stuur maar een mailtje.
Zoieso een goed boek :)
De in dit topic genoemde boeken over design patterns heb ik helaas niet, hoewel ik er inmiddels sterk over begin te twijfelen om er een te kopen (welke?). Als het niet teveel werk is en niet zoveel pagina's betreft dat je aangeklaagd wordt voor copyrightschending :) ben ik zeer geïnteresseerd in de bewuste pagina's.
OlafvdSpek schreef op dinsdag 28 juni 2005 @ 22:48:
Ik zat net te denken over undo en redo. In SVN kun je een commit (niet eenvoudig) undo-en, maar je kunt wel de inverse uitvoeren.
Het 'gevaar' met undo/redo is dat je veranderingen kwijt kunt raken.
Je zou undo dus ook kunnen implementeren als het toevoegen van de inverse actie (aan de undo stack) in plaats van het poppen van de laatste actie.
Het probleem als je een undo bovenop de stack zet is dat de volgende undo actie de undo actie bovenop de stack ongedaan maakt door er een tweede undo bovenop te zetten. Dat maakt alles nogal ingewikkeld.

Het is volgens mij handiger om een tweede stack te maken (de redo stack) waar je bij een multi-undo alle inverse undo-acties (dus de originele acties) op zet. Is de volgende bewerking een redo, dan werk je die tweede stack af. Is de volgende bewerking iets anders, dan maak je de redo stack leeg en ga je vrolijk verder met stapelen op de undo stack

Een goede grap mag vrienden kosten.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
tomatoman schreef op dinsdag 28 juni 2005 @ 23:43:
Het probleem als je een undo bovenop de stack zet is dat de volgende undo actie de undo actie bovenop de stack ongedaan maakt door er een tweede undo bovenop te zetten. Dat maakt alles nogal ingewikkeld.
Klopt, het is inderdaad iets lastiger dan ik dacht.
Het is volgens mij handiger om een tweede stack te maken (de redo stack) waar je bij een multi-undo alle inverse undo-acties (dus de originele acties) op zet. Is de volgende bewerking een redo, dan werk je die tweede stack af. Is de volgende bewerking iets anders, dan maak je de redo stack leeg en ga je vrolijk verder met stapelen op de undo stack
Het probleem is dat je op dat moment veranderingen verliest en dat wilde ik nu juist voorkomen.
Maar je hebt gelijk dat het toch best lastig wordt omdat je dan een hele lading acties op de stack hebt staan die niet meer in je document zitten.

Verwijderd

tomatoman schreef op dinsdag 28 juni 2005 @ 23:43:

Het is volgens mij handiger om een tweede stack te maken (de redo stack) waar je bij een multi-undo alle inverse undo-acties (dus de originele acties) op zet. Is de volgende bewerking een redo, dan werk je die tweede stack af. Is de volgende bewerking iets anders, dan maak je de redo stack leeg en ga je vrolijk verder met stapelen op de undo stack
Dat lijkt mij een hele juiste manier. Zo heb ik het in ieder geval ook gedaan ;) . Zelf heb ik het met een twee dimensionale array gedaan (voor alleen tekst). Dan kan je de positie van de cursor er ook in zetten (delete of backspace). Eigenlijk had ik er een driedimensionale van moeten maken dan kan je ook aangeven hoe vaak achter elkaar de undo/redo aangeroepen moet worden (in het geval van de find and replace) Dat scheelt de gebruiker een muisarm. Nu moet ik het op een dirty manier doen door alle tekst op te slaan. Maar ja daar kom je natuurlijk pas achter als je de find and replace gaat implementeren |:( .

Je hebt dus twee meerdimensionale array's. Één voor undo en een voor redo. Bij een undo actie de data in de redo array zetten en bij verder werken de redo legen (anders maak je je het wel extreem moeilijk). Natuurlijk zijn je array's of wat voor memory blokken dan ook dynamisch we leven immers in 2005.

  • alienfruit
  • Registratie: Maart 2003
  • Laatst online: 00:59

alienfruit

the alien you never expected

Misschien kan je wat met het restrictieve lineaire undo model? Verder is het natuurlijk belangrijk dat je "undo branches". Overigens genoeg artikelen over een undo/redo implementatie hoor :))

Als je wil dat een gebruiker zijn acties op een database kan undo-en moet je centraal een globale en een lokale undo lijst bij gaan houden. Een undo voor alle gebruikers en lokaal, alleen dit wordt wel lastig omdat je dan opzoek moet gaan acties die kunnen worden gemerged of uitgesplitst die vervolgens weer nieuwe acties zijn in de undo stack. Om te zorgen dat een zo correct mogelijke recovery van de data komt.<-- als je bijvoorbeeld met meerdere gebruikers aan hetzelfde werkt :+

Undo/Redo blijft een lastig deel :)

[ Voor 89% gewijzigd door alienfruit op 29-06-2005 02:14 ]


  • justmental
  • Registratie: April 2000
  • Niet online

justmental

my heart, the beat

Kun je niet gewoon 1 stack maken met alle acties en de 'diff' die die acties op hebben geleverd op je document?
Bij een undo zet je dan je stack pointer terug en gooi je de diff omgekeerd over je document, bij een redo gaat de stack pointer weer omhoog etc.
Als er dan na een undo een andere actie gedaan wordt schrijf je verder op de stack waar je gebleven was.

Who is John Galt?


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Verwijderd schreef op woensdag 29 juni 2005 @ 00:02:
[...]
Dat lijkt mij een hele juiste manier. Zo heb ik het in ieder geval ook gedaan ;) . Zelf heb ik het met een twee dimensionale array gedaan (voor alleen tekst). Dan kan je de positie van de cursor er ook in zetten (delete of backspace). Eigenlijk had ik er een driedimensionale van moeten maken dan kan je ook aangeven hoe vaak achter elkaar de undo/redo aangeroepen moet worden (in het geval van de find and replace)
Dat is niet twee- of drie dimensionaal. Je wijzigingsobjecten hebben blijkbaar 2 of 3 velden { wijzigingstype, positie, herhaling } en daar heb je dan een 1-dimensionale array van.

Dat model loopt overigens spaak bij replace - je hebt als gevolg van een replace een heleboel wijzigingen, die je met een enkele undo allemaal wil veranderen.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • MBV
  • Registratie: Februari 2002
  • Laatst online: 14:20

MBV

Misschien een idee om te kijken hoe ze het bij GVim, openoffice o.i.d. hebben opgelost? kan je direct wat code jatten (oh nee, dat mocht niet ;) )

Ik stuur deze week wel een mailtje met de bewuste pagina's

  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 04-05 13:09
Op dofactory.com doen ze het met het Command Pattern. Weet zo niet welke ze in het GoF boek gebruiken?

offtopic:
Trouwens erg goede site. Alle Patterns staan er beknopt uitgelegd en met voorbeeldcode er bij!

[ Voor 16% gewijzigd door riezebosch op 29-06-2005 10:40 ]

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


  • MBV
  • Registratie: Februari 2002
  • Laatst online: 14:20

MBV

In mijn boek staat het met een Command pattern. Had geen zin om het uit te zoeken hoe het zat, heb alleen even in mijn boekenkast gekeken óf het erin stond :)

@sig:
"It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration." - E.W. Dijkstra
Ik heb toch echt eventjes basic gedaan op mijn rekenmachine, en begon daar direkt met 'functies' maken. Volgens mij is het allemaal goed gekomen, aan mijn cijfers modelleren etc te zien :P

[ Voor 58% gewijzigd door MBV op 29-06-2005 12:09 ]


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 14:03

curry684

left part of the evil twins

Tis erg simpel in een tekstverwerker hoor. Je werkt inderdaad met een LIFO stack, waarin je alle acties abstraheert tot basale operaties, zijnde "insert" en "delete". Alle operaties zijn terug te brengen tot die 2 acties, en laten die nou net kinderlijk eenvoudig te reversen zijn. Vervolgens breng je 'markers' aan in je stack die een rollback point introduceren, en je hebt het 'Replace All' probleem opgelost omdat 1 keer Ctrl-Z rollbackt tot ie een 'rollback point' tegenkomt en daarmee dus de complete operatie undoet.

Professionele website nodig?


  • MBV
  • Registratie: Februari 2002
  • Laatst online: 14:20

MBV

Klinkt simpel Curry, alhoewel jij voor het gemak de opmaak vergeet.
Is ook ongeveer hoe het in "Design Patterns" staat beschreven, al gebruiken zij meer een 'array'-achtige structuur: een lijst met 'command'-objecten die een 'execute' en 'unexecute' functie hebben heeft een lijn 'heden'. Normaal staat die lijn na het laatste item, na een undo actie staat hij ervoor. Zo kan je eenvoudig undo/redo doen. Als je een stack popt, ben je het item kwijt, en kan je dus geen redo doen.

offtopic:
kan iemand mij vertellen of ik inbreuk doe op het copyright als ik die 2/3 pagina's fotografeer en hier dump?

Verwijderd

MSalters schreef op woensdag 29 juni 2005 @ 09:54:
[...]

Dat is niet twee- of drie dimensionaal. Je wijzigingsobjecten hebben blijkbaar 2 of 3 velden { wijzigingstype, positie, herhaling } en daar heb je dan een 1-dimensionale array van.

Dat model loopt overigens spaak bij replace - je hebt als gevolg van een replace een heleboel wijzigingen, die je met een enkele undo allemaal wil veranderen.
Je hebt gelijk. Maar volgens mij kan een twee dimensionale array wel heel bruikbaar zijn als je een undo redo wil implementeren zonder data verlies. Met bepaalde markers kun je dan de redo op bapaalde punten weer open gooien.
Helaas moet je de gebruiker wel de mogelijkheid geven om te kunnen kiezen uit verschillende redo acties op bepaalde punten. De vraag is in hoeverre de gebruiker daar bij gebaat is. Een simpel wat vaker saven lijkt mij voor de gebruiker eenvoudiger en al helemaal voor de programmeur.

Dat bedoelde ik met een herhalingsgetal. Normaal zal je deze op 1 willen hebben maar bij het replacen zal dit getal zo hoog zijn als het aantal vervangde tekens. Zoals Curry al aangeeft met behulp van roll back points lijkt mijn manier daar een beetje op.

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 14:03

curry684

left part of the evil twins

MBV schreef op woensdag 29 juni 2005 @ 12:35:
Klinkt simpel Curry, alhoewel jij voor het gemak de opmaak vergeet.
Is ook ongeveer hoe het in "Design Patterns" staat beschreven, al gebruiken zij meer een 'array'-achtige structuur: een lijst met 'command'-objecten die een 'execute' en 'unexecute' functie hebben heeft een lijn 'heden'. Normaal staat die lijn na het laatste item, na een undo actie staat hij ervoor. Zo kan je eenvoudig undo/redo doen. Als je een stack popt, ben je het item kwijt, en kan je dus geen redo doen.
Je 'popt' pas als er een nieuwe entry op moet, tot die tijd bewaar je alleen de positie in de stack bij het undoen. Op die manier blijft een redo mogelijk. Opmaak zit in een tekstverwerker altijd in de tekst zelf embedded en is dus ook een 'insert' of 'delete' actie :)

Professionele website nodig?


  • MBV
  • Registratie: Februari 2002
  • Laatst online: 14:20

MBV

Jaja, smoesjes om jezelf te redden :P
@TS: mailtje met foto's is al onderweg :)

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 07-04 13:41
MBV schreef op woensdag 29 juni 2005 @ 12:35:
offtopic:
kan iemand mij vertellen of ik inbreuk doe op het copyright als ik die 2/3 pagina's fotografeer en hier dump?
Waarschijnlijk wel, maar op het internet is genoeg te vinden over het Command pattern, al dan niet in combinatie met undo/redo. Op javaworld staat er een mooi artikel over.

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 05-05 19:18
Ik zoiets een geïmplementeerd in een project voor m'n opleiding.
Daarbij heb ik gebruik gemaakt van een CommandProcessor die Command objecten uitvoerde en een stack bijhield van uitgevoerde Command's t.b.v. undo functionaliteit. De Command objecten zelf waren verantwoordelijk voor het uitvoeren van acties op een model en het bijhouden van de state van dat model (m.b.v. Memento pattern).
Op deze manier kon ik dus ook macro's maken voor het uitvoeren/terugrollen van meerdere acties (meerdere Command objecten in sequentie).

  • Gerco
  • Registratie: Mei 2000
  • Laatst online: 04-05 22:29

Gerco

Professional Newbie

riezebosch schreef op woensdag 29 juni 2005 @ 10:39:
Op dofactory.com doen ze het met het Command Pattern. Weet zo niet welke ze in het GoF boek gebruiken?
Ze gebruiken daar het command pattern en het memento pattern (alleen voor complexe situaties).

Wat ze doen komt neer op Commands op een stack gooien die een Memento van het complexe object opvragen voor ze hun werk erop doen. Bij unexecute() geven ze die memento weer terug aan het complexe object en dat restored zijn state ermee. Voor eenvoudige objecten waarop gewerkt moet worden kun je dan een inverse functie gebruiken en anders een Memento.

- "Als ik zou willen dat je het begreep, legde ik het wel beter uit!" | All number systems are base 10!


  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 05-05 19:18
Gerco schreef op woensdag 29 juni 2005 @ 17:22:
[...]

Ze gebruiken daar het command pattern en het memento pattern (alleen voor complexe situaties).

Wat ze doen komt neer op Commands op een stack gooien die een Memento van het complexe object opvragen voor ze hun werk erop doen. Bij unexecute() geven ze die memento weer terug aan het complexe object en dat restored zijn state ermee. Voor eenvoudige objecten waarop gewerkt moet worden kun je dan een inverse functie gebruiken en anders een Memento.
Ja, zo heb ik het destijds ook toegepast.
Het object waarvan de state bewaard moest worden had twee methoden getMemento en setMemento. Het memento object zelf werd aangemaakt door een inner class van dat object (vond ik in die situatie de mooiste oplossing).

Zelf had ik wel de indruk dat het een logge oplossing is, want als er maar één member veranderd wordt er wel steeds een memento object aangemaakt dat de volledige state bevat die misschien voor 99% ongewijzigd is.

[ Voor 14% gewijzigd door Kwistnix op 29-06-2005 17:59 ]


  • MBV
  • Registratie: Februari 2002
  • Laatst online: 14:20

MBV

In Design Patterns hadden ze het over een ChangeFontCommand (ofzo, dat idee), die een unexecute actie had. Die deed zijn verandering ongedaan. Volgens mij hoef je daarvoor niet het hele document opnieuw te laden :)

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 05-05 19:18
MBV schreef op woensdag 29 juni 2005 @ 18:27:
In Design Patterns hadden ze het over een ChangeFontCommand (ofzo, dat idee), die een unexecute actie had. Die deed zijn verandering ongedaan. Volgens mij hoef je daarvoor niet het hele document opnieuw te laden :)
Opnieuw laden niet, want het font type veranderd niet aan de inhoud van het document, maar alleen de weergave van die inhoud. Stel dat je in deze situatie een Memento gaat gebruiken.
Het object Document maakt een Memento aan waar de hele state in bewaard wordt, dus inhoud, font face, font size etc. Je ChangeFontCommand gebruikt dit Memento object om de state voor het wijzigen van het font type op te slaan.

Dit is een vrij dure opratie in deze context, omdat alleen het font type veranderd (één member), maar het Memento object slaat wel de volledige state op, dus ook de volledige inhoud en alle andere members, terwijl dat helemaal niet relevant is.

Dit is een van de grootste nadelen van het Memento pattern:
Using Mementos might be expensive if the Originator must store a large portion of its state information in the Memento or if the Caretakers constantly request and return the Mementos to the Originator. Therefore, this pattern should only be used if the benefit of using the pattern is greater than the cost of encapsulation and restoration

[ Voor 20% gewijzigd door Kwistnix op 29-06-2005 19:16 ]


  • Sendy
  • Registratie: September 2001
  • Niet online
Als je je quote goed leest zie je:
... if the Originator must store a large portion of its state information ...
Bij het wijzingen van een font kan je ook alleen het vorige font opslaan. Als er meerdere fonts tegelijk gewijzigd worden dan moet je iets meer opslaan, maar je hoeft nooit het hele document te bewaren in iedere stap.

Wel grappig, ik ben net ook zo'n undo-lijst aan het programmeren.

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 05-05 19:18
Sendy schreef op woensdag 29 juni 2005 @ 21:01:
Als je je quote goed leest zie je:

[...]

Bij het wijzingen van een font kan je ook alleen het vorige font opslaan. Als er meerdere fonts tegelijk gewijzigd worden dan moet je iets meer opslaan, maar je hoeft nooit het hele document te bewaren in iedere stap.

Wel grappig, ik ben net ook zo'n undo-lijst aan het programmeren.
Als je Memento implementeert dan implementeer je er één per object die de hele state, of alles wat relevant kan zijn (aan verandering onderhevig), van dat object onthoudt.
Dan loop je dus wel degelijk tegen het probleem aan dat je gegevens in het Memento object stopt die helemaal niet gewijzigd zijn. Je gaat namelijk niet voor iedere member van een object een apart Memento object maken, want dan kan je net zo goed geen Memento implementeren.
In het voorbeeld van het document zou het dus zo kunnen zijn dat je de inhoud van het document ongewijzigd in de Memento stopt vanwege een simpele font wijziging. Dan heb je dus niet goed nagedacht over wat je wel en niet bij elkaar in de Memento stopt, maar da's een ander verhaal :)

  • Sendy
  • Registratie: September 2001
  • Niet online
Je hebt inderdaad gelijk. Mijn fout.

Vraag me dan toch af hoe je dit handiger/beter kan doen. Als je een ingewikkeld commando naar het model wil sturen, weet eigenlijk alleen het model wat er veranderd is. Dus de execute() zou een commando moeten retourneren dat de staat terugzet. Echter, het model wil (of zou) helemaal niets met commando's te maken hebben?!? Hmm...
Pagina: 1