Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 e.g. slow printing job. 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. 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;
like image 314
pKarelian Avatar asked Mar 07 '10 09:03

pKarelian


3 Answers

Another option (that does not require a flag field) would be to temporarily assign NIL to the event:

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;
like image 92
dummzeuch Avatar answered Nov 15 '22 09:11

dummzeuch


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. The "if no fRunning" line may be not nessesary here, but I don't removed it because it is more safe:

// 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;
like image 26
kludg Avatar answered Nov 15 '22 10:11

kludg


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.

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.
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. In which case, yes, you must disable the original button to prevent reentry.

like image 29
filofel Avatar answered Nov 15 '22 11:11

filofel