How To Undo?
For controls that is based on windows standard edit control, the undo feature is easy to add. Since that control already implement correct behavior when receiving windows message EM_UNDO. Upon receiving this message, the edit control must undo its last operation. Visit this msdn page to get details on EM_UNDO message.
And it is fortunate for us that Delphi TCustomEdit is actually a wrapper for windows standard edit control. So to undo we only have to send EM_UNDO message. While the direct implementation is not difficult, it could be a line like this:
Edit1.Perform(EM_UNDO, 0, 0);
but it's a bit complex to make it user friendly. First we have to be able to undo when user press undo shortcut (Ctrl+Z) and also we have to provide the ability to undo in main menu and/or tool bar. As a good coder (at least we want to be one, right? ) we prefer not to have exactly same code cluttered in different places. That's harder to maintain.
In this case I really recommend to use Delphi's TAction class to help us. You can execute TAction using keyboard shortcut. You can assign TAction to a menu item and they will automatically synchronize each other (e.g. you can change the enabled property of the TAction and menu items assigned with the TAction will automatically reflect the Enabled value). This also the same with toolbar button. So TAction is our perfect candidate to help us implementing undo feature.
Now create a new application, drop and arrange a few TEdit and a few TMemo into the main form. To get our undo TAction you can either use TActionList (from Standard tab) or TActionManager (from Additional tab). For this demo project I prefer to use TActionList for its simplicity. However for large projects I recommend to use TActionManager.
After you drop a TActionList into the main form, double click it. An editing dialog will show. Click the New action button to add new TAction. give the new TAction name of actUndo. Set its Caption property into &Undo. Set its Shortcut property into Ctrl+Z. And add the following codes to its OnExecute event.
procedure TForm1.actUndoExecute(Sender: TObject); begin if Self.ActiveControl is TCustomEdit then TCustomEdit(Self.ActiveControl).Perform(EM_UNDO, 0, 0); end;
The above codes will be executed whenever the TAction got executed. The code will first see what is the control currently active (having focus) in the form. If the active control is a TCustomEdit, then the code will tell it to perform as if it receives EM_UNDO message.
Note that up until now we have implement the Ctrl+Z requirement (since we have set the shortcut property of actUndo into Ctrl+Z). Now is time to show it in menu and toolbar.
To show it in main menu, drop TMainMenu (found it in Standard tab) component into the form. Double click to open menu editor, and add an Edit menu, and under it add a new menu item. For this new menu item you actually don't have to do any adjustment. Just set its Action property into actUndo and its other properties will be shynchronized. Its caption will automatically set to Undo for example. Really neat, eh?
To show it in a toolbar button, drop a TToolBar (found in Win32 tab) component into the form. Righ click on it and select New Button menu item to add new button. And again you only need to set the new button's Action property to actUndo, and everything will automatically be synchronized by Delphi.
Can We Undo?
There is another feature that I think interesting to accompany our undo feature. It's to detect if we can undo or not. Sometimes users want to know whether they have done some modifications or not. By telling whether they can undo or not is the same like telling them that they have done some modifications.
We will be using EM_CANUNDO windows message to find out if we can undo or not. This msdn article will give you more details on this windows message.
Here is the basic code to detect if a TCustomEdit could undo or not.
function IsCanUndo(AEdit: TCustomEdit): Boolean; begin Result := AEdit.Perform(EM_CANUNDO, 0, 0) <> 0; end;
When to detect? The Application's OnIdle event is the best place to detect. So drop an TApplicationEvents (found in Additional tab) into the form, and add the following code for its OnIdle event.
procedure TForm1.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean); begin // check if we can undo if Self.ActiveControl is TCustomEdit then actUndo.Enabled := TCustomEdit(ActiveControl).Perform(EM_CANUNDO, 0, 0) <> 0 else actUndo.Enabled := False; end;
See that with this code we can see that every component associated with actUndo will automatically enabled and disabled according to actUndo state.
Let's see how our demo project executes. The following image show you the demo project when it's first run. See that the undo feature is disabled since we have detected that the current edit control (Edit1) does not able to undo.
Now let's activate Memo1 and edit its content. See that as soon as the content of Memo1 is changed, the Undo button will be enabled.
Now if we activate our undo feature (either by pressing Ctrl+Z, or click on the undo button, or click on menu Edit - Undo), we will undo whatever we have typed into Memo1 control previously.
See now how easy to implement undo feature in Delphi applications?
Full source code is attached. Fell free to use or improve it. I welcome any kind of feedback.
Edited by LuthfiHakim, 13 May 2012 - 09:12 AM.