Fixing FireMonkey: TMemo/TEdit – OnExit

Currently in the Delphi XE3 release there is a bug in the OnExit processing of TMemo and TEdit (possibly others) controls.

Issue

If you make a change in a TEdit or TMemo control and then exit the control, after the OnExit event is called, another OnChange event is called. This shouldn’t happen.

This doesn’t occur in the VCL framework and shouldn’t happen in FireMonkey.

Solution

TEdit

The fix for the TEdit cause is pretty simple. Since the FNeedChange field in TCustomEdit is protected we can use a class cracker to fix it. You could put the fix into either a new control or in either an OnExit or OnChange handler in your application.

type
  TEditClass = class(TEdit);
...
procedure Form1.OnExit(Sender: TObject);
begin
  TEditClass(Edit1).FNeedChange := False;
end;

It’s interesting to note for TEdit, FNeedChange is protected. This is because in the TCustomEditBox descendant (used by TSpinBox and TNumberBox controls), the FNeedChange field is set to False after the overriden Change method is called. Perhaps this should have triggered the developer making that change, to actually fix the issue in the ancester class.

TMemo

The fix for TMemo is more interesting because unlike the TCustomEdit class, FNeedChange is private. Thankfully extended RTTI comes into play.

I put this code where the memo content was saved to disk, you could put the code in the same spot in your applications (if applicable), or place it in either OnChange or OnExit events.

var
  LCtx: TRTTIContext;
  LField: TRttiField;
  LInst: TRttiInstanceType;
begin
  // save your memo contents
  LInst := LCtx.GetType(Memo1.ClassType).AsInstance;
  LField := LInst.GetField('FNeedChange');
  if LField <> nil then
    LField.SetValue(Memo1, False);
end;

If your save is triggered by the user selecting a control that takes focus from the memo, the OnExit event will trigger before executing the fix above. Under these circumstances, moving the fix to the OnExit event of the memo is advised.

Another TMemo Issue

The OnChange event is only triggered after typing two characters in a TMemo, the first character typed into the memo doesn’t trigger the OnChange event.

2 Comments

  1. Andreas Hausladen says:

    > Perhaps this should have triggered the developer making that change, to actually fix the issue in the ancester class

    Maybe it was the same developer who worked on the VCL Styling code where “he” added code to paint the TGraphicControl child controls in 3 components instead of painting them in the base class so that they would have been painted in all controls.

  2. Steve says:

    >> Perhaps this should have triggered the developer making that change, to actually fix the issue in the ancester class
    >
    > Maybe it was the same developer who worked on the VCL Styling code where “he” added code to paint the TGraphicControl child controls in 3 components instead of painting them in the base class so that they would have been painted in all controls.

    More of an answer to Andreas’s comment:
    Seems a symptom of Agile / Scrum Software Development (at least from my experience) (and as far as I know, I think FM is built this way too), where there is little context of the “big-picture” of the product or feature and have different people working on similar types of code, that should ideally have been done by one person as they all related. So instead of implementing a feature more elegantly in one place (such a base class), instead similar code is duplicated all over the place, because there are 3 Scrum stickies that say…
    “1) Implement TGraphicControl painting in class X”, “2) Implement TGraphicControl painting in class Y” and “3) Implement TGraphicControl painting in class Z”.

    And to improve efficiency to let 3 different developers of different skills each implement it in “parallel” (each unaware what method the other is using – and end up with 3 different pieces of code (and quality) to really one problem… to draw a TGraphicControl 🙂 … so rather than see it’s the same type of code really, that should be implemented by one person so that you end up with a more consistent and elegant solution 🙂