Handling the OnChange event handler on a TEdit is great until you programmatically change the text and trigger your OnChange event handler again. It gets really fun if you have two TEdit boxes that update each other in their OnChange event handler. I was reading Automate Restorable Operations with Custom Managed Records by Erik van Bilsen over on Griggy’s Blog and I had a great idea 💡 to automate the disabling and re-enabling of the OnChange event handler!
Custom Managed Records (or CMR as Eric calls them) is a super powerful language feature introduced in 10.4 Sydney. It takes the Record data type and adds Initialization and Finalization methods that are automatically called (as well as other features). Since Records are reference counted Finalization method is automatically called when the last reference goes out of scope. Additionally, it uses try..finally
blocks to make sure the finalization method is called.
So instead of writing code like…
1 2 3 4 5 6 7 8 9 10 |
procedure TForm13.Edit1Change(Sender: TObject); begin var savedEvent := Edit2.OnChange; Edit2.OnChange := nil; try Edit2.Text := Rot47(Edit1.Text); finally edit2.OnChange := savedEvent; end; end; |
It can be dramatically simplified to the following, while still having the same effect…
1 2 3 4 5 |
procedure TForm13.Edit2Change(Sender: TObject); begin var Ignorer := TIgnoreEditChanges.Create(Edit1); Edit1.Text := Rot47(Edit2.Text); end; |
It is important that you assign the instance created, otherwise it goes out of scope immediately, and you loose the benefit.
Originally I implemented this to only support TCustomEdit, but then I got to thinking about TMemo and a number of the other components with OnChange that don’t share a common ancestor. Then I realized I’d probably be better off using RTTI (which should make it agnostic to FMX vs VCL) and maybe a fluent syntax with generics . . . so this isn’t fully implemented yet. . . . Use it at your own risk.
Random tip #1 for handling OnChange events in FMX/FireMonkey forms – if you want to be notified immediately of changes, and not only when the user exits the field, you can handle the OnTyping event, or map the two together.
Random tip #2 I’ve posted to GitHub Gist my Rot47 code and the IgnoreEditChanges unit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
unit IgnoreEditChanges; interface uses System.Classes; type TIgnoreEditChanges = record private FEdit: TObject; FOnChange: TNotifyEvent; public constructor Create(const AnEdit: TObject); class operator Initialize(out ADest: TIgnoreEditChanges); class operator Finalize(var ADest: TIgnoreEditChanges); class operator Assign(var ADest: TIgnoreEditChanges; const [ref] ASrc: TIgnoreEditChanges); end; implementation uses FMX.Controls, FMX.Edit, FMX.DateTimeCtrls, FMX.ComboEdit, FMX.Memo; constructor TIgnoreEditChanges.Create(const AnEdit: TObject); begin Assert(Assigned(AnEdit)); FEdit := AnEdit; if FEdit is TCustomEdit then begin FOnChange := TCustomEdit(AnEdit).OnChange; TCustomEdit(AnEdit).OnChange := nil; end else if FEdit is TCustomDateTimeEdit then begin FOnChange := TCustomDateTimeEdit(AnEdit).OnChange; TCustomDateTimeEdit(AnEdit).OnChange := nil; end else if FEdit is TCustomMemo then begin FOnChange := TCustomDateTimeEdit(AnEdit).OnChange; TCustomMemo(AnEdit).OnChange := nil; end else raise EInvalidOperation.Create( 'TIgnoreEditChanges does not support objects of type ' + AnEdit.ClassName); end; class operator TIgnoreEditChanges.Initialize(out ADest: TIgnoreEditChanges); begin ADest.FEdit := nil; end; class operator TIgnoreEditChanges.Finalize(var ADest: TIgnoreEditChanges); begin if Assigned(ADest.FEdit) then begin if ADest.FEdit is TCustomEdit then TCustomEdit(ADest.FEdit).OnChange := ADest.FOnChange else if ADest.FEdit is TCustomDateTimeEdit then TCustomDateTimeEdit(ADest.FEdit).OnChange := ADest.FOnChange else if ADest.FEdit is TCustomMemo then TCustomMemo(ADest.FEdit).OnChange := ADest.FOnChange end; end; class operator TIgnoreEditChanges.Assign(var ADest: TIgnoreEditChanges; const [ref] ASrc: TIgnoreEditChanges); begin raise EInvalidOperation.Create( 'TIgnoreEditChange records cannot be copied.') end; end. |
What cool uses are you finding for Custom Managed Records?
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition