Community pulse: Undo in form designer.

reaching-the-sky-lOne of the main drawbacks of our IDE is the lack of Undo in the Form Designer. I thought that it was a matter of time to do it, but when I read this thread I figured out that the problem can be also elsewhere… …besides of the assumptions about what the community thinks about something. πŸ˜‰

A Snapshot system would work very well here in my humble opinion. Every time when the form is changed, a Global Snapshot (saving in the history list the entire DFM and PAS – one can compress them to save memory) will be created when the system enters again in stable state. So, one doesn’t need to track which properties are changed (because some properties change in complex ways – depending one on another) and also there are no discrepancies between DFM and PAS.

When only the Pas file is changed, then we should have the present undo engine.

So, we have the following algorithm (based on someone’s πŸ˜‰ example):

  1. User drops a button
    – Form Change.
    – After everything is done (name & position setting, take care at CnPack & others who interfere with dialogs here) do a General Snapshot.
  2. User double clicks button
    Form (Dfm) Change. General Snapshot.
  3. User adds some code. – eg. ‘Readln’;
    Form stays the same. So save the sequence: Add ‘R’, Add ‘e’, Add ‘a’, Add ‘d’, Add ‘l’, Add ‘n’, Add ‘;’
  4. User moves the button a little bit.
    Form Change. General Snapshot.
  5. User sets Font.Size to 15VCL sets also ParentFont to False
    Form Change. General Snapshot when the System is idle. (Ie. when the VCL changed already the ParentFont) – this should be easy since the ParentFont is changed in Font’s Setter.
  6. User change the name of the button.
    Form Change. General Snapshot when the System is idle. (Ie. when both the DFM and PAS are updated and, hence, in sync).
  7. User deletes the button
    Form Change. General Snapshot.

So when one presses Undo multiple times, it should:

7. The button appears
6. Revert to the old name of the button
5. Revert *both* the ParentFont and Font.Size changes
4. Revert to the old position of the button
3. Delete the ‘;’, ‘n’, ‘l’, ‘d’… (etc.) – the present Undo engine

NOTE: Now we should remain with something like this:

procedure TfrmMain.Button1Click(Sender: TObject);
begin

end;

and with a pointer in dfm to the Button1Click handler
2. The empty procedure procedure above will disappear as well as the Button1Click handler from dfm and from TfrmMain class definition.

1. The button disappears

Comments

Because the algorithm is abstract (seeing the DFM+PAS ecosystem as a black-box) it works whatever changes are happening inside the system. The main drawback is memory consumption, but I think that the cases in which one has 10 MB on his form can be regarded as a corner case. But also, in this very corner case 100 MB for undo gives us 10 form changes backwards (of course, if we don’t use compression) which is (IMHO) more than sufficient. Especially when we compare with what we have today.

Did I miss something?

Thoughts?

Comments?

