简体   繁体   English

Delphi:如何在不调用onchange事件的情况下在TEdit / TMaskEdit中设置文本

[英]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 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. 如何更改文本框中的文本并回避onchange事件。

I have used something like changing the OnChange handler, but more often, I use a flag. 我使用过类似OnChange处理程序的东西,但更常见的是,我使用了一个标志。

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.) 另外,我不是硬编码OnChange实际的OnChange过程,而是存储Edit控件的当前值,然后重置它(如果没有设置它会工作,或者如果另一个地方改变了它,等等)

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. 您可以考虑使用对象来管理事件的NIL并恢复以前安装的事件处理程序。 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: 这将提供比SetTextWithoutOnChange ()例程更可重用的实用程序:

  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. 或者,为了使它更方便使用和消除“ with ”,我会使TSuspendEvent类成为一个接口对象,并将其用于一个函数,该函数产生一个可以允许“生活在范围内”的接口引用, 以我的AutoFree()实现为例。 In fact, you could use AutoFree () as-is to manage this already: 实际上,您可以使用AutoFree ()来管理它:

  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: 如果您只是将TSuspendEvent包装在它自己的接口生成函数中,遵循与AutoFree ()相同的模式,您可以进一步简化为:

  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. 据我所知,如果你的对象的OnChange被设计为在Text属性被更改时触发,你必须坚持将事件设置为nil temporrly。 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. 问题是onChange事件本身。 I don't use it at all for text fields. 我根本不使用它来处理文本字段。

remove all OnChange and use the OnExit instead and tie it to the OnKeyUp. 删除所有OnChange并使用OnExit,并将其绑定到OnKeyUp。

All Edits have a common ancestor TCustomEdit. 所有编辑都有一个共同的祖先TCustomEdit。

I generally use one method called CustomEditKeyUp and point all the edits on a form to this single method (TEdit, TLabeledEdit etc etc.) 我通常使用一个名为CustomEditKeyUp的方法,并将表单上的所有编辑指向此单个方法(TEdit,TLabeledEdit等等)

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. 出于某种原因,OnExit在TCustomEdit中是私有的,因此需要Hack。 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. 如果你知道编辑是来自OnExit公开的不同路线,那么如果采用不同的方式进行编辑并且不需要Hack。

Then For each Edit control, use a specific OnExit 然后对于每个Edit控件,使用特定的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' 如果你想以编程方式更改值而不激活'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) 另一种方法是使用Delphi 8中引入的Class Helpers。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';

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM