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

Home
DelphiZeus
postNote, Show you some Notes

Home



This is a Note Posting program using in a file called postNote.dpr , it makes a yellow note main Form without a Caption. There are numbered notes displayed which can be changed by right clicking the main Form and getting a Popup Menu. The main Form is sized for the amount of notes that are displayed. This reads and writes to a .INI file.
Under Construction

program postNote;

uses
  Windows, Messages, commctrl, Shellapi, smallUtils;
                     {CommCtrl is needed for UpDown Button
                      ShellApi is needed for Tray Icon}

{$R *.RES}

var
wClass, adClass: TWndClass;
hNoteHandle, Font1, Font2, Font3, Font4, hAdHandle, hOkBut, hAddBut, hAddABut, hDeleteBut,
hLabel1, hEdit1, hUpDown1, hEdit2, hGroup1, hListBox1, hUpdateBut, hHideBut, hPanel1,
hDelSelBut, hLabel2, hLabel3, hNoChangeBut, hIcon1, hCloseBut, hMinBut, hRBStand, hRBYellow: HWND;
Msg: TMSG;
Brush1: HBRUSH;
IniName, ButtonChar: PChar;
CharBuffer: array[0..255] of Char;
{I use an array length of 255, which is more than needed here.
most instructions tell you to get the length of the API "string"
but if you know of a maxinum length then create an array that's larger
and use it in several different calls. .
if you use an array that's smaller you lose the charaters past your array size}

j, Top, Left, NoteNum, Position, appWidth, FontSize, EditAdj, EditHgt: Integer;
NoteList: Array[0..25] of String;
{NoteList is [0..25] instead of [0..23] to allow for copying}

Rect1: TRect;
Size1: TSize;
IconData: TNotifyIconData;
PGroup1Proc, PListBox1Proc, PPanel1Proc: Pointer;
DoChange, IsMar: Boolean;
UpSelect, DownSelect: Byte;
LogFont1: TLogFont;

const
  PostNotes = 'Post  Notes';
  FormPos = 'Position';
  Notes = 'Notes';
  Welcome = 'Welcome to your Post Notes, right click for menu';

  {the application defined notification message}
  WM_TRAYICONCLICKED = WM_USER + 1;
  {the WM_USER is the begining of the range for User messages
  DO NOT send this message to another Application unless you
   define it there like you did here}

procedure ShutDown;
var
WinPlace: TWindowPlacement;
begin
WinPlace.length := SizeOf(TWindowPlacement);
Shell_NotifyIcon(NIM_DELETE, @IconData);
{NIM_DELETE removes the tray Icon}

GetWindowPlacement(hNoteHandle,@WinPlace);
{since you can close this while it is minimized, you can get the
WinPlace.rcNormalPosition Rect for the normal position, gettting the
window rect will give false position if minimized}
WritePrivateProfileString(FormPos,'Top',PChar(Int2Str(WinPlace.rcNormalPosition.Top)),IniName);
WritePrivateProfileString(FormPos,'Left',PChar(Int2Str(WinPlace.rcNormalPosition.Left)),IniName);

{when you create a font, brush or bitmap, these are Windows OS
Objects and you need to Delete them, or they will stay in the OS
until a reboot}
DeleteObject(Brush1);
DeleteObject(Font1);
DeleteObject(Font2);
DeleteObject(Font3);
DeleteObject(Font4);

{these DestroyWindow calls may not be needed.
I put them here to make sure the Windows OS
cleans out it's references to them and avoid
memory leaks}
DestroyWindow(hOkBut);
DestroyWindow(hAddBut);
DestroyWindow(hAddABut);
DestroyWindow(hUpdateBut);
DestroyWindow(hDeleteBut);
DestroyWindow(hDelSelBut);
DestroyWindow(hNoChangeBut);
DestroyWindow(hHideBut);
DestroyWindow(hCloseBut);
DestroyWindow(hMinBut);
DestroyWindow(hIcon1);
DestroyWindow(hLabel1);
DestroyWindow(hLabel2);
DestroyWindow(hLabel3);
DestroyWindow(hEdit1);
DestroyWindow(hUpDown1);
DestroyWindow(hEdit2);
DestroyWindow(hGroup1);
DestroyWindow(hListBox1);
DestroyWindow(hAdHandle);
UnregisterClass(adClass.lpszClassName,hInstance);

PostQuitMessage(0);
{PostQuitMessage tells the Windows OS that your message loop is ending,
so it will destroy the references (handles of windows and created classes)
that are recorded by Widows OS. If you exit the process with
ExitProcess(hInstance); then these handles and Classes remain in the OS
until a reboot. Use a Memory leak program to see this.
PostQuitMessage ends the GetMessage loop but not your process,
you can do clean up code below the GetMessage loop}
end;

procedure CheckNum;
{this procedure checks the user input to Edit2
and limits it to the range of NoteNum}
var
Str1: String;
begin
Str1 := GetWindowStr(hEdit2);
{the GetWindowStr(hWnd) and GetWindowPChar(hWnd) are 2
very useful functions, you can use the GetWindowPChar to
transfer one windows text to another like this -
SetWindowText(hEdit1,GetWindowPChar(hEdit2));}
if (Str1 = '0') then
begin
SetWindowText(hEdit2,'1');
Str1 := '1';
Exit;
end;
if Str2Int(Str1) > NoteNum then
  begin
  SetWindowText(hEdit2,PChar(Int2Str(NoteNum)));
  Str1 := Int2Str(NoteNum);
  end;
if Str1 <> '' then
SetWindowText(hDeleteBut,PChar('Delete Note '+Str1));
end;

procedure SizeNote;
{this calculates the vertical size needed for
the main Post Notes window using TextMetrics from
GetTextExtentPoint32  and  DrawText}
var
i: Integer;
TempDC: HDC;
begin
TempDC := GetDC(hNoteHandle);
SelectObject(TempDC,Font2);
{for a Device Context
(HDC, see Win32 API help for "Device Contexts", like of a Canvas in Delphi)
you need to SelectObjects (fonts, pens, brushes) to use in Drawing on that DC}

GetTextExtentPoint32(TempDC, PostNotes, 10, Size1);
{GetTextExtentPoint32 gets the size of text for that DC}
SetRect(Rect1,1,5,appWidth-2,5+Size1.cy);
{5+Size1.cy is used to put a 5 pixel border on top}
Position := 5+Size1.cy+2;
SetBkMode(TempDC, TRANSPARENT);
SelectObject(TempDC,Font1);
GetTextExtentPoint32(TempDC, '24. ', 4, Size1);
{'24. ' is used to get the left side space for the Numbers}
EditAdj := 6+Size1.cx;
if NoteNum > 0 then
for i := 0 to NoteNum-1 do
begin
SetRect(Rect1,6+Size1.cx,Position,appWidth-6,Position + Size1.cy);
DrawText(TempDC,PChar(NoteList[i]),Length(NoteList[i]),Rect1,DT_LEFT or DT_WORDBREAK or DT_CALCRECT);
{DrawText() with the  DT_CALCRECT  flag will NOT draw any text, instead it
adjusts the Rect1 bottom so that all of the text will fit in the rect}
Position := Rect1.Bottom + 2;
{use the new Rect1.Bottom for the next Rect1.Top}
end;
{to call GetDC and then ReleaseDC may seem unnessary
why not just call GetDC once and then ReleaseDC once when the app closes
(like Delphi VCL), these GetDC are a quick functions and
releasing it cuts down on window's system resources use}
ReleaseDC(hNoteHandle,TempDC);

{there are alot of flags for SetWindowPos(), which can help for moving
or sizing a window or changing Z order or focus}
SetWindowPos(hNoteHandle,0,
    Left,	// horizontal position
    Top,	// vertical position
    appWidth,	// width
    Rect1.Bottom+20,	// height
    SWP_NOCOPYBITS or SWP_NOMOVE or SWP_SHOWWINDOW or SWP_NOZORDER 	// window-positioning flags
    );
end;

procedure FillListBox;
var
i: Integer;
NumStr: PChar;
begin
SendMessage(hListBox1, LB_RESETCONTENT, 0, 0);
{clears the ListBox}
if NoteNum > 0 then
  begin
  for i := 0 to NoteNum-1 do
    begin
    NumStr := PChar(Int2Str(i+1)+' '+NoteList[i]);
    SendMessage(hListBox1, LB_ADDSTRING, 0, Integer(NumStr));
    SendMessage(hListBox1, LB_SETITEMDATA, i, i+1);
    {A number is added to the NoteList string and it is added to
     the ListBox}
    end;
  end else
SendMessage(hListBox1, LB_ADDSTRING, 0, Integer(' '));
{if there are no NoteList strings then just add a Space
character so there will be a Selection Showing}

SendMessage(hListBox1,LB_SETCURSEL,0,0);
{this sets the Selection in the ListBox, you could calculate
the previous select and go to that one again}
SetFocus(hListBox1);
end;

procedure AddNote(before: Boolean);
{this Adds the Text in Edit1 to the NoteList and
refreshes the listBox and updates the Post Note window}
var
TempList: Array[0..23] of String;
Pos, i: Integer;
EditStr: String;
begin
if NoteNum > 23 then
  begin
  {this limits the Notes to 24}
  MessageBox(hNoteHandle,'You have added the Maximum of  24  Notes, sorry no can do','NO MORE NOTES !',MB_OK or MB_ICONERROR);
  Exit;
  end;
EditStr := GetWindowStr(hEdit1);
if EditStr = '' then
EditStr := ' ';
{If there is No text in Edit1 then put a Space so it is not empty for the INI}

if NoteNum <> 0 then
for i := 0 to NoteNum-1 do
  TempList[i] := NoteList[i];
{since we can add the note to any position, a copy of the NoteList is made}

{we don't want to record the Welcome message}
if (NoteNum = 1) and (NoteList[0] = Welcome) then
NoteList[0] := EditStr else
  begin
  if NoteNum = 0  then
  begin
  NoteList[0] := EditStr;
  Inc(NoteNum);
  end else
    begin
      Pos := SendMessage(hListBox1, LB_GETCURSEL, 0, 0);
      {get the select index from ListBox}
      if Pos < 0 then Exit;
      {Exit if nothing is selected}
    if not Before then Inc(Pos);
    {determine if note goes before selection}
    NoteList[Pos] := EditStr;
    for i := Pos+1 to NoteNum do
      NoteList[i] := TempList[i-1];
      {copy all remaining notes from TempList}
    Inc(NoteNum);
    end;
  end;

EnableWindow(hEdit2, True);
EnableWindow(hDeleteBut, True);
sendMessage(hUpDown1,UDM_SETRANGE,0,MAKELONG(short(NoteNum), 1));
{you set the RANGE of your UpDown button,
the MAKELONG combines 2 16bit values into one 32bit value}

SetWindowText(hEdit2,'1');
for i := 0 to NoteNum - 1 do
WritePrivateProfileString(Notes,PChar(Int2Str(i+1)),PChar(NoteList[i]),IniName);
{we write all the NoteList to the INI  file}

for i := 0 to 23 do
  TempList[i] := '';
{Empty  TempList to release memory}
SizeNote;
FillListBox;
InvalidateRect(hNoteHandle,nil,True);
{InvalidateRect  redraws the main Post Note window}
end;


procedure DeleteNote(selNum: Integer);
{Deletes selected note}
var
i: Integer;

procedure NoteNum0;
var
n: Integer;
begin
{used when there are no NoteList strings}
NoteNum := 0;
SetWindowText(hEdit2,'1');
EnableWindow(hEdit2, False);
EnableWindow(hDeleteBut, False);
for n := 1 to 24 do
WritePrivateProfileString(Notes,PChar(Int2Str(n)),nil,IniName);
{if  nil  is used for the Text in  WritePrivateProfileString
then the line is conpleatly eliminated}
end;

begin
if NoteNum < 1 then
  NoteNum0 else
begin
if NoteNum = 1 then
  begin // NoteNum = 1
  NoteList[0] := '';
  NoteNum := 0;
  end else
  begin
  if selNum+1 = NoteNum then
    NoteList[selNum] := ''
    else
    for i := selNum to NoteNum do
      NoteList[i] := NoteList[i+1];
  Dec(NoteNum);
  end;
end; // NoteNum = 1
if NoteNum < 1 then
  NoteNum0 else
  begin
  SetWindowText(hEdit2,'1');
  for i := 0 to NoteNum-1 do
    WritePrivateProfileString(Notes,PChar(Int2Str(i+1)),PChar(NoteList[i]),IniName);
  for i := NoteNum+1 to 24 do
    WritePrivateProfileString(Notes,PChar(Int2Str(i)),nil,IniName);
  end;

if NoteNum > 0 then
sendMessage(hUpDown1,UDM_SETRANGE,0,MAKELONG(short(NoteNum), 1));
SizeNote;
FillListBox;
InvalidateRect(hNoteHandle,nil,True);
end;

procedure ChangeNotes;
{this updates NoteList}
var
i, Num: Integer;
TempList: Array[0..23] of String;
TempStr: String;

begin
for i := 0 to NoteNum-1 do
  TempList[i] := NoteList[i];
  {copy NoteList}

{for i := 0 to 3 do
begin
Num := SendMessage(hListBox1, LB_GETITEMDATA, i, 0);
MessageBox(hAdHandle,PChar('Item Data for 1 is  '+Int2Str(Num)+' '+Int2Str(SendMessage(hListBox1, LB_GETITEMDATA, i+1, 0))) ,
          'No Get Item Data',MB_YESNO or MB_ICONQUESTION);
  if Num = LB_ERR then  Exit;
end;}


for i := 0 to NoteNum-1 do
  begin
  SendMessage(hListBox1, LB_GETTEXT, i, Integer(@CharBuffer));
  TempStr := String(CharBuffer);
  Delete(TempStr,3,260);
  Num := SendMessage(hListBox1, LB_GETITEMDATA, i, 0);
  if Num < 1 then Exit;
  NoteList[i] := TempList[Num-1]
  {eliminate everything but the number in the string}
  {NoteList[i] := TempList[OldNum-1];}
  {if Str2Int(TempStr) > 9 then
    NoteList[i] := TempList[Str2Int(TempStr)-1] else
    NoteList[i] := TempList[Str2Int(TempStr[1])-1];}
    {copy the notes fromthe tempList to new positions in listBox}
  end;
FillListBox;
for i := 0 to NoteNum-1 do
  TempList[i] := '';
SizeNote;
InvalidateRect(hNoteHandle,nil, True);
TempStr := '';
EnableWindow(hDelSelBut, True);
EnableWindow(hUpdateBut, False);
EnableWindow(hNoChangeBut, False);
for i := 0 to NoteNum-1 do
    WritePrivateProfileString(Notes,PChar(Int2Str(i+1)),PChar(NoteList[i]),IniName);

end;

procedure DragDropListBox;
{the listBox is changed after a drag and drop}
var
{TempPChar: PChar;}
Pos: Byte;
PrevNum: Integer;
begin
{UpSelect, DwnSelect are from the ListBoxProc fuction}
Pos := UpSelect;
SendMessage(hListBox1, LB_GETTEXT, DownSelect, Integer(@CharBuffer));
PrevNum := SendMessage(hListBox1, LB_GETITEMDATA, DownSelect, 0);
{remember that you can use
length := SendMessage(hListBox1, LB_GETTEXTLEN, DwnSelect, 0);
to get the length of the PChar string in that item index and
set a memory allocation to that, like this - -
length := SendMessage(hListBox1, LB_GETTEXTLEN, DwnSelect, 0);
GetMem(TempPChar, length+1); // 1 added for #0
SendMessage(hListBox1, LB_GETTEXT, DwnSelect, Integer(TempPChar));
SendMessage(hListBox1, LB_INSERTSTRING, UpSelect, Integer(TempPChar));
FreeMem(TempPChar);
but if you create an Array of Char to a length of more than is needed
like  CharBuffer  here, then you won't have to make all those calls
for GetMem(TempPChar, length+1);
I use a 1 Kb length alot
Buffer: Array[0..1023] of Char}

if DownSelect < UpSelect then
  Inc(UpSelect);
SendMessage(hListBox1, LB_INSERTSTRING, UpSelect, Integer(@CharBuffer));
SendMessage(hListBox1, LB_SETITEMDATA, UpSelect, PrevNum);
if DownSelect > UpSelect then
  Inc(DownSelect);
SendMessage(hListBox1, LB_DELETESTRING, DownSelect, 0);
SendMessage(hListBox1, LB_SETCURSEL, Pos, 0);
EnableWindow(hDelSelBut, False);
EnableWindow(hUpdateBut, True);
EnableWindow(hNoChangeBut, True);
end;

function Group1Proc(hWnd,Msg,wParam,lParam: Integer): Integer; stdcall;
begin
{this is where the Group1 messages are processed}
case Msg of
  {because hDeleteBut is a child of Group1 it's button
   click messages are processed here}
  WM_COMMAND: if lParam = abs(hDeleteBut) then
                begin
                if GetWindowStr(hEdit2) = '' then
                  SetWindowText(hEdit2,'1')
                  else
                  DeleteNote(SendMessage(hUpDown1,UDM_GETPOS,0,0)-1);
                end
              else if (lParam = abs(hEdit2)) and (HIWORD(wParam) = EN_UPDATE) then CheckNum;
              {you want to check what the user types into Edit2 with  lParam = abs(hEdit2)
              and a "Notification message" EN_UPDATE, which is in the upper 16 bit value
              of the 32 bit wParam number. The EN_UPDATE is sent BEFORE the text is displayed}
  end; // case

Result := CallWindowProc(PGroup1Proc, hWnd, Msg, wParam, lParam);
{if you have transfered a window's WndProc with a
SetWindowLong(hGroup1, GWL_WNDPROC, Longint(@Group1Proc));
you have to get default processing with a
CallWindowProc()
and NOT a  DefWindowProc() }
end;

function ListboxProc(hWnd,Msg,wParam,lParam: Integer): Integer; stdcall;
{we need to get mouse click messages here for Drag and Drop}
var
X, Y, selNum: Integer;
begin
  case Msg of
  WM_LBUTTONUP: begin
                X := LOWORD(lParam);
                Y := HIWORD(lParam);
                {mouse position is given in the HI and LO Word positions of lParam}
                selNum := SendMessage(hListBox1, LB_ITEMFROMPOINT, 0, MAKELPARAM(X, Y));
                if X < 145  then
                {X < 147 is to make sure the mouse BUTTONUP was inside the listbox
                  you might check Y also}
                  begin
                  if DoChange and (DownSelect <> selNum) then
                    begin
                    UpSelect := selNum;
                    DragDropListBox;
                    {ChangeListBox rearranges the notes but does not
                      change their numbers}
                    end;
                  end;
                end;
  WM_LBUTTONDOWN: begin
                  X := LOWORD(lParam);
                  Y := HIWORD(lParam);
                  selNum := SendMessage(hListBox1, LB_ITEMFROMPOINT, 0, MAKELPARAM(X, Y));
                  {LB_ITEMFROMPOINT gets the item index closest to the point}
                  if HIWORD(selNum) = 0 then
                    begin
                    DownSelect := LOWORD(selNum);
                    DoChange := True;
                    end;
                  end;
  end;
Result:=CallWindowProc(PListBox1Proc,hWnd,Msg,wParam,lParam);
end;

procedure HideGroup;
begin
{since the controls in Group1 are children
of the group they hide also}
if IsWindowVisible(hGroup1) then
  begin
  ShowWindow(hGroup1,SW_HIDE);
  ShowWindow(hPanel1,SW_SHOW);
  {ShowWindow(hRBYellow,SW_SHOW);}
  SetWindowText(hHideBut,'Show Group');
  end else
  begin
  ShowWindow(hGroup1,SW_SHOW);
  ShowWindow(hPanel1,SW_HIDE);
  {ShowWindow(hRBYellow,SW_HIDE);}
  SetWindowText(hHideBut,'Hide Group');
  end;
end;

procedure RBStandClick(Stand: Boolean);
begin
SendMessage(hRBYellow, BM_SETCHECK, Integer(not Stand), 0);
SendMessage(hRBstand, BM_SETCHECK, Integer(Stand), 0);
if Stand then
begin
SetClassLong(hAdHandle,GCL_HBRBACKGROUND,COLOR_BTNFACE+1);
end else
begin
SetClassLong(hAdHandle,GCL_HBRBACKGROUND,Brush1);
end;
invalidateRect(hAdHandle,nil,True);
end;

function Panel1Proc(hWnd,Msg,wParam,lParam: Integer): Integer; stdcall;
var
PS: TPaintStruct;

begin
case Msg of
{WM_PAINT: begin
          SetRect(Rect1,2,2,220,56);
          BeginPaint(hWnd, PS);
          DrawEdge(PS.hDC,Rect1,EDGE_RAISED,BF_RECT);
          EndPaint(hWnd,PS);
          Result := 0;
          Exit;
          end;}
  WM_COMMAND: if LParam = abs(hRBStand) then RBStandClick(True)
            else if LParam = abs(hRBYellow) then RBStandClick(False)

  end; // case

Result := CallWindowProc(PPanel1Proc, hWnd, Msg, wParam, lParam);
end;

function AddDelProc(hWnd, Msg, wParam, lParam: LongInt):LongInt; stdcall;
{message from the Add Delete window are processed here}
var
Num: Integer;
begin
case Msg of
WM_DESTROY: SetFocus(hNoteHandle);

WM_COMMAND: begin
            if LParam = abs(hOkBut) then PostMessage(hAdHandle,WM_CLOSE,0,0)
            {to Close and destroy a Window of a New Class like hAdHandle
             use PostMessage(hAdHandle,WM_CLOSE,0,0) which will use the default
             Close and Destroy for hAdHandle, destroying it and all of it's children,
             DO NOT CALL PostQuitMessage, which will Terminate EVERYTHING}
            else if lParam = abs(hAddBut) then AddNote(True)
            else if lParam = abs(hAddABut) then AddNote(False)
            else if lParam = abs(hHideBut) then  HideGroup
            else if lParam = abs(hDelSelBut) then
              begin
              Num := SendMessage(hListBox1,LB_GETCURSEL,0,0);
              if Num <> LB_ERR then
              {test to see if item is selected in ListBox}
              DeleteNote(Num);
              end
            else if lParam = abs(hUpdateBut) then ChangeNotes
            else if lParam = abs(hNoChangeBut) then
              begin
              FillListBox;
              EnableWindow(hDelSelBut, True);
              EnableWindow(hUpdateBut, False);
              EnableWindow(hNoChangeBut, False);
              end
              else if (lParam = abs(hEdit1)) and (HIWORD(wParam) = EN_MAXTEXT) then
              {the EN_MAXTEXT notification is sent when the text exceeds the Edits capasity}
              MessageBox(hAdHandle,PChar('You tried to put more text in this Edit than it will hold '+#13+SysErrorMessage(GetLastError)),
                'Too much Text',MB_OK or MB_ICONERROR);

            end;
WM_CTLCOLOREDIT: if lParam = abs(hEdit1) then
{this is the pre Edit Paint message to get colors
to paint the Edit}
                      begin
                      SetTextColor(wParam,$00000000);
                      SetBkColor(wParam,$0080FFFF);
                      Result := wClass.hbrBackground{Brush1};
                      {result is the brush used to paint anything
                      not covered by text}
                      Exit;
                      {IMPORTENT
                       You MUST Exit so the DefWindowProc is NOT called
                       try it without Exit and the Edit will NOT change colors}
                      end;
end; //case
Result := DefWindowProc(hWnd,Msg,wParam,lParam);
{since this is a New Class we use the
DefWindowProc() here}
end;

procedure ShowAddDelete;
begin
{this creates the Add & Delete Dialog Box
it could be sized for the Screen Resolution}
if hAdHandle = 0 then
{IMPORTANT
when the hAdHandle closes it is destroyed by default.
But the adClass is NOT released so you don't have to register it again}
  begin
  {I create a second Class since I want a different background
  color AND (more Important) a different lpfnWndProc}
  adClass.hInstance := hInstance;
    with adClass do
    begin
      Style:=         CS_PARENTDC or CS_BYTEALIGNCLIENT;
      hIcon:=         LoadIcon(hInstance,'MAINICON');
      lpfnWndProc:=   @AddDelProc;
      {this Sub-Class uses a different message WndProc
        look at the AddDelProc  above and compare it to
        MessageProc  below}
      hbrBackground:= COLOR_BTNFACE+1;
      lpszClassName:= 'Add Delete';
      hCursor:=       wClass.hCursor;
    end;
  RegisterClass(adClass);
end; // hAdHandle = 0

  GetWindowRect(hNoteHandle,Rect1);
  {we need the Main window's position in Rect1 to position the hAdHandle window}

  {by creating a WS_EX_TOOLWINDOW , we can assign a Parent (hNoteHandle) to it}
  hAdHandle := CreateWindowEx(WS_EX_TOOLWINDOW , adClass.lpszClassName,' Add and Delete your Notes',
    WS_CAPTION or WS_SYSMENU{ or WS_MINIMIZEBOX},
    {WS_SYSMENU puts an X close button on it}
    Rect1.Left + 120,  // Left
    Rect1.Top + 20,  // Top
    420,                     // Width
    428,                     // Height
    hNoteHandle,                       // Parent Window Handle
    0,                       // Handle of Menu
    hInstance,                   // Application Instance
    nil);

  hPanel1 := CreateWindowEX(WS_EX_TOPMOST {WS_EX_CONTROLPARENT {or WS_EX_WINDOWEDGE or WS_EX_CLIENTEDGE,},
             adClass.lpszClassName,'',{WS_VISIBLE or }WS_CHILD or WS_DLGFRAME{ or WS_CLIPSIBLINGS {WS_BORDER{SS_CENTER},
    13,12,320,60,hAdHandle,0,hInstance,nil);

  hLabel1:=CreateWindow('Static','Change the Notes',WS_VISIBLE or WS_CHILD or SS_CENTER or WS_CLIPSIBLINGS,
    6,6,374,26,hAdHandle,0,hInstance,nil);

  {this Group1, Edit2, UpDown1, Icon1, and DeleteBut are here just to show you how to make Group1 a
  parent for Edit2 DeleteBut, and UpDown1, we will have to SubClass it below to get the messages for it}
  hGroup1 := CreateWindow('Button','Just for API use info',
    WS_VISIBLE or WS_CHILD or BS_GROUPBOX or BS_TEXT or WS_CLIPSIBLINGS,
    13,32,280,60,hAdHandle,0,hInstance,nil);
{if you move the Group1 control then all the children move with it, if you Hide Group1 then all children are hidden}

  hEdit2:=CreateWindowEx(WS_EX_CLIENTEDGE, 'Edit', '1', WS_VISIBLE or WS_CHILD or ES_LEFT or ES_NUMBER or WS_CLIPSIBLINGS,
    8,22,52,25,hGroup1,0,hInstance,nil);
  {the positions  25,22,52,25  are relative to the Group1, NOT the  hAdHandle window}

  hUpDown1 := CreateUpDownControl(WS_CHILD or WS_VISIBLE or UDS_SETBUDDYINT or
   UDS_ALIGNRIGHT or UDS_ARROWKEYS {or UDS_AUTOBUDDY},25,12,19,25,hGroup1,123,hInstance,hEdit2,NoteNum,1,1);
   {the UpDown1 control can have a "Buddy" control (UDS_SETBUDDYINT), that it will attatch to (hEdit2)
     UpDown controls use a different messaging type than regular buttons, see "Up-Down Controls" in Win32 API help}

  hDeleteBut := CreateWindow('Button','Delete Note 1',WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT or WS_CLIPSIBLINGS,
    70,22,110,24,hGroup1,0,hInstance,nil);





  hRBstand:=CreateWindow('Button','Standard Color form',
    WS_VISIBLE or WS_CHILD or BS_RADIOBUTTON,
    8,5,180,24,hPanel1,0,hInstance,nil);

  hRBYellow:=CreateWindow('Button','Yellow Color form',
    WS_VISIBLE or WS_CHILD or BS_RADIOBUTTON,
    8,30,180,24,hPanel1,0,hInstance,nil);

  hIcon1 := CreateWindow('Static', 'MAINICON', WS_VISIBLE or WS_CHILD or SS_ICON,
    188,8{22},1,1,hPanel1,0,hInstance,nil);
  {this Icon is created in a "Static" control, you give the resource name 'MAINICON' for the text
   and it will size the control to the Icon, ignoring any width and height that you have given
   a "Static" control will display a bitmap in the same way, with the resource name 'Bitmap8' in the text, ignoring width and height}

  hHideBut := CreateWindow('Button','Hide Group',WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT or WS_CLIPSIBLINGS,
    310,40,90,24,hAdHandle,0,hInstance,nil);

  {I want to color the background for this Edit, see the WM_CTLCOLOREDIT in adProc}
  hEdit1:=CreateWindowEx(WS_EX_CLIENTEDGE, 'Edit', 'type New Notes here', 
                         WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE {or ES_WANTRETURN},
    5,100,AppWidth-EditAdj,(EditHgt*3)+7,hAdHandle,0,hInstance,nil);
    {the Height and Width of this Edit1 is changed to match the Font size used and the width of the Post Notes
      so what the user see's in this edit box will match what is shown on Post Notes.
      Without the ES_AUTOVSCROLL style, no text can be entered beyond the 3 lines shown, this limits the
      text length to less than 150 or so, Now we can have CharBuffer length of 256 and have room to spare}

  {Room has been left for the font resizing of Edit1}
  hLabel2:=CreateWindow('Static','<-- Click on Notes to select',WS_VISIBLE or WS_CHILD or SS_LEFT,
    170,188,170,EditHgt+2,hAdHandle,0,hInstance,nil);

  hAddBut := CreateWindow('Button','add Note Before',WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    170,208,116,26,hAdHandle,0,hInstance,nil);

  hAddABut := CreateWindow('Button','add Note After',WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    170,238,104,26,hAdHandle,0,hInstance,nil);

  hDelSelBut := CreateWindow('Button','Delete Select',WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    170,278,104,26,hAdHandle,0,hInstance,nil);

  hNoChangeBut := CreateWindow('Button','Undo Changes',WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT or WS_DISABLED,
    286,278,110,26,hAdHandle,0,hInstance,nil);
    {this will UnDo  all drag and drops of ListBox}

  hLabel3:=CreateWindow('Static',
    '<-- Drag and Drop notes to change their order, Click Update to apply changes and Undo Changes to restore',
    WS_VISIBLE or WS_CHILD, 160,304,252,(EditHgt*3)+2,hAdHandle,0,hInstance,nil);

  hUpdateBut := CreateWindow('Button','Update Notes',WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT or WS_DISABLED,
    170,372,104,26,hAdHandle,0,hInstance,nil);

  hListBox1 := CreateWindowEx(WS_EX_CLIENTEDGE,'LISTBOX','',
    WS_VISIBLE or WS_CHILD or LBS_HASSTRINGS or LBS_NOTIFY or WS_VSCROLL,
    8,188,150,220,hAdHandle,0,hInstance,nil);
    {this listbox is only wide enough to see the begining of the message
      so you can drag and drop them to rearrange them}

  hOkBut := CreateWindow('Button','OK',WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    330,374,64,26,hAdHandle,0,hInstance,nil);

  {Fonts are assigned here}
  SendMessage(hLabel1,WM_SETFONT,Font2,0);
  SendMessage(hGroup1,WM_SETFONT,Font3,0);
  SendMessage(hOkBut,WM_SETFONT,Font2,0);
  SendMessage(hEdit1,WM_SETFONT,Font1,0);
  {make sure to give the same font to Edit1 that is used for the notes}
  SendMessage(hListBox1,WM_SETFONT,Font3,0);
  SendMessage(hLabel2,WM_SETFONT,Font3,0);
  SendMessage(hLabel3,WM_SETFONT,Font3,0);
  SendMessage(hHideBut,WM_SETFONT,Font3,0);
  SendMessage(hRBStand,WM_SETFONT,Font3,0);
  SendMessage(hRBYellow,WM_SETFONT,Font3,0);

  SendMessage(hRBStand, BM_SETCHECK, 1, 0);

{it's called SubClassing, it changes the Class reference to WndProc,
We don't make any New Classes but like the New Class we made, "adClass" , we want to have
another WndProc message function for the Group1 and ListBox1. - - -
We need to get a Pointer to the memory address of the window's WndProc for each
control, so we can send them for default message handleing. Then use SetWindowLong with
GWL_WNDPROC to tell the Windows OS where to send the messages}
PGroup1Proc := Pointer(SetWindowLong(hGroup1, GWL_WNDPROC, Longint(@Group1Proc)));
                                                   {sends messages to Group1Proc}
PListbox1Proc := Pointer(SetWindowLong(hListBox1, GWL_WNDPROC, Longint(@ListboxProc)));
                                         {see Group1Proc and ListboxProc  above}
PPanel1Proc := Pointer(SetWindowLong(hPanel1, GWL_WNDPROC, Longint(@Panel1Proc)));
FillListBox;
{FillListBox puts the NoteList into the ListBox1}

UpdateWindow(hAdHandle);
ShowWindow(hAdHandle, SW_SHOW);
SetFocus(hEdit1);
SendMessage(hEdit1,EM_SETSEL,0,-1);
{EM_SETSEL message with a  -1  in the lParam will
 select all the text in the edit}
end;

procedure ShowPopMenu(IsTray: Boolean; X, Y: Integer);
var
hPopMenu: HMENU;
begin
hPopMenu := CreatePopupMenu;
{this CreatePopupMenu fuction makes an empty menu}

{there are 2 Pop menus, the IsTray sets it to the app Right
click menu or the Tray click menu}
if not IsTray then
begin
{InsertMenu() adds Items to a menu}
InsertMenu(hPopMenu,0,MF_BYPOSITION or MF_STRING,101,'Change Notes');
                                  {the  101  is the ID number sent to
                                the WndProc message function when menu click}
InsertMenu(hPopMenu,1,MF_BYPOSITION or MF_STRING,102,'Hide');
InsertMenu(hPopMenu,2,MF_BYPOSITION or MF_STRING,103,'About');
InsertMenu(hPopMenu,3,MF_BYPOSITION or MF_STRING,104,'Exit');
end else
begin
if not IsIconic({IsWindowVisible(}hNoteHandle) then
InsertMenu(hPopMenu,0,MF_BYPOSITION or MF_STRING,102,'Hide Notes')
else InsertMenu(hPopMenu,0,MF_BYPOSITION or MF_STRING,105,'Show Notes');
InsertMenu(hPopMenu,1,MF_BYPOSITION or MF_STRING,103,'About');
InsertMenu(hPopMenu,2,MF_BYPOSITION or MF_STRING,104,'Exit');
end;


{the TrackPopupMenu fuction is active as long as the PopUp Menu is displayed,
so nothing else is happening in this thread while the menu is up}
TrackPopupMenu(hPopMenu,	// handle of shortcut menu
    TPM_LEFTALIGN or TPM_LEFTBUTTON,	// screen-position and mouse-button flags
    X-5,	// horizontal position, in screen coordinates
    Y-5,	// vertical position, in screen coordinates
    0,	// reserved, must be zero
    hNoteHandle,	// handle of window that gets menu messages
    { see  MessageProc at the WM_COMMAND for menu messages}
    nil	// points to RECT that specifies no-dismissal area
   );
DestroyMenu(hPopMenu);
end;

procedure DoAbout;
{shows a custom Icon Message Box with
"About" info in it}
var
  MsgParams: TMsgBoxParams;
begin
with MsgParams do
  begin // with
  cbSize := SizeOf(MsgParams);
  hwndOwner := hNoteHandle;
  hInstance := wClass.hInstance; //where to find resources, Icons
  lpszText := '         This is Post Notes version 0.0'#13'By Lost in Space, Know Nothing programmers'#13'In a custom Icon, message box';
  lpszCaption := ' About  Post Note version 2.2';
  dwStyle := MB_OK or MB_USERICON or MB_APPLMODAL;
  lpszIcon := 'MAINICON';
  dwContextHelpId := 0; //help context
  lpfnMsgBoxCallback := nil;
  dwLanguageId := LANG_NEUTRAL;
  end; //with
MessageBoxIndirect(MsgParams);
{MessageBoxIndirect lets you put a custom Icon AND create a NON-MODAL
  MessageBox, this one is Modal}
end;

function MessageProc(hWnd, Msg, wParam, lParam: Integer):Integer; stdcall;
var
paintDC: HDC;
PS: TPaintStruct;
i: Integer;
MousePos: TPoint;
Const
SC_DragMove = $F012;
begin
case Msg of
WM_PAINT: begin
          SetRect(Rect1,1,5,appWidth-2,28);
          paintDC := BeginPaint(hWnd, PS);
          {the DC that is returned from BeginPaint() is
           different than the DC from GetDC(hAdHandle)
           although they both paint on the same pixels
           the BeginPaint DC is Clipped to only the area
           that needs repainting, which is availible in
           the PS variable as a TRect as PS.rcPaint}
          SelectObject(paintDC,Font2);
          {SelectObject() changes the font used for text drawing for paintDC}

          GetTextExtentPoint32(paintDC, PostNotes, 10, Size1);
          {get's the Size1 of the title}

          SetRect(Rect1,1,5,appWidth,5+Size1.cy);
          {you could also usee Delphi's Rect(top,left,bottom,right);}

          Position := 5+Size1.cy+2;
          SetBkMode(paintDC, TRANSPARENT);
          {SetBkMode to TRANSPARENT means that ONLY text is drawn}
          if PS.rcPaint.Top < 5+Size1.cy then
          {to Increase performance you can test the area to be repainted
           with the PS.rcPaint  Rect
           the DrawText and TextOut are fast anyway so you may not need
           to test for that}
          DrawText(paintDC,PostNotes,10,Rect1,DT_CENTER);
          
          SelectObject(paintDC,Font1);
          {change fonts to smaller one}
          GetTextExtentPoint32(paintDC, '24. ', 4, Size1);
          EditAdj := 6+Size1.cx;
          EditHgt := Size1.cy;
          for i := 0 to NoteNum-1 do
          begin
          SetRect(Rect1,6+Size1.cx,position,AppWidth-9,Position + Size1.cy);
          TextOut(paintDC,6,Rect1.Top,PChar(Int2Str(i+1)+'.'),Length(Int2Str(i+1)+'.'));
          DrawText(paintDC,PChar(NoteList[i]),Length(NoteList[i]),Rect1,DT_LEFT or DT_WORDBREAK or DT_CALCRECT);
          DrawText(paintDC,PChar(NoteList[i]),Length(NoteList[i]),Rect1,DT_LEFT or DT_WORDBREAK);
          Position := Rect1.Bottom + 2;
          end;
          EndPaint(hWnd,PS);
          Result := 0;
          Exit;
          end;
WM_DESTROY: ShutDown;
WM_COMMAND: begin
            if lParam = abs(hCloseBut) then PostMessage(hNoteHandle,WM_CLOSE,0,0)
            else if lParam = abs(hMinBut) then
              begin
              ShowWindow(hNoteHandle,SW_MINIMIZE);
              {ShowWindow(hNoteHandle,SW_HIDE);}
              end
            {menu click messages are lParam = 0
              LOWWORD(wParam) has the number ID}
            else if lParam = 0 then
              case LOWORD(wParam) of
              {the menu ID number is in the LOWORD position of wParam}
              101: ShowAddDelete;
              102: begin
                   ShowWindow(hNoteHandle,SW_MINIMIZE);
                   {SW_HIDE will take it off the task bar}
                   {ShowWindow(hNoteHandle,SW_HIDE);}
                   end;
              103: DoAbout;
              104: PostMessage(hNoteHandle,WM_CLOSE,0,0);
              105: begin
                   ShowWindow(hNoteHandle,SW_SHOW);
                   ShowWindow(hNoteHandle,SW_RESTORE);
                   end;
              end;
            end;
WM_SIZE: if wParam = SIZE_MINIMIZED then ShowWindow(hNoteHandle, SW_HIDE);
WM_LBUTTONDOWN: begin
                ReleaseCapture;
                {ReleaseCapture releases the mouse, needed for the SC_DragMove}
                SendMessage(hNoteHandle, WM_SysCommand, SC_DragMove, 0);
                {SC_DragMove message is used by OS to drag the titlebar around}
                end;
WM_CONTEXTMENU: ShowPopMenu(False,LOWORD(lParam),HIWORD(lParam));
{WM_CONTEXTMENU is more or less a right click on your app}
WM_TRAYICONCLICKED: begin
{this is a user message used for a Tray Icon Click}
                    if lParam = WM_LBUTTONUP then
                      begin
                      ShowWindow(hNoteHandle, SW_SHOW);
                      ShowWindow(hNoteHandle, SW_RESTORE);
                      end
                    else if lParam = WM_RBUTTONUP then
                      begin
                      GetCursorPos(MousePos);
                      ShowPopMenu(True,MousePos.x,MousePos.y);
                      end;
                    end;
end; // case
Result:=DefWindowProc(hWnd,Msg,wParam,lParam);
end;

begin  // / / / /  Main Program begin / / / /
Brush1 := CreateSolidBrush($0080FFFF);

{because these windows may not be created I
initialize the variables}
hAdHandle := 0;
hOkBut := 0;
hAddBut := 0;
hAddABut := 0;
hUpdateBut := 0;
hDeleteBut := 0;
hDelSelBut := 0;
hHideBut := 0;
hNoChangeBut := 0;
hLabel1 := 0;
hLabel2 := 0;
hLabel3 := 0;
hEdit1 := 0;
hUpDown1 := 0;
hEdit2 := 0;
hGroup1 := 0;
hListBox1 := 0;

DoChange := False;

j := GetSystemMetrics(SM_CXSCREEN);
{because of the differences in Screen res
resizeing is needed, and font size to match}
if j < 700 then
  begin
  appWidth := 250;
  FontSize := 13;
  end else
  if j < 900 then
  begin
  appWidth := 275;
  FontSize := 14;
  end else
  if j < 1200 then
  begin
  appWidth := 300;
  FontSize := 15;
  end else
  if j < 1500 then
  begin
  appWidth := 325;
  FontSize := 16;
  end else
  begin
  appWidth := 350;
  FontSize := 18;
  end;

{remember to DeleteObject for all fonts you create}
Font1:=CreateFont(
    -FontSize,                           // Height
    0,                             // Width
    0,                             // Angle of Rotation
    0,                             // Orientation
    FW_NORMAL,                       // Weight
    0,                             // Italic
    0,                             // Underline
    0,                             // Strike Out
    ANSI_CHARSET,               // Char Set
    OUT_TT_PRECIS,            // Precision
    CLIP_DEFAULT_PRECIS,           // Clipping
    ANTIALIASED_QUALITY,               // Render Quality
    VARIABLE_PITCH or FF_ROMAN,  // Pitch & Family
    'Times New Roman'{'MS Sans Serif'});              // Font Name

Font2 := CreateFont(-(FontSize+4),0,0,0,FW_BOLD,0,0,0,ANSI_CHARSET,OUT_TT_PRECIS,
   CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,VARIABLE_PITCH or FF_SWISS,'Arial');

Font3 := CreateFont(-FontSize,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
   CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,VARIABLE_PITCH or FF_SWISS,'MS Sans Serif');

Font4 := CreateFont(-12,0,0,0,0,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
   CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH or FF_DONTCARE,'Marlett');
   {the Marlett font has some of the System Symbols in it like the line for
   minimize and the box for Maximize}

if GetObject(Font4,SizeOf(LogFont1),@LogFont1) <> 0 then
if LogFont1.lfFaceName = 'Marlett' then IsMar := True;

wClass.hInstance := hInstance;
  with wClass do
  begin
    Style:=         CS_PARENTDC  or CS_BYTEALIGNCLIENT;
    hIcon:=         LoadIcon(hInstance,'MAINICON');
    lpfnWndProc:=   @MessageProc;
    {I like MessageProc more than WndProc}
    hbrBackground := Brush1;
    {this yellow Brush1 is used to paint this Class's background}
    lpszClassName:= 'Post Note';
    hCursor:=       LoadCursor(0,IDC_ARROW);
  end;

RegisterClass(wClass);
{you may want to use RegisterClassEx if you want to set a
small Icon other than the small Icon in MainIcon}

IniName := PChar(GetFilePath(ParamStr(0))+'\note.ini');
{this sets the path and name for the  INI  file}

  GetPrivateProfileString(FormPos,'Top','70', CharBuffer, SizeOf(CharBuffer), IniName);
  {GetPrivateProfileString reads the specified String or gets the lpDefault
  if there is no string to read}
  Top := Str2Int(String(CharBuffer));
  {get the Top and Left for the window position}
  GetPrivateProfileString(FormPos,'Left','70', CharBuffer, SizeOf(CharBuffer), IniName);
  Left := Str2Int(String(CharBuffer));
NoteNum := 0;
  for j := 1 to 24 do
  begin
  GetPrivateProfileString(Notes, PChar(Int2Str(j)), 'z#~kr&n(+l', CharBuffer, SizeOf(CharBuffer), IniName);
  if CharBuffer = 'z#~kr&n(+l' then Break;
  {if the unusual default string  'z#~kr&n(+l' is returned then there are no more string so Break}
  NoteList[j-1] := String(CharBuffer);
  Inc(NoteNum);
  {NoteNum is the Number of Notes}
  end;
if NoteNum = 0 then
  begin
  NoteList[0] := Welcome;
  {if this is the first run or there are no Notes then display something besides a blank}
  NoteNum := 1;
  end;

hNoteHandle := CreateWindowEx(WS_EX_APPWINDOW, wClass.lpszClassName, 'Post Note', WS_POPUP or WS_SYSMENU or WS_MINIMIZEBOX or WS_VISIBLE,
    Left, Top, appWidth, 40, 0, 0, hInstance, nil);
{this is our MainWindow it has the WS_POPUP style to keep it from having a Titlebar,
I don't use CreateWindowEX unless I need an EX style}

SizeNote;
{SizeNote detrmines and sets the Length of the Post Note Window}
if IsMar then ButtonChar := 'r'
else ButtonChar := 'X';
hCloseBut := CreateWindow('Button',ButtonChar,WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    appWidth-17,3,14,13,hNoteHandle,0,hInstance,nil);
if IsMar then SendMessage(hCloseBut,WM_SETFONT,Font4,0);

if IsMar then ButtonChar := '0'
else ButtonChar := '-';
hMinBut := CreateWindow('Button',ButtonChar,WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    appWidth-35,3,14,13,hNoteHandle,0,hInstance,nil);
if IsMar then SendMessage(hMinBut,WM_SETFONT,Font4,0);

UpdateWindow(hNoteHandle);

{this IconData is used by the Shell_NotifyIcon to get that
Tray Icon}
with IconData do begin
   cbSize := SizeOf(TNotifyIconData);
   Wnd := hNoteHandle;
   uID := 77;
   uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
   uCallbackMessage := WM_TRAYICONCLICKED;
   {set the callback message here, to be used in the MessageProc}
   hIcon := wClass.hIcon;
   {dont forget that you can use the properties of wClass
    to set variables, like wClass.hIcon here
    others you could use are
    wClass.lpszClassName for the Class Name in createWindow,
    wClass.hbrBackground for the brush}
   szTip := 'Click to Show Post Notes';
 end;

 Shell_NotifyIcon(NIM_ADD, @IconData);
 {adds the Tray Icon}

while GetMessage(Msg,0,0,0) do
  begin
    {since the INI  files will Not accept new line charaters I need to Block
      Edit1  VK_RETURN from being Translated, I could have SubClassed Edit1 to
      do this, but there was only ONE message so I did it here, try the Enter key in Edit1}
    if not ((Msg.hwnd = hEdit1) and (Msg.message = WM_KEYDOWN) and (Msg.wParam = VK_RETURN)) then
    TranslateMessage(Msg);  // Translate any keyboard Msg's
    DispatchMessage(Msg);   // Send it to our WindowProc
    
{please NOTICE that ALL of the Windows OS messages for this Application
come through GetMessage,
even if they are in a New Class window (like Edit1) or are SubClassed}
  end;

for j := 0 to 25 do
  NoteList[j] := '';
{crear all strings to release memory}

end.


Next
In the next lesson more advanced methods are used to get timers that have excelent accuracy. We will create a Mutimedia timer and a Thread timer to do image animations.
  Lesson 11, More Timers


       






H O M E