9 thoughts on “Community pulse: Undo in form designer.

  1. Wouldn’t the background compiler in D2010 need to take a snapshot before the user could continue with the Form Designer and Editor. Perhaps this mechanism could be overloaded to implement the Undo/Redo.

  2. I don’t think you’ve addresses the problem that *form* design and *code* editing are two different contexts within the common environment.

    If I make a mixture of code changes and form design “tweaks”, when I “undo” a form design change I do not (always) want any intervening code changes undone:

    1. Place a control
    2. Add some code
    3. Move the control
    4. Add another control
    5. Move the control placed at #1 above
    6. Add some more code
    7. Re-arrange code (I have a preference for how methods are organised in a unit that doesn’t always agree with the IDE default placement of the code)
    8. Review my form design (i.e. return to form design view)

    So now imagine I’m sitting looking at my FORM design and decide that I didn’t want to move that control (at step #5) after all.

    So I “Undo”.

    But I do NOT want the code changes I made at #6 and #7 to be undone.

    Or maybe sometimes I do.

    I think the only way a form design Undo could work would be for a given Form DEsign activity context. That is, for as long as I am working in the form designer I can undo any changes I make. But as soon as I switch to a code editor view my form design undo buffer is reset.

    Now that might work.

    It might not be perfect, but it’s better than nothing and avoids the problem of trying to handle undo across separate and not always related edit contexts (code + form design).

    • “But I do NOT want the code changes I made at #6 and #7 to be undone.

      Or maybe sometimes I do.”

      Then DON’T press Undo, depending on your intent. πŸ™‚

      Your solution is more restrictive than what I proposed without giving any advantage. Why reset the buffer? If you don’t want to ‘undo’ also the code, copy it or don’t press the ‘Undo’ button. As you know, we must to keep in sync the Dfm and Pas. Also, another option would be at the step in which one presses the ‘Undo’ in a different context, perhaps a Don’t-Show-Again dialog would say ‘We revert a change from ” (ie. Form / Code)

  3. In fact, your solution have common sense.
    But I propose simpler solution. After each operation system performs snapshot, even for pas files only.
    But this snapshots are differential one.
    Good differential compressed snapshots could weight even less then raw changes only. And yes, they are fast as I made detailed review of such techniques some time ago.

    So, here is practical implementation:
    1) Change occured (button moved, for example)
    2) DFM file is in memory.
    3) Undo engine always have previous DFM file, before the change.
    4) Engine make reverse differential compression (original – current file, new – file before change). And leaves it in memory.
    5) As soon as changes become too numerous all old changes could be dumped to disk using FIFO principle.
    6) One undo requires only one simple decompression. And you must note that normal text edit operations could be implemented inside compression scheme (as it uses insert and delete operations already) to speed up things.

    Is it hard to implement? No, it is not.
    It is not as facinating as playing with various properties changes and similar things. I know young boys, they like that πŸ™‚

    And, btw, this post also shows biggest mistake made by Delphi developers – separating DFM and PAS files (and later making DFM binaries). ALl DFM data must be included within form class declaration and must be contained in same PAS file. So, we could return to elegant units concention as opposed to idiotic project conception.

  4. I agree with commenter Jolyon Smith that a shared undo stack for the form designer and the code editor might be difficult to manage elegantly, but the essential problem he describes can already occur in the code editor alone, which is simply that after performing tasks A, B and C one might wish to undo only task B.

    However, whereas in the code editor one would probably work around the problem by caching the code affected by task A in the clipboard or an auxiliary edit buffer before undoing, such techniques are more difficult and error-prone if both code and UI changes are involved. This makes it tempting to resort to Jolyon’s dodge of providing undo only during an uninterrupted session in the form designer (or better, until manual changes are made in the code editor).

    Perhaps in the short term that’s the best that could be hoped for, but a system that allows undoing out of sequence where possible should be the eventual goal. Ideally, you could work freely within the undo history of an entire project across multiple sessions.

    • To undo the changes from DFM only requires AI in order to keep the things “safe” (ie. to have always in sync the DFM and PAS). πŸ™‚ This is the main problem. Jolyon proposal can be extended to ‘reset the Form’s Undo buffer at the first “manual” change in the editor’. Deleting the buffer by just switching is way to much. But also this proposal while is more relaxed than Jolyon’s doesn’t give any advantage over my proposal.

      Also I rather see a single Undo Stack / DFM+PAS entity. But the items in this stack have different types. Hence the structure will be something like (not optimized, just an example):

      type
      
      TUndoItemType = (uitDFMPASSnapshot, uitEditor);
      
      TUndoItem = record
        Kind: TUndoItemType;
        GeneralSnapshotData: //whatever...
        EditorUndoData: //the undo structure which we have now
        //...etc.
      end;
      
      
  5. Indeed, there are more ways to change DFM:
    1) Experts can change properties of DFM. F.e. Gexperts expert – “Replace component”, CnPack expert – “Autocorrect properties” and many others.
    2) The other thing, which is more complicate (imho) is changing components’ properties from another form.
    F.e.
    a) form TForm1 contains component ClientDataset1
    b) form TForm2 contains components: DbGrid1 and DataSource1. DbGrid1 is linked to DataSource1, but DataSource1 is linked to TForm1 ClientDataset1.

    It is possible to change any property of TForm1.ClientDataset1 while TForm1 is not opened in designer. One should open TForm2 and expand DataSource1’s property Dataset.

    • 1. I wonder how the Editor’s Undo works in these cases. But, anyway, as you said this isn’t a problem. The problem (perhaps) is…
      2. …so we need also in our TUndoItem an ‘Entity: string’ member which will hold the changed entity name (Form1 in your case). I think that in the same way in which is intuitive to change from the Form2 things in the Form1, in the same way we must do our Undo. Imho, we must follow the same logic. But yes, here is a question to make: We need separate Undo stacks / Form isn’t it? And then…

      • I really believe that today Delphi have no time to implement brialliant, but hard to make ideas and approaches.
        I prefer simple ones that work. Proposed spanshot idea works in all situations and having separate undo stcks is impossible due to simple logic – it can lead correct project to absolutely uncorrect and unknown one one by pressing undo (as you don’t control all dependencies).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s