Site hosted by Angelfire.com: Build your free website today!

Home
DelphiZeus
More Timers
MultiMedia and Thread timers

Home



In the BrushPens Program, a Timer was used and the WM_TIMER message was sent to the MessageProc and this caused the Splash Window to change it's text. Using SetTimer( ) is a convient way to get timed events if you want them for intervals of more than about 200 miliseconds. For intervals below 200 milliseconds, the accuracy of the System Timer is very poor. So we will use some alteratives, a Multimedia timer and a Thread timer. This Timers program demonstrates how to use SetTimer( ) with a separate timer message function to do the same type of Splash Screen. Another type of timer, a Multi-Media timer is shown and is used to do animations. The Multi-Media Timer is much more accurate than the System Timer that times the SetTimer( ) function. Another type of timer, a Thread Timer will be created to show you how to create a separate thread and let a SleepEx( ) function fire a timer event.To show the difference in the accuracy of the System and Mutimedia timers, two identical Text Scrolling functions are used, one fired by the System Timer and the other fired by the Mutimedia Timer, so you can see the results in how fast the text is scrolled. In order to measure the real time interval of these timers, I will use the Performance Counter, which can get more than a million counts per second, and can be used to time even the fastest events. A common use for the Mutimedia timer is for graphic animations and sprite movement. I will show how to do a moving ball animation, where a small picture of a ball is drawn at sequential locations of a rectangle. Two different methods of animation are shown here, one just paints the ball on the display and the other paints the entire rectangle to avoid flicker.
We have used the SetTimer( ) function for a system timer before so we will talk about using the TimerProc function with that next.


Using the TimerProc with SetTimer( )
Here we use a TimerProc fuction instead of the WM_TIMER message to fire the timer event. Look at the TimerProc in the index of the Win32 API Help. When you call the SetTimer( ) function you will need to give the address of your TimerProc in the last parameter like this -
SetTimer(hForm1,4,1000, @TimerProc);
Otherwise, SetTimer( ) is used the same as was demonstrated in the BrushPen program with the WM_TIMER message in the WindowProc function. If you give a method address in the last parameter of SetTimer( ) then the WM_TIMER message will call thet procedure instead of doing the message. If you look at the TimerProc in the Timers Program code below you will see
TimerProc(Wnd: HWnd; Mesg, TimerID, SysTime: Longint); stdcall;
The 4 parameters are like the 4 parameters in MessageProc( ) and the Wnd as hForm1 and Mesg as WM_TIMER. We will test the TimerID parameter to get the ID number and execute the code for that TimerID. The SysTime parameter is the system TicCount which the GetTickCount( ) function returns.



the MultiMedia Timer
The Multimedia timer allows developers to schedule timer events with the greatest resolution (or accuracy) possible for that computer's hardware. These multimedia timers allow you to schedule timer events at a higher resolution than other timer services, especialy the System timer using the SetTimer( ) function.
These Multimedia timers are useful for applications that demand high-resolution timing. For example, a MIDI sequencer requires an accurate timer because it must maintain the sequence of MIDI events within a resolution of 1 millisecond. Unlike the WM_TIMER message used for the SetTimer( ) function, the Mutimedia Timer can triger it's callback function while you program is gong other processing.
To get the minimum and maximum timer resolutions supported by MutiMedia timers, use the timeGetDevCaps( ) function. This function fills the wPeriodMin and wPeriodMax members of the TTimeCaps record with the minimum and maximum resolutions allowed. This range can vary across computers and Windows platforms or versions. After you get the minimum and maximum available MultiMedia timer resolutions, you must set the minimum resolution you want your application to use. Use the timeBeginPeriod( ) and timeEndPeriod( ) functions to set and clear this resolution. You must match each function call to timeBeginPeriod( ) with a function call to timeEndPeriod( ), giving the same minimum resolution in both calls. A program can make multiple timeBeginPeriod( ) calls, as long as each of these is matched with a call to timeEndPeriod( ).
After you have set your mutimedia timer resolution, you can start timer events by using the timeSetEvent( ) function. This function returns a timer identifier that can be used to stop or identify timer events. The third parameter in this function is the address of a TimeProc callback function that is called when a timer event takes place. There are two types of multimedia timer events: single and periodic. A single timer event occurs only once, after a specified number of milliseconds. A periodic timer event keeps occuring every time a specified number of milliseconds elapses until it is stopped. The interval between periodic events is called an event delay. Periodic timer events with an event delay of 10 milliseconds or less consume a significant portion of CPU resources. The relationship between the resolution of a timer event and the length of the event delay is important in timer events. For example, if you set a resolution of 5 and an event delay of 100, the multimedia timer can notify the callback function after an interval ranging from 95 to 105 milliseconds. You can cancel an active timer event at any time by using the timeKillEvent( ) function. Be sure to cancel any outstanding timers before ending the program or thread that has the TimerProc function. You must include the mmsystem unit in your Uses clause to use the mutimedia timer functions.
the MultiMedia Timer is in a separate Thread
In order to maintain high timer resolutions, the multimedia timer runs in its own Windows System thread. This OS thread is NOT suspended by a System-Modal dialog box, so if you press the famous Ctrl - Alt - Delete keys and get the Close Program Dialog box, your program's thread is suspended, but the Mutimedia Timer thread keeps on going and calling the TimerProc, so when the System-Modal is closed all the mutimedia timer events that were called during the dialog box's existance will be called without delay when the dialog box closes.



the Thread Timer
Instead of using a Mutimedia timer you can create a separate thread and have that thread sleep with the SleepEX( ) function and fire an event each time the SleepEx( ) returns. A thread is the basic unit to which the operating system allocates processor time. A thread can execute any part of the process code, including parts currently being executed by another thread. Windows OS supports preemptive multitasking, which creates the effect of simultaneous execution of multiple threads. Multitasking divides the available processor time among the processes or threads that need it. Creating separate threads is used in API to allow code execution for one thread not to be heald up by the code execution in another thread. I will not attempt to try and explain all the dymanics and factors of separate threads. You can read some of the aspects of it in the Win32 API Help under the index of "Processes and Threads".

If you look at the code in the DoThreadTimer( ) procedure in the Timer Program below you will see
hThread := BeginThread(nil, 0, @ThreadFunc, nil, 0, ThreadId);
The BeginThread( ) function starts a sparate thread. Since the third parameter is @ThreadFunc the
function ThreadFunc(Parameter: Pointer): Integer; stdcall;
will be where this thread will executed. This ThreadFunc( ) is much like the .dpr program code execution, as in a .dpr, the threads execution starts at "begin" and ends at "end;" So you will need a loop to keep this thread alive, if you were creating windows in this thread you would use a Message loop, but we are not creating any windows and will use a While loop with a
SleepEx(TimerR.Interval, False); in it. The While loop runs as long as the thdTiming is true, so you need to set thdTiming to true before you start this thread. See the comments in the code below for more info.





