开发者

Delphi: How to set text in TEdit/TMaskEdit without invoking the onchange event

开发者 https://www.devze.com 2022-12-15 10:52 出处:网络
I\'ve got a pretty big setup form which I\'d like to populate with 开发者_StackOverflowdata from a class.so I\'m doing a lot of

I've got a pretty big setup form which I'd like to populate with 开发者_StackOverflowdata from a class. so I'm doing a lot of

Edt1.text := ASettings.FirstThing; 

I'd like to avoid

Edt1.onchange := nil;
Edt1.text := ASettings.FirstThing; 
Edt1.onchange := edt1Onchange;

How do I change the text in a text box and sidestep the onchange event.


I have used something like changing the OnChange handler, but more often, I use a flag.

updatingFromCode := true;
Edt1.Text := ASettings.FirstThing;
updatingFromCode := false;

then

procedure TForm1.OnChange(...);
begin
  if updatingFromCode then
    Exit;
  ...


Also, rather than hardcoding the OnChange the the actual OnChange procedure, I would store the Edit control's current value, then reset it (which will work if it is not set, or if another place has changed it, etc.)

oldOnChange := Edt1.OnChange;
Edt1.OnChange := nil;
Edt1.Text := ASettings.FirstThing; 
Edt1.OnChange := oldOnChange;


You might consider using an object to manage the NIL'ing of the event and restoring the previously installed event handler. It's a little dangerous to assume that the event to be restored just happens to be the one assigned at design-time/which happens to have the "name that fits" - you should always save/restore the currently assigned handler, just to be safe.

This would provide an even more re-usable utility than the SetTextWithoutOnChange() routine:

  TSuspendEvent = class
  private
    fObject: TObject;
    fEvent: String;
    fHandler: TMethod;
  public
    constructor Create(const aObject: TObject; aEvent: String);
    destructor Destroy; override;
  end;

  constructor TSuspendEvent.Create(const aObject: TObject; aEvent: String);
  const
    NILEvent  : TMethod = (Code: NIL; Data: NIL);
  begin
    inherited Create;

    fObject := aObject;
    fEvent  := aEvent;

    fHandler := GetMethodProp(aObject, aEvent);

    SetMethodProp(aObject, aEvent, NILEvent);
  end;


  destructor TSuspendEvent.Destroy;
  begin
    SetMethodProp(fObject, fEvent, fHandler);

    inherited;
  end;

In usage, this would look something like:

  with TSuspendEvent.Create(Edit1, 'OnChange') do
  try
    Edit1.Text := 'Reset!';
  finally
    Free;
  end;

For the "Thou shalt not use 'with' crowd" - by all means declare yourself an additional local variable and use that if it will help you sleep easier at night. :)

Or, to make it even more convenient to use and eliminate "with", I would make the TSuspendEvent class an interfaced object and wrap its use in a function that yielded an interface reference to it that could be allowed to "live in scope", as exemplified by my AutoFree() implementation. In fact, you could use AutoFree() as-is to manage this already:

  AutoFree(TSuspendEvent.Create(Edit1, 'OnChange'));
  Edit1.Text := 'Reset!';

Dsabling events for a period that extends beyond the scope of a single procedure requires more management than any helper utilities are likely to be able to provide in a generic fashion I think, at least not without also having specific means for restoring events explicitly, rather than automatically.

If you simply wrapped TSuspendEvent inside it's own interface yielding function, following the same pattern as AutoFree() you could simplify this further to:

  SuspendEvent(Edit1, 'OnChange');
  Edit1.Text := 'Reset!';

As a final note, I think it should be fairly easy to see how this could be quite simply extended to support suspending multiple events on an object in a single call, if required, for example:

  SuspendEvent(Edit1, ['OnChange', 'OnEnter']);


As far as I know if the OnChange of your object is designed to fire when the Text property is changed you have to stick with setting the event to nil temporarly. Myself, I do it this way (in a try finally):

Edt1.onchange := nil;
try
    Edt1.text := ASettings.FirstThing;
finally
    Edt1.onchange := edt1Onchange;
end;

You could also do some procedure to handle it for you:

procedure SetTextWithoutOnChange(anEdit: TEdit; str: String);
var
    anEvent: TNotifyEvent;
begin
    anEvent := anEdit.OnChange;
    anEdit.OnChange := nil;
    try
        anEdit.Text := str;
    finally
        anEdit.OnChange := anEvent;
    end;
end;


I know this is an old question but I thought I would add my solution that does not involve any of the complicated procedures outlined in the previous answers in case it comes up in another search.

The problem is the onChange event itself. I don't use it at all for text fields.

remove all OnChange and use the OnExit instead and tie it to the OnKeyUp.

All Edits have a common ancestor TCustomEdit.

I generally use one method called CustomEditKeyUp and point all the edits on a form to this single method (TEdit, TLabeledEdit etc etc.)

type THack = class(TCustomEdit);

procedure TForm1.CustomeEditKeyUP(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if (Key=VK_RETURN) and (Sender is TCustomEdit) then
    {This is just to isolate the one occasion when they press the
    enter but don't exit immediately}
    if Assigned(THack(Sender).OnExit) then THack(Sender).OnExit(Sender);
end;

For some reason, the OnExit is private in a TCustomEdit so the Hack is needed. If you know that the edits are from a different route where the OnExit is public, cast if differently and the Hack is not necessary.

Then For each Edit control, use a specific OnExit

procedure TForm1.MyEditExit(Sender: TObject);
begin
  if MyEdit.Modified then
  begin
    {Do Something here}
    MyEdit.Modified := false;
  end;
end;

If you want to change the value programmatically without it firing 'OnExit'

....
MyEdit.Text :='I've changed it'
MyEdit.Modified := false;
....

The big advantage for me is that when I am parsing the input for say a valid number, I only have to do this once when editing is completed and not for every single backspace, delete insert etc all surrounded by try except as the various formating functions error out as they don't understand spaces etc.. For database etc, the number of calls will be greatly reduced.

That's my two penneth.


Another way is by using Class Helpers introduced in Delphi 8. http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Class_and_Record_Helpers_(Delphi)

You could write:

type
  TEditHelper = class helper for TEdit
  public
    procedure SetTextDisableOnChange(const AText: string);
  end;

{ TEditHelper }

procedure TEditHelper.SetTextDisableOnChange(const AText: string);
var
  OnChangeTmp: TNotifyEvent;
begin
  OnChangeTmp:=OnChange;
  try
    OnChange:=nil;
    Text:=AText;
  finally
    OnChange:=OnChangeTmp;
  end;
end;

and then:

EditCtrl.SetTextDisableOnChange('I do not invoke OnChange!');
EditCtrl.Text:='I invoke OnChange';
0

精彩评论

暂无评论...
验证码 换一张
取 消