Link to home
Create AccountLog in
Avatar of akb
akbFlag for Australia

asked on

Timer not accurate when window not in focus

I have an application which toggles the scroll lock on the keyboard to sound a countdown siren for a sporting event.  I have the siren hooked up to the scroll lock LED.  As the time approaches the minute, I sound the siren for 200ms each second, then for 1000ms on the minute.  I switch on the LED, then use a timer to time the 200ms or 1000ms.  When the OnTimer procedure is called, the LED (and thus the siren)
is switched off.  When the program window has focus, this works perfectly.  When it does not have focus, the timer is inaccurate and the LED stays on for a second or more.
Is there any way I can make the timer more accurate when the window does not have focus?
I have tried setting the priority of the task in Task Manager to Real Time and High, but this makes no difference.
I have tried running this application on a very fast processor and it still does not work.
Avatar of MerijnB
MerijnB
Flag of Netherlands image

you might want to look at timeSetEvent, which is a high resolution timer with more precision and priority.

See here for an example: http://www.swissdelphicenter.ch/torry/showcode.php?id=216
Avatar of wildzero
wildzero

Also Jedi has a timer component which works better.
Avatar of akb

ASKER

WildZero, the link you gave me takes me to an advertisement for Registry CleanFixer".
Avatar of akb

ASKER

Thanks MerijnB. It's very late here so I'll take a closer look in the morning/
ok, sleep tight
UI messages are never that accurate. You might want to cosider using waitable timer objects instead, see e.g. http://msdn2.microsoft.com/en-us/library/ms687008(vs.85).aspx ("Using Waitable Timer Objects" - sorry, C/C++ sample)
Hi there,

I didn't post any link.  experts-exchange linked the keyword to an ad....
Avatar of akb

ASKER

OK. Sorry WildZero. I thought it was your link. Was not thinking clearly as it was very late. Thanks. I will google jedi.
Waitable timer objects should work fine for you. See also http://delphi32.blogspot.com/2006/03/using-waitable-timer-in-delphi.html 
Avatar of akb

ASKER


unit StartClock_Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Winprocs, WinTypes,
  DateUtils,
  Dialogs, StdCtrls, ExtCtrls;
 
 
type
  TKeyType = (ktCapsLock, ktNumLock, ktScrollLock);
  TForm1 = class(TForm)
    LabelTime2: TLabel;
    PanelChangeColour: TPanel;
    TimerTime: TTimer;
    TimerSiren: TTimer;
    LabelTime1: TLabel;
    procedure FormCreate(Sender: TObject);
    Procedure HideTitlebar;
    procedure FormResize(Sender: TObject);
    procedure PanelChangeColourClick(Sender: TObject);
    procedure TimerTimeTimer(Sender: TObject);
    procedure Timer();
    procedure Siren(Duration:integer);
    procedure TimerSirenTimer(Sender: TObject);
  private
    { Private declarations }
  public
    Cnt2 : Integer;
    HoldFormWidth  : integer;
    { Public declarations }
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
 
procedure SetLedState(KeyCode: TKeyType; bOn: Boolean);
// SetLedState(ktCapsLock, True);  // CapsLock on
// SetLedState(ktNumLock, True);  // NumLock on
// SetLedState(ktScrollLock, True);  // ScrollLock on
var
 KBState: TKeyboardState;
 Code: Byte;
begin
 Code:= 0;
 case KeyCode of
   ktScrollLock: Code := VK_SCROLL;
   ktCapsLock: Code := VK_CAPITAL;
   ktNumLock: Code := VK_NUMLOCK;
 end;
 GetKeyboardState(KBState);
 if (Win32Platform = VER_PLATFORM_WIN32_NT) then
 begin
   if Boolean(KBState[Code]) <> bOn then
   begin
     keybd_event(Code,
                 MapVirtualKey(Code, 0),
                 KEYEVENTF_EXTENDEDKEY,
                 0);
 
     keybd_event(Code,
                 MapVirtualKey(Code, 0),
                 KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP,
                 0);
   end;
 end
 else
 begin
   KBState[Code] := Ord(bOn);
   SetKeyboardState(KBState);
 end;
end;
 
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  SetLedState(ktScrollLock, False   );  // ScrollLock off
  HideTitleBar;
  Form1.WindowState:=wsMaximized;         // Cover the whole screen
  HoldFormWidth  := Form1.Width;
  Timer();
  LabelTime1.Visible:=True;
  LabelTime2.Visible:=True;
  Cnt2:=0;
end;
 
 
Procedure TForm1.HideTitlebar;
Var
  Save : LongInt;
Begin
  If BorderStyle=bsNone then Exit;
  Save:=GetWindowLong(Handle,gwl_Style);
  If (Save and ws_Caption)=ws_Caption then Begin
    Case BorderStyle of
      bsSingle,
      bsSizeable : SetWindowLong(Handle,gwl_Style,Save and
        (Not(ws_Caption)) or ws_border);
      bsDialog : SetWindowLong(Handle,gwl_Style,Save and
        (Not(ws_Caption)) or ds_modalframe or ws_dlgframe);
    End;
    Height:=Height-getSystemMetrics(sm_cyCaption);
    Refresh;
  End;