Timers program


This program creates a Splash screen like the Brushs and Pens program but it draws directly on the desktop HDC instead of creating a Splash screen Form to draw on. It uses SetTimer( ) to do the Splash screen timing for the letter display and changing to the main Form. Here we use a procedure named "TimerProc" to process the WM_TIMER events. See the code in the TimerProc for more info. This Program creates 4 buttons and a Check Box on a form about 580x420 pixels in size. You should notice that in the Win Class setup the style is set to CS_OWNDC
Style := CS_BYTEALIGNCLIENT or CS_OWNDC;
The CS_OWNDC gives this window a "Persistant" long Lasting HDC. Called a Private Device Context, a unique device context for a window in that class. So you need only get the HDC once and then use it for all subsequent painting. Although the CS_OWNDC style is convenient, use it carefully, because each device context uses a portion of system resources. Since we are doing graphic animation here the Private HDC is helpful since we do not need to call GetDC( ) in the thousands of calls for painting the moving ball.
Two Text Scrolling Boxes are Drawn in the WM_PAINT message using the DrawEdge(PaintS.hDC, ScrolRect1, EDGE_SUNKEN, BF_RECT); function. This gives the appearence of a windowed control (an Edit Box). If you click the "Start Scroll" button the "procedure ScrollText;" is called to create a background Bitmap for the Scrolling Text Boxs. Then the TimeSetEvent( ) and the SetTimer( ) are called to start the 2 timers with the same interval of 38 milliseconds. The SetTimer( ) time event will fire the TimerProc to draw a new rectangle in the upper Text Scroll box and the TimeSetEvent( ) time event will fire the MMTimerProc to draw the scrolling text in the lower box.

see comments in the code below for more info

program Timers;
{this program uses 3 different ways to do timed events. Here the
WM_TIMER message is not used for the SetTimer function, but a special
TimerProc fuction is used. This also demonstrates the use of a Multi-Media
Timer, which is more accurate than the SetTimer. And an example of a
Timer event fired in a separate Thread using SleepEx( )}

uses
  Windows, Messages, SmallUtils, mmsystem;
  {mmsystem is needed for a Multi Media Timer}

{$R *.RES}

type
  ThreadRec = record
    Interval: Cardinal;
    ID: Word;
end;


var
wClass: TWndClass;
hForm1, hExitBut, hScrollBut, hSpriteBut, hPFlipBut, hThreadCB: THandle;
Font1, Brush1, Brush2, Brush3, Pen1, hThread: THandle;
SpriteBmp1, MaskBmp, FlipBmp, BkgndBmp: THandle;
mainMsg: TMSG;
Rect1, ScrolRect1, ScrolRect2: TRect;
FormDC, Bmp1DC, MaskDC, FlipDC, BkgndDC: HDC;
FontLog1: TLogFont;
DrawTimes: Byte;
Posit1, Posit2: Integer;
BrushLog1: TLogBrush;
Scrolling, GetPer, GetPer1, Moving, GoRight, GoDown, Fliping, thdTiming: Boolean;
Size1: TSize;
TimeCaps1: TTimeCaps;
TimerID1, TickCount: Cardinal;
PerFreq, PerCount0, PerCount1, PerCount2, PerCount3: Int64;
BallPoint: TPoint;
TimerR: ThreadRec;


const
Splash = 'Timers';
TextStr1 = 'This is a WM_TIMER Scroll';
TextStr2 = 'This is a MM Timer Scroll';

procedure ShutDown;
begin
KillTimer(hForm1,1);
KillTimer(hForm1,2);
KillTimer(hForm1,3);
KillTimer(hForm1,4);
{make sure timers are dead}
thdTiming := False;
timeKillEvent(TimerID1);
TimeEndPeriod(TimeCaps1.wPeriodMin);
{be sure to timeKillEvent and
TimeEndPeriod before you close your
app}

CloseHandle(hThread);
{release the OS references to the thread handle}
DeleteObject(Font1);
DeleteObject(Brush1);
DeleteObject(Brush2);
DeleteObject(Brush3);
DeleteObject(Pen1);
ReleaseDC(hForm1, FormDC);
DeleteDC(Bmp1DC);
DeleteDC(MaskDC);
DeleteDC(FlipDC);
DeleteDC(BkgndDC);
DeleteObject(SpriteBmp1);
DeleteObject(MaskBmp);
DeleteObject(FlipBmp);
DeleteObject(BkgndBmp);
PostQuitMessage(0);
end;

function MakeBrush(Color1, Color2: Cardinal): THandle;
  var
  BmpDC: hDC;
  i, k, Dot: Integer;
  Bitmap1: THandle;
  TempDC: HDC;
begin
{this creates a diagonal stripe bitmap brush with 2 colors}
TempDC := GetDC(0);
Bitmap1 := CreateCompatibleBitmap(TempDC,8,8);
BmpDC := CreateCompatibleDC(TempDC);
ReleaseDC(GetDesktopWindow,TempDC);
SelectObject(BmpDC,Bitmap1);

Dot := 0;
for i:= 0 to 7 do
  begin
    for k:= 0 to 7 do
    if ((k+Dot) mod 4) <> 0 then
    SetPixelV(BmpDC,i,k, Color1)
    else
    SetPixelV(BmpDC,i,k, Color2);
    Inc(Dot);
  end;
BrushLog1.lbStyle := BS_PATTERN;
BrushLog1.lbHatch := Bitmap1;
Result := CreateBrushIndirect(BrushLog1);
DeleteDC(BmpDC);
DeleteObject(Bitmap1);
end;

function MakeBrush3: THandle;
  var
  BmpDC: hDC;
  Bitmap2: THandle;
  i, k, Dot: Integer;
  TempDC: HDC;
begin
{this makes a bitmap brush with alternating black
and white pixels}
TempDC := GetDC(0);
{this function is called before the Form window is
created, so we get the desktop hDC}
Bitmap2 := CreateBitmap(8,8,1,1,nil);
BmpDC := CreateCompatibleDC(TempDC);
ReleaseDC(GetDesktopWindow,TempDC);
SelectObject(BmpDC,Bitmap2);

