Home |
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 |