end;
 
 
procedure TForm1.FormResize(Sender: TObject);
begin
//   if HoldFormWidth<>0 then begin
//     LabelTime1.Font.Size :=  ( LongInt(LabelTime1.Font.Size) * LongInt(Form1.Width) div LongInt(HoldFormWidth) ) ;
//     HoldFormWidth := 0;
//   end;
 
end;
 
procedure TForm1.PanelChangeColourClick(Sender: TObject);
begin
  if LabelTime1.Color = clWhite then
  begin
    PanelChangeColour.Color := clBlack;
    Form1.Color := clBlack;
    LabelTime1.Color := clBlack;
    LabelTime1.Font.Color:=clWhite;
    LabelTime2.Color := clBlack;
    LabelTime2.Font.Color:=clWhite;
  end
  else
  begin
    PanelChangeColour.Color := clWhite;
    Form1.Color := clWhite;
    LabelTime1.Color := clWhite;
    LabelTime1.Font.Color:=clBlack;
    LabelTime2.Color := clWhite;
    LabelTime2.Font.Color:=clBlack;
  end;
end;
 
procedure TForm1.TimerTimeTimer(Sender: TObject);
begin
  Timer();
end;
 
procedure TForm1.Timer();
var
  Secs : Integer;
begin
  if (MillisecondOf(now())<>0) or (TimerTime.Interval<>1000) then
  begin
    TimerTime.Enabled:=false;
    TimerTime.Interval:=1000-MillisecondOf(now());
    TimerTime.Enabled:=true;
  end;
  Secs :=  StrToInt(FormatdateTime('ss',now()));
  if (Secs>=55) and (Secs<=59) then
  begin
    Siren(200);  // Sound siren for 200 milliseconds
  end;
  if (Secs=0) then
  begin
    Siren(1000);  // Sound siren for 1000 milliseconds
  end;
  LabelTime1.Caption:=FormatdateTime('hh:nn',now());
  LabelTime2.Caption:=FormatdateTime('ss',now());
  Cnt2:=Cnt2+1;
 
end;
 
procedure TForm1.Siren(Duration:integer);
// Sound siren for Duration milliseconds
begin
  TimerSiren.Enabled:=False;
  SetLedState(ktScrollLock, True);  // ScrollLock ON
  TimerSiren.Interval:=Duration;
  TimerSiren.Enabled:=True;
end;
 
procedure TForm1.TimerSirenTimer(Sender: TObject);
begin
  SetLedState(ktScrollLock, False);  // ScrollLock OFF
  TimerSiren.Enabled:=False;
end;
 
end.

Open in new window

Very interesting. I might also use that at our karate tournaments. Mind telling me how you hooked up the hooter to your keyboard?

Anyway, I suggest you run your code in a separate thread. If you post your code, I will modify it to run in it's own thread.

Regards
Pierre
ASKER CERTIFIED SOLUTION
Avatar of Pierre Cornelius
Pierre Cornelius
Flag of South Africa image

Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
Avatar of akb

ASKER

Thanks jkr. I have now tried using waitable timers, but the result is exactly the same.  It seems the problem is simply that when the program does not have focus, it doesn't get enough processing time allocated to it to allow the timers to work even close to acccurately.
Avatar of akb

ASKER

PierreC, would you mind emailing me the whole project please? I'm having trouble making your code work. Thanks.  synergy_andrew AT hotmail.com

Hooking up the hooter is easy.  I'm simply driving a 4.5v relay from the keyboard.  There is a resistor in series with the scroll lock LED.  I just hook the relay in before the resistor so it is receiving the full 5V.  I then use the relay to switch the hooter on and off.
Sorry for the delay Andrew. Was out of town for a funeral. I mailed the source files (same as what was posted above though) to you as requested along with the exe. What problem are you having to compile?

Just a note on the code, you probably noticed that I don't use a timer at all but rather the getTickCount method to keep track of time elapsed. The GetTickCount simply returns the number of milliseconds elapsed since startup.
Did you come right, Andrew?
Avatar of akb

ASKER

Hi Pierre,

Sorry it has taken so long for me to get back to this.  Your solution does work very well.  I have, however, opted for a totally different solution.  I have decided to use sound through the PC's speakers instead of the hooter.  This gives me more options for changing the type of sound, pitch, volume, etc.  I have tested this and it is working well.  The built in timer seems to work ok for this purpose.  I am also toying with the idea of having the computer announce the time each minute, as this is currently done by a human (who often calls it late or forgets altogether).

Thanks again for your assistance.
andrew
Avatar of akb

ASKER

Thanks Pierre
Glad to help