Dot := 0;
for i:= 0 to 7 do
  begin
    for k:= 0 to 7 do
    if ((k+Dot) mod 2) <> 0 then
    SetPixelV(BmpDC,i,k,$00000000)
    else
    SetPixelV(BmpDC,i,k,$00FFFFFF);
    Inc(Dot);
  end;
BrushLog1.lbStyle := BS_PATTERN;
BrushLog1.lbHatch := Bitmap2;
Result := CreateBrushIndirect(BrushLog1);
DeleteDC(BmpDC);
DeleteObject(Bitmap2);
end;

procedure TimerProc(Wnd: HWnd; Mesg, TimerID, SysTime: Longint); stdcall;
var
TimeRec: TSystemTime;
Time: String;
OldColor: Cardinal;
OldMode: Integer;
OldFont: THandle;
begin
{the WM_TIMER message fires this TimerProc function, if the address
of this function is given in SetTimer( )}
case TimerID of
  1 : begin
  {this stops the Splash Screen and shows the main Form}
      KillTimer(hForm1,1);
      KillTimer(hForm1,2);
      ReleaseDC(GetDesktopWindow,MaskDC);
      MaskDC := 0;
      ShowWindow(hForm1, SW_SHOWNORMAL);
      SetTimer(hForm1,4,1000, @TimerProc);
      end; //1
  2 : begin
  {this is the Splash Screen Painting, unlike the BrushPen program
  this does not create splash window, but just draws on the
  desktop. DrawTimes is used to know which splash letter to draw}
      if DrawTimes = 0 then
      PatBlt(MaskDC, Rect1.Left+80, Rect1.Top+30, 300, 250,PATCOPY)
      else if DrawTimes < 7 then
      TextOut(MaskDC,0,0,@Splash[DrawTimes],1)
      else
      PatBlt(MaskDC, Rect1.Left+80, Rect1.Top+30, 300, 250,DSTINVERT);

      Inc(DrawTimes);
      if DrawTimes > 8 then
        KillTimer(hForm1,2);
      end; //2
  3 : begin
  {this is the Scroll Text painting}
      {FormDC := GetDC(hForm1);
      SelectObject(FormDC, Brush2);
      SetBkMode(FormDC,TRANSPARENT);
      the 3 functions above are NOT needed because we are using a
      Private Device Context FormDC, for our main Form}
      if GetPer1 and (PerFreq <> 0) then
        begin
        {the PerformanceCounter is a very fast high-resolution counter
        which can be used to find out the amount of time between 2 calls
        to QueryPerformanceCounter( )}
        QueryPerformanceCounter(PerCount3);
        GetPer1 := False;
        SetBkMode(FormDC,OPAQUE);
        Time := Int2Str(((PerCount3-PerCount2)*10000) div PerFreq);
        Insert('.',Time,Length(Time));
        TextOut(FormDC,6,ScrolRect1.Top+6,PChar(Time), Length(Time));
        {this will show the milliseconds between the PerCount2 and PerCount3
        Query which should be equal to the Interval in SetTimer, but the interval
        will be about 55 no matter what you set it to below 55, 55 millisecond
        is about as fast as the system timer can go}
        SetBkMode(FormDC,TRANSPARENT);
        Time := '';
        end;

      if Posit1 > Size1.cx+17 then
        begin
        {Posit1 will be larger than Size1.cx+17 only once per scroll cycle}
        Posit1 := 0;
        if PerFreq <> 0 then
          begin
          QueryPerformanceCounter(PerCount2);
          GetPer1 := True;
          end;
        end;
      InflateRect(ScrolRect1,-2,-2);
      PatBlt(FormDC, ScrolRect1.Left, ScrolRect1.Top, 196, 26,PATCOPY);
      ExtTextOut(FormDC,ScrolRect1.Left-Posit1, ScrolRect1.Top+4,
                 ETO_CLIPPED, @ScrolRect1, TextStr1, 25, nil);
      {ExtTextOut( ) clips the text output to the ScrolRect1}
      ExtTextOut(FormDC,ScrolRect1.Right-Posit1, ScrolRect1.Top+4,
                 ETO_CLIPPED, @ScrolRect1, TextStr1, 25, nil);
      Inc(Posit1);
      InflateRect(ScrolRect1,2,2);
      end; //3
  4: begin
     {this timer event fires about once a second and draws the local
     Date and Time on the form, like a clock}
     GetLocalTime(TimeRec);
     {to get date and time you can use GetLocalTime}
     OldFont := SelectObject(FormDC, GetStockObject(ANSI_VAR_FONT));
     OldColor := SetBkColor(FormDC, GetSysColor(COLOR_BTNFACE));
     OldMode := SetBkMode(FormDC, OPAQUE);
     {since a Private DC is used for this form, all DC Objects and modes
     set here will remain for the FormDC even after this function, so you need
     to record the DC settings you change and reset them}
     Time := 'Local Date - '+Int2Str(TimeRec.wMonth)+'/'+Int2Str(TimeRec.wDay)+
             '/'+Int2Str(TimeRec.wYear)+'    Time - '+Int2Str(TimeRec.wHour)+':'+
             Int2Str(TimeRec.wMinute)+'  '+Int2Str(TimeRec.wSecond)+' Seconds   ';
     TextOut(FormDC,128,365,@Time[1],Length(Time));
     {after the text is drawn reset the FormDC settings back to what they were,
     or other animation drawing operations will be affected}
     SelectObject(FormDC, OldFont);
     SetBkColor(FormDC, OldColor);
     SetBkMode(FormDC, OldMode);
     Time := '';
     end;
  end; // case
end;

procedure MoveBall;
var
PCstr: String;
begin
{this animates a sprite (a picture of a ball and shadow), so
it moves around a Rectangle and appears to bounce off the edges}
if GetPer and (PerFreq <> 0) then
  begin
  {this will display the actual time elasped for this timer
  notice that it is the interval set in the Muti-Media or Thread timer}
  QueryPerformanceCounter(PerCount1);
  GetPer := False;
  SetBkMode(FormDC,OPAQUE);
  PCstr := Int2Str(((PerCount1-PerCount0)*100000) div PerFreq);
  Insert('.',PCstr,Length(PCstr)-1);
  TextOut(FormDC,460,120,PChar(PCstr),
          Length(PCstr));
  SetBkMode(FormDC,TRANSPARENT);
  end;
