Have an amazing solution built in RAD Studio? Let us know. Looking for discounts? Visit our Special Offers page!
DelphiRAD Studio

Ignore Changes with Custom Managed Records

Automate Restorable Operations with Custom Managed Records by Erik van Bilsen over on Griggy's Blog

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…

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…

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.

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?

RAD Studio 13.1 Florence Now Available See What's New in RAD Studio 13.1 Delphi is 31 - Webinar Replay

Reduce development time and get to market faster with RAD Studio, Delphi, or C++Builder.
Design. Code. Compile. Deploy.

Start Free Trial   Upgrade Today

   Free Delphi Community Edition   Free C++Builder Community Edition

About author

Director of Delphi Consulting for GDK Software USA. Many software related patents, including swipe and pattern unlock and search engines. First Silver and Gold Delphi badges on Stack Overflow Former Developer Advocate for Embarcadero Technologies. Long time fan of programming, especially with Delphi. Author, Podcaster/YouTuber, Improvisor, Public Speaker, Father, and Friend.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

IN THE ARTICLES