-->

Delphi idle handler only fires when I move the mou

2019-05-09 14:55发布

问题:

I have an OnIdle handler in my D2006 app. With this code:

procedure TMainForm.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);

begin
Inc (IdleCalls) ;
Sleep (10) ;
Done := False ;
end ;

the app runs smoothly, the idle handler is called 100 times per second, and the CPU usage is next to zero.

I then added a TActionList and connected up some controls to actions, coded an Execute and Update handler.

procedure TMainForm.ActionNewButtonExecute(Sender: TObject);
begin
DoNewProject ;
end ;

procedure TMainForm.ActionNewButtonUpdate(Sender: TObject);
begin
ActionNewButton.Enabled := AccessLevelIsSupervisor ;
end;

Problem. The OnUpdate event doesn't fire. On a hunch I set Done := true in the OnIdle handler and the OnIdle handler is then only called when I move the mouse. And the Update action still doesn't fire.

Why might the Update handler not be firing, and should I set Done to true or false? Or both?

回答1:

As mentioned in the comments, Sleep in the idle handler will do no good, also the bacground processing will stall if there is no activity on the application.

You can however lower the CPU usage w/o much disturbing effects:
After processing all OnIdle events, the application will call WaitMessage (which will sleep while the message queue is empty), if the Done parameter is True - you can just unconditionally set it in your handler.

As for background processing, use either a thread and call back to the main thread via Synchronize or, if you really-really have to, use a timer and don't ever forget to handle reentrancy (both solutions will by the way wake the application even while WaitMessage).



回答2:

Use the source, Luke. :)

Look at the Forms unit, specifically TApplication.Idle. It contains, in part, the following:

Done := True;
try
  if Assigned(FOnIdle) then FOnIdle(Self, Done);
  if Done then
    if FActionUpdateDelay <= 0 then
      DoActionIdle
  // Excluded to avoid copyright violation
  // See also the else portion, which contains (in part)
  else
    if IdleTimerHandle = 0 then
    begin
      IdleTimerHandle := SetTimer(0, 0, FActionUpdateDelay, IdleTimerDelegate);
      if IdleTimerHandle = 0 then
        DoActionIdle
    end;
finally
  // Omitted
end;

As you can see, DoActionIdle is only called when either Done = True and FActionUpdateDelay <= 0 or IdleTimerHandle = 0. DoActionIdle (also part of TApplication) is what calls UpdateAction. So if neither of the above conditions are met, TAction.OnUpdate is never called.

There's a separate method, TApplication.DoMouseIdle, that you may want to peruse as well.



回答3:

Get rid of that OnIdle event handler, you accepted it is there just in case.

If you later need to perform background tasks, learn how to use threads. To get a specific frequency, you're allowed to use sleep or any other technique within a thread.

My advice is in this way because, as you see, that way of do things is interfering with other parts of your application. If it is a bug in the TApplication, I don't know, maybe it is. If you want to investigate more, make a copy of your project, check everything and if you think this have to work another way, fill a QC entry about that.

I was looking the XE source code and it seems Ok, they set an event to update the actions if the Idle event is not done.. I don't see a bug there. I have no pre-2010 ready installations to check ancient versions.