SelectObject(FormDC, Brush1);
{SetROP2(FormDC,R2_COPYPEN);}
PatBlt(FormDC, BallPoint.x, BallPoint.y, 49, 44,PATCOPY);
{BallPoint is used to remember the position of the ball
GoRight and GoDown are used to set the direction of movement}
if BallPoint.x > 366 then
  begin
  GoRight := False;
  if PerFreq <> 0 then
    begin
    QueryPerformanceCounter(PerCount0);
    GetPer := True;
    end;
  end else
if BallPoint.x < 104 then
GoRight := True;
{the ball changes direction when the position gets to the max 366
and min 104 values, which corresponds to the rectangle's position
minus the width of the sprite bitmap}
if GoRight then
Inc(BallPoint.x,5) else
Dec(BallPoint.x,4);
{changing the BallPoint X and Y will move the ball.
You need to match the amount of pixel change with the
timer interval to get the speed of ball movement you
want}

if BallPoint.y > 213 then
GoDown := False else
if BallPoint.y < 12 then
  begin
  GoDown := True;
  if PerFreq <> 0 then
    begin
    QueryPerformanceCounter(PerCount0);
    GetPer := True;
    end;
  end;
if GoDown then
Inc(BallPoint.y,3) else
Dec(BallPoint.y,2);
{television uses 25 frames per second (an inteval of
about 40 milliseconds). You might try to keep your animation
interval between 30 and 40 milliseconds and adjust your pixel
Increase for movement, to get the ball movement speed you want}

SelectObject(FormDC, Brush3);

{SetRop2 is R2_MASKPEN and will draw the Elipse so only the
black pixels of Brush3 will be drawn, a shadow effect}
Ellipse(FormDC,BallPoint.x+4,BallPoint.y+5,BallPoint.x+49,BallPoint.y+44);

{2 paints are required to get a transparent bitmap, first the MaskBmp
is drawn with the ScrAnd raster operation. Next the Inverted colors
Bmp1 is drawn with the SrcInvert, so the colors will go back to normal
and the transparent mask area will go back to the background hDC colors}
BitBlt(FormDC, BallPoint.x, BallPoint.y, 40, 40, MaskDC, 0, 0, SrcAnd);
BitBlt(FormDC, BallPoint.x, BallPoint.y, 40, 40, Bmp1DC, 0, 0, SrcInvert);
end;

procedure FlipIt;
var
RectP: TRect;
{Counts: Cardinal;}
begin
{if you watch the MoveBall animation above you will see the ball flicker
sometimes, if the background paint and the ball paint are in a different
screen refresh, then you get flicker - -
In this FlipIt animation, a method called Page-Flipping is used.
Instead of drawing the new background and ball on the screen, a
bitmap is used to do this painting off screen, and then the whole
bitmap is drawn onscreen, and then there is no refresh between the
background and ball drawing, and no flickering. However, bitmaps can
get so large that doing animation of many sprites or Scanlines and
other graphics operations on them, and then drawing the bitmap on screen
takes longer than the animation timer interval, so you can
have limits for page flipping}
if PerFreq <> 0 then
QueryPerformanceCounter(PerCount2);

BitBlt(FlipDC, BallPoint.x, BallPoint.y, 49, 47, BkgndDC,
       BallPoint.x, BallPoint.y, SrcCopy);
{Erase the sprite by copying the BkgndBmp over it}
if BallPoint.x > 240 then
GoRight := False else
if BallPoint.x < 4 then
GoRight := True;
if GoRight then
Inc(BallPoint.x,5) else
Dec(BallPoint.x,4);

if BallPoint.y > 196 then
GoDown := False else
if BallPoint.y < 2 then
GoDown := True;
if GoDown then
Inc(BallPoint.y,3) else
Dec(BallPoint.y,2);
{now draw a new sprite on the FlipBmp}
Ellipse(FlipDC,BallPoint.x+4,BallPoint.y+5,BallPoint.x+49,BallPoint.y+44);
BitBlt(FlipDC, BallPoint.x, BallPoint.y, 40, 40, MaskDC, 0, 0, SrcAnd);
BitBlt(FlipDC, BallPoint.x, BallPoint.y, 40, 40, Bmp1DC, 0, 0, SrcInvert);

BitBlt(FormDC, 100, 10, 380, 250, FlipDC, 0, 0, SrcCopy);
{the entire FlipBmp is now copied to the FormDC instead of a smaller section
to cover the sprite. Since there is only ONE draw operation to the Screen
(FormDC) there is no flicker}
if PerFreq <> 0 then
  begin
  {this will show the High Performance ticks required to draw on the bitmap
  and dwaw the bitmap on the form}
  QueryPerformanceCounter(PerCount3);
  {Counts := ((PerCount3-PerCount2)*1000) div PerFreq;}
  {to get the time in milliseconds you need to divide by the PerFreq
  but this bitmap is small enough that it takes less than 1 millisecond}
  SetBkMode(FormDC,OPAQUE);
  SetRect(RectP,424,5,474,35);
  FillRect(FormDC,RectP, wClass.hbrBackground);
  TextOut(FormDC,424,5,PChar(Int2Str({Counts}PerCount3-PerCount2)),
          Length(Int2Str({Counts}PerCount3-PerCount2)));
  SetBkMode(FormDC,TRANSPARENT);
  end;
end;

procedure ScrollIt;
var
Ticks: String;
begin
{This animates a text display so it looks like the text
is scrolling from left to right. It is the same as the scroll text
in the TimerProc number 3: So you can see the difference between
a Multi-Media Timer and the system Timer WM_TIMER animation.}

{FormDC := GetDC(hForm1);
SelectObject(FormDC, Brush2);
SetBkMode(FormDC,TRANSPARENT);}
{the functions above do NOT need to be called
because we are using a Private DC}
if GetPer and (PerFreq <> 0) then
  begin
  {this will display the actual time elasped for this timer
  notice that it is the interval set in the Muti-Media or Thread timer}
  QueryPerformanceCounter(PerCount1);
  GetPer := False;
  SetBkMode(FormDC,OPAQUE);
  TextOut(FormDC,ScrolRect2.Right+8,ScrolRect2.Top+6,PChar('Ticks '+
          Int2Str(GetTickCount-TickCount)),
          Length('Ticks '+Int2Str(GetTickCount-TickCount)));
  Ticks := Int2Str(((PerCount1-PerCount0)*10000) div PerFreq);
  Insert('.',Ticks,Length(Ticks));
  TextOut(FormDC,6,ScrolRect2.Top+6,
          PChar(Ticks{Int2Str(((PerCount1-PerCount0)*1000) div PerFreq)}),
          Length(Ticks{Int2Str(((PerCount1-PerCount0)* 1000) div PerFreq)}));
  SetBkMode(FormDC,TRANSPARENT);
  Ticks := '';
  end;

