Tuesday, July 5, 2011

VCL TTimer stops when a window dragged or pull down menu clicked

I have TTimer enabled and is supposed to run non-stop forever until the user stop it. However, it doesn't work that way. Within OnTimerevent, it processes window messages over and over again in milliseconds.
For instance, here is a snippet of my code.


procedure TDXCommDlg.Timer2Timer(Sender: TObject);
begin
  inherited;
  if Scanning then
  begin
    Timer1.Enabled := false;
    Timer2.Enabled := false;
     while not PostMessage(Handle,WM_USER + 10,1234,5678) do;
     Timer1.Enabled := true;
  end;
end;
 
 
What happens is this. While the TTimer is enabled and running, you drag any windows of the application or click on pull down menu the TTimer event completely stops working, even though I have taken precautionary steps in other part of the code to prevent this from happening. However, it doesn't seem to be helping.
The only way to restart the OnTimer event is to stop and restart the Timer by the user through TButton event.
The same code or program works fine under Windows XP compiled with Delphi 7. Currently, I am using Windows 7 and Delphi 2010 to rebuild my system.
I will try to give you more information. What I am working on is a copyrighted software.
There is a user defined procedure called HandleMsg. It actually processes the serial port messages. HandleMsg is set to the Application event onMessage;
Application.onMessage:=HandleMsg();
PostMessage is associated with onMessage event of the application.
Whenever PostMessage is called, it fires onMessage event which is set to HandleMsg().


Here more of my code:
 procedure TDXCommDlg.HandleMsg(var
 Msg: TMsg; var Handled: Boolean);
 begin
      Handled := false;
      case Msg.message of
      WM_USER + 10:
              begin
                   if (Msg.wParam = 1111) and (Msg.lParam = 2222) then
                   begin
                     SendLanMessage;
                     Handled := true;
                   end
                   else if (Msg.wParam = 1234) and (Msg.lParam = 5678) then
                   begin
                        SendMessage;
                        Handled := true;
                   end
                   else
                   begin
                     if (Msg.wParam = 4321) then
                     begin
                       MainFrm.CloseWindow(TViewFrm(Msg.lParam).WinCap);
                     end;
                   end;
              end;
      end; { case } end;
HandleMsg() responds to PostMessage. Correct me if I am wrong.


 Answer:

In both cases (starting to size/move window or opening a menu) the last message dispatched from TApplication.ProcessMessage is WM_NCLBUTTONDOWN (or a WM_NCRBUTTONDOWN if caption and system menu exists and clicked on the caption.. or a WM_RBUTTONUP if opening a context menu, etc..). Common to all is they are starting a modal message loop.
For instance the below is from WM_ENTERSIZEMOVE documentation:
The WM_ENTERSIZEMOVE message is sent one time to a window after it enters the moving or sizing modal loop. [....] The operation is complete when DefWindowProc returns.
After a modal message loop is started the HandleMessage call in TApplication.Run will not return until DefWindowProc for the relevant window returns (in the WM_NCLBUTTONDOWN case for instance, the dispatched message will cause a WM_SYSCOMMAND to be sent to the window which will start the modal message loop and will not return until moving/sizing is complete). So you won't be able to use an OnMessage handler of the application in this period which is called in TApplication.ProcessMessage.
Your solution is simple. Instead of using the OnMessage handler, handle the message with a message handler of your form:
const
  WMUSER_10 = WM_USER + 10;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    procedure Timer1Timer(Sender: TObject);
  private
    procedure WmUser10(var Msg: TMsg); message WMUSER_10;
  public
  end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  PostMessage(Handle, WMUSER_10, 1234, 5678);
end;

procedure TForm1.WmUser10(var Msg: TMsg);
begin
  //
end;

Or, put your code in the OnTimer event, since WM_TIMER is itself posted.

No comments: