views:

313

answers:

3

I've got a pretty big setup form which I'd like to populate with data 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.

+8  A: 

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;
jasonpenny
Could you not also use the Sender parameter? Presumably he wants the OnChange to fire when someone is inputting text into the box itself but not when populating it from a separate procedure, like an auto-populate of sorts. In that case, am I right in thinking you could have TForm1.OnChange(Sender:TObject) and then do "if Sender as TEdit" (do stuff) else (do nothing)"? Then you don't have all of the extra setting and unsetting of variables in your code.
Justin
No. The Sender of the OnChange event will always be the TEdit, regardless of whether the Text is updated by the user or programmably. The VCL never sets the Sender parameter to nil.
Remy Lebeau - TeamB
+4  A: 

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;
AlexV
Added SetTextWithoutOnChange procdeure to answer.
AlexV
+2  A: 

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']);
Deltics
wow, that seems like a lot of work although probably worth it. I'll remember this and probably implement it some day, thanks!
Peter Turner
It's not a lot of work, really (it took about 3 minutes, and "AutoFree" took about 10 minutes). And the result is savings that will continue to accrue over the life time of the projects in which you then use the results (the code is entirely generic - small 'g').
Deltics