if Posit2 > Size1.cx+17 then
  begin
  Posit2 := 0;
  if PerFreq <> 0 then
    begin
    QueryPerformanceCounter(PerCount0);
    TickCount := GetTickCount;
    GetPer := True;
    end;
  end;

{the Size1 is for different text, TextStr1, than is used here,
but it gives the same scroll width as the scrolling in TimerProc
for comparision}

InflateRect(ScrolRect2,-2,-2);
BitBlt(FormDC, ScrolRect2.Left, ScrolRect2.Top, Size1.cx+17,
       ScrolRect2.Bottom- ScrolRect2.Top, BkgndDC, Posit2, 0, SrcCopy);
BitBlt(FormDC, ScrolRect2.Right-Posit2, ScrolRect2.Top, Posit2,
       ScrolRect2.Bottom- ScrolRect2.Top, BkgndDC, 0, 0, SrcCopy);
{PatBlt(FormDC, ScrolRect2.Left, ScrolRect2.Top, 196, 26,PATCOPY);
ExtTextOut(FormDC,ScrolRect2.Left-Posit2, ScrolRect2.Top+4,
           ETO_CLIPPED, @ScrolRect2, TextStr2, 25, nil);
ExtTextOut(FormDC,ScrolRect2.Right-Posit2, ScrolRect2.Top+4,
           ETO_CLIPPED, @ScrolRect2, TextStr2, 25, nil);}
InflateRect(ScrolRect2,2,2);
{ReleaseDC(hForm1, FormDC);}
Inc(Posit2,2);

end;

procedure MMTimerProc(uTimerID, uMessage: cardinal; dwUser, dw1, dw2: integer); stdcall;
begin
{you should keep all operations in other procedures and not do anything
here except call those procedures. This Fuction will be called by the
Muti-Media timer even if the procedures called from a previous MMTimerProc
have NOT yet completed. The Multi-Media timer runs in a separate thread.}
case dwUser of
  1: MoveBall;
  2: FlipIt;
  10: ScrollIt;
  else timeKillEvent(TimerID1);
end;
end;

function ThreadFunc(Parameter: Pointer): Integer; stdcall;
begin
{you should keep all operations in other procedures and not do anything here
except call those procedures. This Fuction runs in a separate thread and will
be called even if the procedures from a previous ThreadFunc have NOT yet completed.}
Result := 0;
while thdTiming do
  begin
  {thdTiming cuts this thread off, it uses a while loop with a
  SleepEx to get a timer interval effect}
  SleepEx(TimerR.Interval, False);
  case TimerR.ID of
    1: if thdTiming then FlipIt;
    {check for thdTiming here also in case it changed
    during SleepEx}
    2: if thdTiming then MoveBall;
    else Break;
    end;
  end;
TimerR.ID := 0;
EndThread(Result);
{ExitThread(Result);}
{no code below EndThread will be executed}
TextOut(FormDC,4, 100, 'Below EndThread', 15);
end;

