Now is the time to talk about time counting. As with the VCL, KOL has a TTimer object for this. It, when activated, creates a system object that regularly invokes the designated timer handler. In fact, such a timer is bound to one of the windows: by default, to a window specially created for all timers of the main thread, or, if the TIMER_APPLETWND symbol is specified in the application, then to the applet window (or the main form if the applet is not used).
Why am I writing all this. First, it is clear from what has been said that there are no guarantees that such a watch will "tick" with the highest accuracy. The time lag between invocations of the event handler assigned to the timer may not be exactly the same. And this essentially depends, among other things, on the speed of the system, on the degree of its workload with various tasks, on the requested response period. In particular, such a timer is unlikely to be triggered more often than once every 50 milliseconds, i.e. more often than 20 times per second (1 millisecond = 0.001 seconds, i.e. 1000 milliseconds are included in a second, if anyone has forgotten).
Second, a message from the system is first queued and then processed. If your own task is busy with any long calculations (or waiting), it will not be able to handle this event from a simple timer until control returns to the message loop, or one of the methods like ProcessMessages is called.
Third, it is obvious that in order for a timer that works through window messages to work, it needs at least one window. In case the application has no windows at all (for example, if you are creating a console application), a special window (TimerOwnerWnd) is created for timers by default. But the window handle can be saved by specifying the TIMER_APPLETWND conditional compilation symbol in the project options. In this case, the applet window will be used (which may sometimes be the same as the main form window). The timer message handler is "attached" to this window. But if there is no such window, then the TTimer object will not be used.
|
Note that if, in the case of a multithreaded application, the first timer is "started" (by setting its Enabled property to TRUE) not in the context of the main thread, and the conditional compilation symbol TIMER_APPLETWND is not defined in the project, then the special TimerOwnerWnd window that "owns" the timer will be created in the context of the current one, i.e. not the main thread. As a result, if there is no message loop running on that thread, your timers will never fire. I was somehow “lucky” to get this very rare combination of conditions, after which I had to puzzle for a long time what was wrong. In my case, the problem was solved by adding the TIMER_APPLETWND symbol, but in principle, it can be solved by immediately starting (and stopping, if not really needed) some trial timer in the main thread, for example, in the OnFormCreate handler. |
I would like to note that in order to ensure minimal code, the _NewWindowed call is used to create such a window, and for this reason the window is not a clean window for receiving only messages. It becomes the so-called topmost window, and can receive broadcast messages. If there is a separate Applet object in the application and the OnMessage handler is installed in it, this handler will receive, among other things, all messages intended for this invisible window. This means, in particular, that system broadcasts will be intercepted one time more than you have forms in the application. Conclusion: parse the handle field of the incoming message to find out which window it is intended for, if required.
However, for all its drawbacks, an important advantage of such an imprecise timer is its relative safety. Its timer handler is called on the same thread (thread) of commands in which the code of other event handlers is running. That is, in the case of a single-threaded application, event handlers never intersect at all, because each of them, including the timer handler, can be considered executing "continuously" within the task. Of course, the system can interrupt it and switch to another task, but then it will still return control to this particular code when it returns control to your application.
Clock constructor:
NewTimer(i)- creates a TTimer object with an interval of i milliseconds, returning a pointer of the PTimer type. The timer is initially created inactive. To run it, you need to set its Enabled property to true.
Timer properties, methods and event:
Handle - descriptor of the hTimer system object, i.e. a number that allows the system to identify this object in low-level API requests. This descriptor contains the value 0 if the timer is currently inactive (the system object is only created when the timer starts);
Enabled - timer activity. This property can be used to start or restart the timer (to restart the clock, you must first stop the clock, that is, set the Enabled property to false);
Interval - timer interval. When this value is changed, when the object is active, the timer is "reset", i.e. the system object is recreated, and the countdown to the next triggering starts over;
OnTimer - timer event. In the event handler, you are allowed to change any properties of the timer object, including the interval, or the active state. For example, if you want the timer to fire once instead of regularly, you would add code to the handler to set the Enabled property to false.
The TTimer object MCK has a mirrored TKOLTimer component. But it allows you to generate code for more than just a simple clock object. When the multimedia timer object was developed, I decided to use the same mirror component to generate its code, especially since the TTimer and TMMTimer objects are very similar (see below). As a result, the TKOLTimer mirror has been enriched with a number of properties that cannot be used when generating code for a regular timer, and are simply ignored, namely: Periodic, Resolution.