Delphi: How to set text in TEdit/TMaskEdit without invoking the onchange event
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';
精彩评论