procedure DoThreadTimer(Interval, TimerID: Word);
var
ThreadId: LongWord;
begin
{The interval should be more than 4 or alot of the processor
time cycles are used, if you need an interval less than 5 then
you may want to set the Thread Priority higher with
SetThreadPriority( )}
if TimerR.ID <> 0 then Exit;
{TimerR.ID is set to 0 when the Thread ends, so check for 0
to make sure you don't start a thread if one is still running}
if Interval < 5 then
TimerR.Interval := 5 else
TimerR.Interval := Interval;
TimerR.ID := TimerID;
CloseHandle(hThread);
{since a thread may be created more than once,
you should Close the Handle}

{if you wanted to use more than one thread then you should keep track
of each thread's handle and close it}
hThread := BeginThread(nil, 0, @ThreadFunc, nil, 0, ThreadId);
{BeginThread( ) is a Delphi System function which does the same as
windows CreateThread( ), except it sets the global IsMultiThread
variable, thereby making the heap thread-safe. Below is the CreateThread( )
function, which will also work}

{hThread := CreateThread(
        NIL,                        // no security attributes
        0,                           // use default stack size
        @ThreadFunc,                  // thread function
        nil,                // argument to thread function
        0,                           // use default creation flags
        ThreadId);}
        
{SetThreadPriority(hThread, THREAD_PRIORITY_ABOVE_NORMAL);}

{use EndThread( ) with BeginThread( ) and use ExitThread( ) with
CreateThread( ), see ThreadFunc( ) above}
end;

procedure ScrollText;
var
Interval,Accur: Word;
begin
if Scrolling then
  begin
  Scrolling := False;
  timeKillEvent(TimerID1);
  KillTimer(hForm1,3);
  EnableWindow(hSpriteBut, True);
  EnableWindow(hPFlipBut, True);
  EnableWindow(hThreadCB, True);
  SetWindowText(hScrollBut, 'Start Scroll');
  DeleteDC(BkgndDC);
  DeleteObject(BkgndBmp);
  PatBlt(FormDC, ScrolRect1.Left+2, ScrolRect1.Top+2, 196, 26,PATCOPY);
  PatBlt(FormDC, ScrolRect2.Left+2, ScrolRect2.Top+2, 196, 26,PATCOPY);
  TextOut(FormDC,ScrolRect1.Left+4, ScrolRect1.Top+6, TextStr1, 25);
  TextOut(FormDC,ScrolRect2.Left+4, ScrolRect2.Top+6, TextStr2, 25);
  Posit1 := 1;
  Posit2 := 1;
  end else
  begin
  EnableWindow(hSpriteBut, False);
  EnableWindow(hPFlipBut, False);
  EnableWindow(hThreadCB, False);
  SelectObject(FormDC, Brush2);
  SetBkMode(FormDC,TRANSPARENT);
  InflateRect(ScrolRect2,-2,-2);

  BkgndBmp := CreateCompatibleBitmap(FormDC, Size1.cx+17,
                         ScrolRect2.Bottom- ScrolRect2.Top);
  BkgndDC := CreateCompatibleDC(FormDC);
  SelectObject(BkgndDC, BkgndBmp);
  SelectObject(BkgndDC, Brush2);
  SetBkMode(BkgndDC, TRANSPARENT);
  PatBlt(BkgndDC, 0, 0, Size1.cx+17, ScrolRect2.Bottom- ScrolRect2.Top, PATCOPY);
  TextOut(BkgndDC,3, 4, TextStr2, 25);
  InflateRect(ScrolRect2,2,2);
  timeKillEvent(TimerID1);
  Interval := 38;
  Accur := Interval div 2;
  if Accur < TimeCaps1.wPeriodMin then
  Accur := TimeCaps1.wPeriodMin;
  if Accur > TimeCaps1.wPeriodMax then
  Accur := TimeCaps1.wPeriodMax;
  if TimeCaps1.wPeriodMin <> 0 then
  TimerID1 := TimeSetEvent(Interval, Accur, @MMTimerProc, 10, TIME_PERIODIC);

  SetTimer(hForm1,3,Interval, @TimerProc);
  {notice that the Interval for both the MM timer and the
  system timer are the same}
  Scrolling := True;
  SetWindowText(hScrollBut, 'Stop Scroll');
  TextOut(FormDC,ScrolRect1.Left,ScrolRect1.Top-28,PChar('Scroll Interval is '+
          Int2Str(Interval)), 19+Length(Int2Str(Interval)));
  end;
end;

procedure MakeMoveBall;
var
TDC: HDC;
TempBmp, TempBrush, TempPen: THandle;
begin
{this creates the animation sprite bitmap, it adds
a highlight and some shadow arcs to a circle, so it
will have some 3D and look like a ball}
Bmp1DC := CreateCompatibleDC(FormDC);
MaskDC := CreateCompatibleDC(FormDC);
TDC := CreateCompatibleDC(FormDC);
SpriteBmp1 := CreateCompatibleBitmap(FormDC,40,40);
MaskBmp := CreateCompatibleBitmap(FormDC,40,40);
{since this is suppose to look like a ball and bitmaps
are retangular, we need to draw a Transparent bitmap.
To do this we need a mask bitmap with the area we want
to be transparent as white, and the area to be seen as black}
TempBmp := CreateCompatibleBitmap(FormDC,40,40);
{the TempBmp will get an Inverted color sprite and
then the Inverted bitmap will be copied to the
sprite bitmap}
SelectObject(Bmp1DC, SpriteBmp1);
SelectObject(MaskDC, MaskBmp);
SelectObject(TDC, TempBmp);
SelectObject(MaskDC, GetStockObject(WHITE_BRUSH));
PatBlt(MaskDC, 0, 0, 40, 40,PATCOPY);
SelectObject(Bmp1DC, GetStockObject(WHITE_BRUSH));
PatBlt(Bmp1DC, 0, 0, 40, 40,PATCOPY);
SelectObject(MaskDC, GetStockObject(BLACK_BRUSH));
Ellipse(MaskDC,0,0,40,40);
TempBrush := CreateSolidBrush($00AADD00);
SelectObject(Bmp1DC, TempBrush);
Ellipse(Bmp1DC,0,0,40,40);
SelectObject(Bmp1DC, GetStockObject(WHITE_BRUSH));
{the white brush is used to add a highlight}
PatBlt(Bmp1DC, 10, 8, 5, 5,PATCOPY);
TempPen := CreatePen(PS_SOLID, 1, $00669900);
SelectObject(Bmp1DC, TempPen);
{several Arcs are drawn with a darker pen to
add a shadow effect to the ball}
Arc(Bmp1DC,2,2,38,38,18,40,40,18);
DeleteObject(TempPen);
TempPen := CreatePen(PS_SOLID, 1, $0099AA00);
SelectObject(Bmp1DC, TempPen);
Arc(Bmp1DC,5,5,35,35,20,40,40,20);
DeleteObject(TempPen);
TempPen := CreatePen(PS_SOLID, 1, $0099BB00);
SelectObject(Bmp1DC, TempPen);
Arc(Bmp1DC,8,8,32,32,22,40,40,22);
DeleteObject(TempPen);
TempPen := CreatePen(PS_SOLID, 1, $00D2E4B4);
SelectObject(Bmp1DC, TempPen);
Arc(Bmp1DC,3,3,37,37,16,0,0,16);
BitBlt(TDC, 0, 0, 40, 40, MaskDC, 0, 0, SrcCopy);
BitBlt(TDC, 0, 0, 40, 40, Bmp1DC, 0, 0, SrcErase);
{to get a transparent bitmap effect we copy the MaskDC
onto the TDC, then we do an Erase operation with the
Bmp1DC, which will invert the Bmp1DC bitmap}
BitBlt(Bmp1DC, 0, 0, 40, 40, TDC, 0, 0, SrcCopy);
{the temp TDC with the inverted color ball
is copied to the Bmp1DC}
DeleteDC(TDC);
DeleteObject(TempBmp);
DeleteObject(TempBrush);
DeleteObject(TempPen);
end;

procedure EndAnimation;
begin
  thdTiming := False;
  timeKillEvent(TimerID1);
  EnableWindow(hScrollBut, True);
  EnableWindow(hPFlipBut, True);
  EnableWindow(hThreadCB, True);
  EnableWindow(hSpriteBut, True);
  DeleteDC(Bmp1DC);
  DeleteDC(MaskDC);
  DeleteObject(SpriteBmp1);
  DeleteObject(MaskBmp);
  BallPoint.x := 150;
  BallPoint.y := 100;
end;

procedure Animate;
var
Interval,Accur: Word;
begin
if Moving then
  begin
  Moving := False;
  EndAnimation;
  SetWindowText(hSpriteBut, 'Start Move');

  {SelectObject(TempDC, GetSysColorBrush(COLOR_BTNFACE));
  PatBlt(TempDC, 100, 10, 320, 250,PATCOPY);}
  {ReleaseDC(hForm1, TempDC);}
  end else
  begin
  timeKillEvent(TimerID1);
  EnableWindow(hScrollBut, False);
  EnableWindow(hPFlipBut, False);
  EnableWindow(hThreadCB, False);
  SetWindowText(hSpriteBut, 'Stop Move');
  Moving := True;

  QueryPerformanceCounter(PerCount0);

  {TempDC := GetDC(hForm1);}
  MakeMoveBall{(TempDC)};

  SelectObject(FormDC, Brush1);
  PatBlt(FormDC, 100, 10, 320, 250,PATCOPY);
  {SetBkMode(TempDC, TRANSPARENT);}
  BitBlt(FormDC, BallPoint.x, BallPoint.y, 40, 40, MaskDC, 0, 0, SrcAnd);
  BitBlt(FormDC, BallPoint.x, BallPoint.y, 40, 40, Bmp1DC, 0, 0, SrcInvert);

  SelectObject(FormDC, GetStockObject(NULL_PEN));
  QueryPerformanceCounter(PerCount1);
  SetBkMode(FormDC,OPAQUE);
  TextOut(FormDC,35,5,PChar(Int2Str(PerCount1-PerCount0)),
          Length(Int2Str({(}(PerCount1-PerCount0){* 1000) div PerFreq})));

  Interval := 27;

  {shorter Intervals do faster movement, but use up MORE processor time.
   You should increase the change in pixel position for faster movement and
   increase your Interval if your Interval gets below about 30}

  Accur := Interval div 2;
  if Accur < TimeCaps1.wPeriodMin then
  Accur := TimeCaps1.wPeriodMin;
  if Accur > TimeCaps1.wPeriodMax then
  Accur := TimeCaps1.wPeriodMax;
  if SendMessage(hThreadCB, BM_GETSTATE, 0, 0) = BST_CHECKED then
    begin
    Interval := 18;
    thdTiming := True;
    DoThreadTimer(Interval, 2);
    {12 milliseconds less time than you should use, you should double
    this time to 24 milliseconds and double your pixel change in MoveBall
    from
    Inc(BallPoint.x,5) else
    Dec(BallPoint.x,4);
    to
    Inc(BallPoint.x,10) else
    Dec(BallPoint.x,8);}
    end else
  if TimeCaps1.wPeriodMin <> 0 then
  TimerID1 := TimeSetEvent(Interval, Accur, @MMTimerProc, 1, TIME_PERIODIC);

  TextOut(FormDC,430,92,PChar('Move Interval is '+Int2Str(Interval)),
                17+Length(Int2Str(Interval)));
  SetBkMode(FormDC,TRANSPARENT);
  {SetRop2 is set to R2_MASKPEN and will draw the Elipse in MoveBall so only
  the black pixels of Brush3 will be drawn, giving a shadow effect}
  SetROP2(FormDC,R2_MASKPEN);
  end;
end;

procedure PageFlip;
var
TempBrush: THandle;
Interval,Accur: Word;
begin
if Fliping then
  begin
  Fliping := False;
  EndAnimation;
  SetWindowText(hPFlipBut, 'PageFlip move');
  DeleteDC(FlipDC);
  DeleteDC(BkgndDC);
  DeleteObject(FlipBmp);
  DeleteObject(BkgndBmp);
  end else
  begin
  EnableWindow(hScrollBut, False);
  EnableWindow(hSpriteBut, False);
  EnableWindow(hThreadCB, False);
  Fliping := True;
  SetWindowText(hPFlipBut, 'Stop Flip');
  SelectObject(FormDC, GetSysColorBrush(COLOR_BTNFACE));
  PatBlt(FormDC, 100, 10, 320, 250,PATCOPY);
  MakeMoveBall;

  BkgndBmp := CreateCompatibleBitmap(FormDC,280,240);
  BkgndDC := CreateCompatibleDC(FormDC);
  SelectObject(BkgndDC, BkgndBmp);
  SelectObject(BkgndDC, Brush2);
  PatBlt(BkgndDC, 0, 0, 280, 240,PATCOPY);
  SelectObject(BkgndDC, Font1);
  TempBrush := CreateSolidBrush($00FF0000);
  SelectObject(BkgndDC, TempBrush);
  Ellipse(BkgndDC,10,30,270,210);
  DeleteObject(TempBrush);
  SetBkMode(BkgndDC, TRANSPARENT);
  TextOut(BkgndDC,17,70,'Page Flip', 9);

  FlipBmp := CreateCompatibleBitmap(FormDC,280,240);
  FlipDC := CreateCompatibleDC(FormDC);
  SelectObject(FlipDC, FlipBmp);
  BitBlt(FlipDC, 0, 0, 280, 240, BkgndDC, 0, 0, SrcCopy);
  SelectObject(FlipDC, Brush3);
  SelectObject(FlipDC, GetStockObject(NULL_PEN));
  SetROP2(FlipDC,R2_MASKPEN);

  QueryPerformanceCounter(PerCount1);
  SetBkMode(FormDC,OPAQUE);
  TextOut(FormDC,35,5,PChar(Int2Str(PerCount1-PerCount0)),
          Length(Int2Str(((PerCount1-PerCount0)* 1000) div PerFreq)));

  Interval := 24;

  Accur := Interval div 2;
  if Accur < TimeCaps1.wPeriodMin then
  Accur := TimeCaps1.wPeriodMin;
  if Accur > TimeCaps1.wPeriodMax then
  Accur := TimeCaps1.wPeriodMax;
  if SendMessage(hThreadCB, BM_GETSTATE, 0, 0) = BST_CHECKED then
    begin
    Interval := 34;
    thdTiming := True;
    DoThreadTimer(Interval, 1);
    end else
  if TimeCaps1.wPeriodMin <> 0 then
  TimerID1 := TimeSetEvent(Interval, Accur, @MMTimerProc, 2, TIME_PERIODIC);
  
  TextOut(FormDC,430,70,PChar('Flip Interval is '+Int2Str(Interval)),
                17+Length(Int2Str(Interval)));
  SetBkMode(FormDC,TRANSPARENT);
  end;

end;

function MessageProc(hWnd: HWnd; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UINT; stdcall;
  var
  PaintS: TPaintStruct;
begin
case Msg of
  WM_PAINT: begin
            BeginPaint(hWnd, PaintS);
            DrawEdge(PaintS.hDC, ScrolRect1, EDGE_SUNKEN, BF_RECT);
            {the Scrolling text boxes need a sunken edge drawn to make
            them look like a windows control}
            DrawEdge(PaintS.hDC, ScrolRect2, EDGE_SUNKEN, BF_RECT);
            TextOut(PaintS.hDC,6,202,PChar('PerFreq='+Int2Str(PerFreq)),
                    8+Length(Int2Str(PerFreq)));

            if not Scrolling then
              begin
              SelectObject(PaintS.hDC, Brush2);
              SetBkMode(PaintS.hDC,TRANSPARENT);
              PatBlt(PaintS.hDC, ScrolRect1.Left+2, ScrolRect1.Top+2, 196, 26,PATCOPY);
              PatBlt(PaintS.hDC, ScrolRect2.Left+2, ScrolRect2.Top+2, 196, 26,PATCOPY);
              TextOut(PaintS.hDC,ScrolRect1.Left+4, ScrolRect1.Top+6, TextStr1, 25);
              TextOut(PaintS.hDC,ScrolRect2.Left+4, ScrolRect2.Top+6, TextStr2, 25);
              end;
            if Moving then
              begin
              SelectObject(PaintS.hDC, Brush1);
              PatBlt(PaintS.hDC, 100, 10, 320, 250,PATCOPY);
              end;
            EndPaint(hWnd,PaintS);
            end;
  WM_COMMAND: if lParam = abs(hExitBut) then PostMessage(hForm1, WM_CLOSE,0,0)
              else if lParam = abs(hScrollBut) then ScrollText
              else if lParam = abs(hSpriteBut) then Animate
              else if lParam = abs(hPFlipBut) then PageFlip;
  WM_DESTROY: ShutDown;
  end;
Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;

begin // / / / / / / / / main BEGIN / / /
DrawTimes := 0;
Scrolling := False;
GetPer := False;
GetPer1 := False;
Moving := False;
Fliping := False;
GoRight := True;
GoDown := True;
thdTiming := False;
BallPoint.x := 150;
BallPoint.y := 100;
Posit1 := 1;
Posit2 := 1;
TimerID1 := 0;
TimerR.ID := 0;
hThread := 0;
SetRect(ScrolRect1,40,276,240,306);
SetRect(ScrolRect2,40,316,240,346);
{ScrolRect1 and ScrolRect2 are used to paint the text
scrolling boxes}
Brush1 := MakeBrush($003366FF, $00FFFFFF);
Brush2 := MakeBrush($00EEF0FF,$0033E0EF);
Brush3 := MakeBrush3;
Pen1 := CreatePen(PS_SOLID, 4, $00990099);
with FontLog1 do
  begin
  lfHeight := -60;
  lfWidth := 0;
  lfItalic := 0;
  lfWeight := FW_NORMAL;
  lfCharSet := ANSI_CHARSET;
  lfOutPrecision := OUT_TT_PRECIS;
  lfClipPrecision := CLIP_DEFAULT_PRECIS;
  lfQuality := ANTIALIASED_QUALITY;
  lfPitchAndFamily := VARIABLE_PITCH or FF_SWISS;
  lfFaceName := 'Comic Sans MS';
  end;
Font1 := CreateFontIndirect(FontLog1);

wClass.hInstance := hInstance;
with wClass do
  begin
    Style := CS_BYTEALIGNCLIENT or CS_OWNDC;
{the style is set to CS_OWNDC, this will make the Form1
window have a Private Device Context hDC. I use this style
because animations use rapid drawing, with a private hDC
you do not have to GetDC and ReleaseDC with each animation
sprite painting}
    hIcon :=         LoadIcon(hInstance,'MAINICON');
    lpfnWndProc :=   @MessageProc;
    hbrBackground := COLOR_BTNFACE+1;
    lpszClassName := 'Form Class';
    hCursor :=       LoadCursor(0,IDC_ARROW);
  end;

RegisterClass(wClass);

SetRect(Rect1,0,0,574,394);
if not AdjustWindowRect(Rect1,WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU,False) then
SetRect(Rect1,0,0,580,420);

hForm1 := CreateWindow(wClass.lpszClassName, ' Timers',
     WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU ,
    (GetSystemMetrics(SM_CXSCREEN) div 2)-276,
    (GetSystemMetrics(SM_CYSCREEN) div 2)-222,
    Rect1.Right-Rect1.Left, Rect1.Bottom-Rect1.Top, 0, 0, hInstance, nil);

FormDC := GetDC(hForm1);
{the default hDC font will be used for drawing the scrolling text}
GetTextExtentPoint32(FormDC, TextStr1, 25, Size1);
{since CS_OWNDC is in the Window Class style,
using  ReleaseDC(hForm1, FormDC);  has no effect here}

hExitBut :=CreateWindow('Button','E x i t',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    484,360,74,24,hForm1,0,hInstance,nil);

hScrollBut := CreateWindow('Button','Start Scroll',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    20,360,78,24, hForm1,0, hInstance,nil);
SendMessage(hScrollBut,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0);

hSpriteBut := CreateWindow('Button','Start move',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    310,290,78,24, hForm1,0, hInstance,nil);
SendMessage(hSpriteBut,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0);

hPFlipBut := CreateWindow('Button','PageFlip move',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    420,290,86,24, hForm1,0, hInstance,nil);
SendMessage(hPFlipBut,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0);

hThreadCB := CreateWindow('Button',
    'Use Thread Timer',
    WS_CHILD or BS_AUTOCHECKBOX or WS_VISIBLE,
    350,328,110,24,hForm1,0,hInstance,nil);
SendMessage(hThreadCB,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0);


if TimeGetDevCaps(@TimeCaps1, sizeof(TimeCaps1)) = TIMERR_NOERROR then
TimeBeginPeriod(TimeCaps1.wPeriodMin)
else TimeCaps1.wPeriodMin := 0;
{the 2 lines TimeGetDevCaps and TimeBeginPeriod above are NEEDED
to set up your MultiMedia Timer which is much better for movement,
animation, sprite drawing than a SetTimer.}

if not QueryPerformanceFrequency(PerFreq) then
PerFreq := 0;
{QueryPerformanceFrequency gets the number of times a second
this PerformanceCounter ticks, it can be above a million ticks
per second}

GetWindowRect(hForm1, Rect1);
MaskDC := GetDC(0);
{this program draws it's Splash Screen on the desktop,
it is better NOT to draw on the desktop, I do it here
to show that you can draw on the desktop, but you should
create a splash window to draw on, like the BrushPen program}
SelectObject(MaskDC, Font1);
SetTextColor(MaskDC, $003366FF);
SetBkColor(MaskDC,$00DDEE33);
SetTextCharacterExtra(MaskDC, 8);
SetTextAlign(MaskDC, TA_UPDATECP);
MoveToEx(MaskDC,Rect1.Left+106,Rect1.Top+108, nil);
SelectObject(MaskDC,Pen1);
SelectObject(MaskDC, GetStockObject(NULL_BRUSH));
Rectangle(MaskDC, Rect1.Left+78, Rect1.Top+28, Rect1.Left+383, Rect1.Top+283);
SelectObject(MaskDC,Brush1);
{2 timers are started, timer "2" will progressivly draw some text for the
splash screen, Timer "1" will end the splash screen and show the main Form}
SetTimer(hForm1,2,330, @TimerProc);
SetTimer(hForm1,1,4500, @TimerProc);

while GetMessage(mainMsg,0,0,0) do
  begin
    TranslateMessage(mainMsg);
    DispatchMessage(mainMsg);
  end;


end.



       






H O M E