4
votes

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.

2
What's this code supposed to do? while not PostMessage(Handle,WM_USER + 10,1234,5678) do; Why do you think you need it?Cosmin Prund
I added a timer, label and menu to a form. I implemented a timer event handler that incremented a counter and displayed it in the label. The label kept on changing when my menu was dropped down, and when the form drags. I've no idea what your problem is. You need to make it clearer but adding enough code for us to be able to understand and reproduce your issue.David Heffernan
This code processes serial communication over and over in milliseconds. It communicates with an external hardware through DB9 serial port. The serial messages are processed as they are sent out as a window messages. So, the application itself doesn't need to directly process them and overload other part of the application or worse slow it down.ThN
That PostMessage loop is a really bad idea. If PostMessage fails, what rational reason do you have to expect that immediately trying it again would succeed? If it fails, do as the documentation tells you and call GetLastError to find out what's wrong. (But if the problem you're reporting occurs even when that line is absent, then remove it from the question since it's irrelevant.)Rob Kennedy
Cannot duplicate either. But note "The WM_TIMER message is a low-priority message. The GetMessage and PeekMessage functions post this message only when no other higher-priority messages are in the thread's message queue." from msdnSertac Akyuz

2 Answers

6
votes

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.

0
votes

This is to be expected. The Windows timer is based on a message queue flag, and it is the lowest priority event. When you start resizing a window, the timer flag in the main application event queue stops being processed, and the timer will stop. Nothing you can do about that. To be clear, ordinary messages will still work, but the timer stops. This is very easy to test.

Now, to resolve this, you can either send yourself WM_USER messages, which should get through, or the better solution is to use a thread to do the critical operation and use the timer to update UI from information that the thread updates. Then when the user resizes the window, you get a UI pause, but the operations are continuing.