简体   繁体   English

Delphi并防止事件处理

[英]Delphi and prevent event handling

How do you prevent a new event handling to start when an event handling is already running? 当事件处理已经运行时,如何防止新的事件处理启动?

I press a button1 and event handler start eg slow printing job. 我按下按钮1和事件处理程序启动例如慢速打印作业。 There are several controls in form buttons, edits, combos and I want that a new event allowed only after running handler is finnished. 表单按钮,编辑,组合中有几个控件,我希望只有在运行处理程序完成后才允许新事件。

I have used fRunning variable to lock handler in shared event handler. 我已经使用fRunning变量来锁定共享事件处理程序中的处理程序。 Is there more clever way to handle this? 有更聪明的方法来处理这个问题吗?

procedure TFormFoo.Button_Click(Sender: TObject);    
begin
  if not fRunning then
  try
    fRunning := true;
    if (Sender = Button1) then // Call something slow ...
    if (Sender = Button2) then // Call something ...
    if (Sender = Button3) then // Call something ...
  finally
    fRunning := false;
  end;
end;

Another option (that does not require a flag field) would be to temporarily assign NIL to the event: 另一个选项(不需要标志字段)将临时为事件分配NIL:

procedure TForm1.Button1Click(Sender: TObject);
var
  OldHandler: TNotifyEvent;
begin
  OldHandler := (Sender as TButton).OnClick;
  (Sender as TButton).OnClick := nil;
  try
    ...
  finally
    (Sender as TButton).OnClick := OldHandler;
  end;
end;

For convenience sake this could be wrapped into an interface: 为方便起见,这可以包装在一个界面中:

interface

function TempUnassignOnClick(_Btn: TButton): IInterface;

implementation

type
  TTempUnassignOnClick = class(TInterfacedObject, IInterface)
  private
    FOldEvent: TNotifyEvent;
    FBtn: TButton;
  public
    constructor Create(_Btn: TButton);
    destructor Destroy; override;
  end;

constructor TTempUnassignOnClick.Create(_Btn: TButton);
begin
  Assert(Assigned(_Btn), 'Btn must be assigned');

  inherited Create;
  FBtn := _Btn;
  FOldEvent := FBtn.OnClick;
  FBtn.OnClick := NIL;
end;

destructor TTempUnassignOnClick.Destroy;
begin
  FBtn.OnClick := FOldEvent;
  inherited;
end;

function TempUnassignOnClick(_Btn: TButton): IInterface;
begin
  Result := TTempUnassignOnClick(_Btn);
end;

to be used like this: 像这样使用:

procedure TForm1.Button1Click(Sender: TObject);
begin
   TempUnassignOnClick(Sender as TButton);
   ...
end;

You don't have to do this at all, since all of this is happening in the main (VCL) thread: No other button (VCL) event can be entered until the previous (VCL) event handler has returned... The simultaneous execution of another event handler could only happen unexpectedly if some other thread was preemptively entering a second button event (before the first one has completed), but that can't happen, since there is only one VCL thread. 您根本不需要执行此操作,因为所有这些都发生在主(VCL)线程中:在上一个(VCL)事件处理程序返回之前,不能输入其他按钮(VCL)事件...如果某个其他线程抢先进入第二个按钮事件(在第一个按钮事件完成之前),则只能意外地执行另一个事件处理程序,但这不会发生,因为只有一个VCL线程。

Now if the lengthy thing you are doing is done in another thread because you don't want it to block the GUI, then you can simply set the Button.Enabled property to false until your processing is done. 现在,如果您正在做的冗长的事情是在另一个线程中完成的,因为您不希望它阻止GUI,那么您可以简单地将Button.Enabled属性设置为false,直到您的处理完成。
And if you decide to just stick in the button event until everything has completed, use application.processmessages frequently enough in your processing loop to prevent the gui from freezing. 如果您决定只按下按钮事件直到所有内容都完成,请在处理循环中经常使用application.processmessages以防止gui冻结。 In which case, yes, you must disable the original button to prevent reentry. 在这种情况下,是的,您必须禁用原始按钮以防止重新进入。

Your solution is OK. 你的解决方案没问题。 You can also link button clicks to actions and enable/disable actions in TAction.OnUpdate event handler, but you still need fRunning flag to do it. 您还可以将按钮单击链接到操作并在TAction.OnUpdate事件处理程序中启用/禁用操作,但您仍需要fRunning标志来执行此操作。 The "if no fRunning" line may be not nessesary here, but I don't removed it because it is more safe: “如果没有fRunning”这一行可能不是必须的,但我没有删除它,因为它更安全:

// Button1.Action = acButton1, Button2.Action = acButton2, etc

procedure TForm1.acButtonExecute(Sender: TObject);
begin
  if not fRunning then

  try
    fRunning:= True;
    if (Sender = acButton1) then // Call something slow ...
    if (Sender = acButton2) then // Call something ...
    if (Sender = acButton3) then // Call something ...
  finally
    fRunning:= False;
  end;

end;

procedure TForm1.acButtonUpdate(Sender: TObject);
begin
  (Sender as TAction).Enabled:= not fRunning;
end;

As Gerry already mentioned in one of the comments, you can disable entire form: 正如Gerry在其中一条评论中提到的,您可以禁用整个表单:

procedure TFormFoo.Button_Click(Sender: TObject);    
begin
  try
    Enabled := False;
    //...
  finally
    Enabled := True;
  end;
end;

If your app is a single-threaded one, then while your event-handler code is running, your app cannot run other codes, so all calls to that event-handler will be serialized, and you don't need to be worried. 如果您的应用程序是单线程的,那么当您的事件处理程序代码运行时,您的应用程序无法运行其他代码,因此对该事件处理程序的所有调用都将被序列化,您无需担心。

If your event-handler is running any asynchronous job, then you can use the technique you presented in your question. 如果您的事件处理程序正在运行任何异步作业,那么您可以使用您在问题中提供的技术。

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

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