Home |
3. More Messages |
Home |
In this More Messages Program, we will explore other ways to use windows messages. In the "First Window App" program we saw how messages could be received in the WndMessageProc( ) function and tested for the values in the parameters to cause different code to be executed. In More Messages we will change the lParam before we send it to the DefWindowProc, which will change what happens. We created windows but did not try to change any of the window's properties after they were created. It is important to know how to change a window at run time, and since API does not give us the Object Oriented Property access we are used to in Delphi VCL, we will have to know the functions or messages used to change a window at run-time. |
Changing the Parameters before sending them to DefWindowProc This More Messages Program creates a window (Main Form) that you can resize by draging the borders, we will change the information in the LParam of the WndMessageProc( ) ("Window Proc") message function and keep the Form from being resized pass a certain limit. The WM_WINDOWPOSCHANGING message is sent to a window when it has instructions to change it's position or size, but this is sent Before any action is taken. You can look in your local Win32 API Help for WM_WINDOWPOSCHANGING or the web page MSDN-WM_WINDOWPOSCHANGING. If you look at the WM_WINDOWPOSCHANGING message in the WndMessageProc( ) function of the program code below, you will see that the PWindowPos(lParam).cx value is tested and then set if it is above 450. The lParam of this message is a memory address value (Pointer) to a TWindowPos Record, this record type is used to transferr information about position and size for windows. It is defined in windows.pas as - PWindowPos = ^TWindowPos; tagWINDOWPOS = packed record hwnd: HWND; // Identifies the window moved hwndInsertAfter: HWND; // the Z-Order position window x: Integer; // Left position of window y: Integer; // Top position of window cx: Integer; // window Width cy: Integer; // window Height flags: UINT; // flags to set the window position end; TWindowPos = tagWINDOWPOS;We will not need to use most of the values in this record. The two values we need to monitor are - cx - Window width, in pixels. cy - Window height, in pixels In the TWindowPos record definition you should notice that a PWindowPos type has been set as a Pointer to a TWindowPos Record. The wParam and lParam are Integer values, not Pointer types. So we will need to TypeCast the lParam as a PWindowPos type - PWindowPos(lParam) and access the width (cx) and height (cy). Most of the windows structures (Records) have been assigned a Pointer type like PWindowPos, so we can also TypeCast the lParam or wParam to access the members of that Record used with whatever message we need to evaluate. For most messages the wParam and lParam are sent to the DefWindowProc( ) unchanged, to be processed by the system. But here the TWindowPos.cx is tested and changed Before it is sent to the DefWindowProc, whatever New values are placed in the cx will be used by the system for the width of the window. Look at the WM_GETMINMAXINFO message which uses a PMinMaxInfo Pointer type, and the lParam is Typecast to a PMinMaxInfo. Both the WM_WINDOWPOSCHANGING and the WM_GETMINMAXINFO can be used to limit the size of the main form. Here I use WM_WINDOWPOSCHANGING to limit the Max size and WM_GETMINMAXINFO to limit the Minimum size. Also the WM_MOVING and WM_SIZING can limit the size of a window. Communicating with Windows, Forms and Controls Using SendMessage and PostMessage There are several ways to get messages to windows, the 2 functions "PostMessage" and "SendMessage" are used to send messages to a window. In the "First Window App" program I used a "PostMessage(hAppHandle, WM_CLOSE, 0, 0);" to get the WM_CLOSE message posted to the message Proc. PostMessage returns without waiting for the window to process the message and give it a Result (Integer) value, it just has a Boolean Result. SendMessage waits for the window to process the message and return a Result (Integer). You can look at your local API Help for SendMessage or the web page MSDN-SendMessage and API Help for PostMessage or web page MSDN-PostMessage One way to change a button's properties after it has been created is to send it a message to tell it what to change and how to change it. The function SendMessage( ) was made for this. SendMessage(hWnd: THandle; Msg: Cardinal; wParam, lParam: Integer): Integer;This is a very important and useful function, you can get info or modify many windows and controls by sending a message and using the LParam and WParam for sending or receiving information. If you want to change the display font of a control you would send it the WM_SETFONT message. Let's look at the syntax for this SendMessage(hControlHandle, WM_SETFONT, hFontHandle, 1);The first parameter is always the Handle of the window the message is to be sent to, the next parameter is the message to be sent, in this case WM_SETFONT. The next 2 paramters are for the "Information", in this case the WParam is the handle of the Font you want to be drawn in the control, and the LParam is a Redraw flag, set to 1 here to tell the control to refresh itself. All of the Parameters in SendMessage( ) are numeric, LongWord or Integer type. To find out the handle of the font used in a control you would use the WM_GETFONT message. hFontHandle := SendMessage(hControlHandle, WM_GETFONT, 0, 0);Notice that the LParam and WParam are not used like the WM_SETFONT message, instead the font Handle is the Result of the function call. Each different message has different uses in SendMessage( ) for the Result, LParam and WParam, so you will have to look at the Win32 API help to see what these paramertes do for each message. This can be very confusing at first, if you are used to Delphi functions which have very little variation for paramteters. Just remember that there are hundreds of different messages used for totally different operations, so the 3 paramters need to be very flexible. If the LParam and WParam are both numeric (Integer) then how would you get a text string or a TRect? Lets look at the WM_SETTEXT message var PText: PChar; PText := 'Hello to you'; SendMessage(hExitBut, WM_SETTEXT, 0, Integer(PText));You type cast the PChar PText as an Integer. This sends the memory address of the PChar variable to the windows OS as an Integer. If you are not familar with pointers and memory addresses, you may think that since an Integer is 4 bytes, this will only get the first 4 charaters (one byte per charater). This is not correct, the memory address (Pointer value) has no correspondence to the variable type or the variable's contents (a pointer can also "point" to a function's memory address). So when the OS reads this memory address as a pointer to a null-terminated array of charaters (PChar type), it will get all the charaters of the string, not just the first 4. If the variable is not a Pointer type, then you will have to type cast the Address (@) of the variable. A TPoint variable for the EM_POSFROMCHAR message, Like this var PosPoint: TPoint; SendMessage(hEdit1, EM_POSFROMCHAR, Integer(@PosPoint),10);The PosPoint variable address is sent as an Integer and will receive the pixel position of the tenth charater in hEdit1. You can also use the Pointer types for variables, instead of TPoint you could use PPoint and you would then use, var PPosPoint: PPoint; New(PPosPoint); // allocate memory for the pointer SendMessage(hEdit1, EM_POSFROMCHAR, Integer(PPosPoint),10); FreeMem(PPosPoint);There may not be any advantage to using a Pointer type like PPoint. If you use API functions, you will need to get to know some of the typecasting methods needed to use the Delphi variables as parameters in the API functions. Changing Windows with functions There are many functions that will change a specific property of a window. The ones in this program are - function ShowWindow(hWnd: THandle; nCmdShow: Integer): Boolean; function MoveWindow(hWnd: THandle; X, Y, nWidth, nHeight: Integer; bRepaint: Boolean): Boolean; function EnableWindow(hWnd: THandle; bEnable: Boolean): Boolean; function GetWindowText(hWnd: THandle; lpString: PChar; nMaxCount: Integer): Integer; function SetWindowText(hWnd: THandle; lpString: PChar): Boolean; function SetFocus(hWnd: THandle): THandle; You should look these up in your local Win32 API Help and then find them in the program below (procedure DoChange;) to see how they are used. There are many other functions to change the properties of windows, but these are the basic ones that will give you the enough tools to do many programs. |
Program More Messages | |
Here we will look at some of the ways messages are used to get things done, limit the size of the main form, change the title bar text, and change the font used in controls. To try and show the number of messages that pass through the "Window Proc", the variable, NumMessages, is increased each time WndMessageProc( ) is called and GetMess is increased each time GetMessage is called. A static Label, hLabel2, is changed to show these numbers. You will be able to move, resize and click this form, and see the number of messages used for each operation.
The wClass style is set to CS_DBLCLKS this time, so a WM_LBUTTONDBLCLK message is sent to the main form window for double clicks. The lpfnWndProc is set to @WndMessageProc, which will be this programs "Window Proc". The lpszMenuName, cbClsExtra, and cbWndExtra parameters are also included and set to 0. Now we will create the programs main window, using the window handle variable name hAppHandle. To keep this simple I again use the window's sytle of WS_OVERLAPPEDWINDOW and X, Y, positions are set to 100 and 50 . . . WS_OVERLAPPEDWINDOW makes a window with the standard sizable borders, caption bar and system menu. The next 10 windows created are window's controls, (buttons, static, and edit) and set the parent window as hAppHandle. Look at the comments in the code for more Info. Let's look at the function WndMessageProc( ), there are 7 messages used here, WM_COMMAND, WM_DESTROY, WM_RBUTTONUP, WM_SYSCOMMAND, WM_WINDOWPOSCHANGING, WM_GETMINMAXINFO, and WM_LBUTTONDBLCLK. You should see the your local Win32 API Help for information about these messages. Look at the program code for WM_LBUTTONDBLCLK message, this is sent for left button doubleckicks, there is a MoveWindow( ) function to move the hIcon1 control to the cursor position. With mouse click messages the cursor postion is sent in the lParam parameter. | Since the lParam has 4 bytes it is divided into two - 2 byte segments which are accessed with LOWORD( ) and HIWORD( ) functions. LOWORD has the X position and HIWORD has the Y. Like the "First Window App", the WM_COMMAND message code get's button click messages with the lParam as the button handle that was clicked. There is also info in the wParam, I use (HIWORD(wParam) = BN_CLICKED) just to show how to get this info.
Notice that ALL messages are sent to DefWindowProc(hWnd,Msg,wParam,lParam);, and the Result is always the DefWindowProc return. There are thousands of messages, and we only need to deal with the ones that will give us info that we want to get. Like the "System Info Program", there is a MakeTextFile procedure here, but now it gets the text from an edit box. Now look at the DoChange procedure, here we change the display properties of hMakeBut. We do NOT have object oriented access to the propeties of windows. In VCL Delphi you can change the Caption and Width of a button with Button1.Caption := 'New Caption'; Button1.Width := 156;With the API you have to call functions with the window handle as a parameter to get or set windows properties. The SendMessage( ) function is used to set hMakeBut's font. SetWindowText( ) is used to set it's Caption, and MoveWindow( ) is used to set it's position, width and height. Let's look at the MakeTextFile procedure, here GetWindowText( ) is used to get the text in hEdit. An Array of Char variable is used to receive the text from this function. You must use a Variable that has enough memory space allocated to it to get all of the charaters. If you know that the number of charaters will not exceed a certain amount, then an array of charater is good to use. |
Sometimes you can not know the length of the charater string so you will have to find out it's length, look at this procedure -
procedure GetText; var Length: Integer; Text: PChar; Str1: String; begin Length := GetWindowTextLength(hEdit)+1; GetMem(Text,Length); GetWindowText(hEdit,Text,Length); Str1 := String(Text); FreeMem(Text); end;Now let's look at the while GetMessage(Msg,0,0,0) do near the end of the code. I have put in if not ((Msg.hWnd = hEdit) and (Msg.message = WM_CHAR) and (Msg.wParam = Ord('?'))) thento show you that you can "Filter" messages in the GetMessage( ) loop. This will prevent the WM_CHAR message to the hEdit window with the wParam set to the number for "?" from being dispatched to that hEdit window. Complile and run Program1. Now try to type a ? into the edit box. What happens? Do other charaters type into the edit box? Look at the "procedure ManyMessage;" here you are shown several ways to get the message, WM_SETTEXT, to the Main Form window. You can use SetWindowText( ) or SendMessage( ), the recomended ways to do it. Or you can call the Message function - WndMessageProc( ) - directly, Or you can bypass the message function and call the DefWndProc( ) directly. See the comments in the code below for more info. |
program Project1; {this Program will create windows, a Main Form, 5 buttons and an Edit and Static controls. It will demonstrate how Windows messages are used to signal events (mouse Clicks and program termination) and use SendMessage to get and change window properties} uses Windows, Messages; {$R *.RES} var wClass: TWndClass; hAppHandle, hEdit, hLabel1, hLabel2, hIcon1, hKillBut, hMakeBut, hChangeBut, hExitBut, hManyBut: THandle; MainMsg: TMSG; DirPath: Array[0..MAX_PATH] of Char; NumMessages, GetMess: Cardinal; dMess: Byte; function Int2Str(Number : Int64) : String; var Minus : Boolean; begin {SysUtils is not in the Uses Clause so I can not use IntToStr( ) and have to define an Int2Str( ) function here} Result := ''; if Number = 0 then Result := '0'; Minus := Number < 0; if Minus then Number := -Number; while Number > 0 do begin Result := Char((Number mod 10) + Integer('0')) + Result; Number := Number div 10; end; if Minus then Result := '-' + Result; end; procedure MakeTextFile; var File1: TextFile; begin GetWindowText(hEdit,DirPath,MAX_PATH); {Notice that DirPath can be used as a PChar here in GetWindowText and as a String in MessageBox below} if CreateDirectory(DirPath,nil) then begin if MessageBox(hAppHandle, Pchar('the folder "'+ DirPath + '" has been created.'#13'Do you want to create the File "text note.txt" ? ?'), 'Folder It', MB_YESNO or MB_ICONQUESTION) = ID_YES then begin AssignFile(File1, DirPath+'test note.txt'); {$I-} Rewrite(File1); {$I+} if IOResult = 0 then Write(File1,'wow ParamStr 0 is '+ParamStr(0)); {$I-} CloseFile(File1); {$I+} end; end else MessageBox(hAppHandle,'The Folder was NOT Created, it may already exist', 'ERROR on CreateDirectory', MB_OK or MB_ICONERROR); end; function WndMessageProc(hWnd: HWND; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UNIT; stdcall; forward; {this is a Forward declaration for the WndMessageProc function called in the next ManyMessage procedure. Without this, the compiler does not know about the WndMessageProc yet because it's defined after ManyMessages and will not allow it's call without this Forward declaration} procedure ManyMessage; var NewTitle: PChar; begin {here 5 different methods are used to do the same thing, change the Title Barr Caption of this Form. This is to help you see some of the connections in system methods. The windows message system allows muti-Tasking by placing a message for a window into a message queue, so that program can get that message when it is finished other tasks and has processor time for that message} NewTitle := PChar('New Title '+Int2Str(dMess)); {dMess is used to change the method used} case dMess of 0: SetWindowText(hAppHandle, NewTitle); {The SetWindowText function causes a WM_SETTEXT message to be sent to the window, you use a PChar variable for the text} 1: SendMessage(hAppHandle,WM_SETTEXT,0,Integer(NewTitle)); {An application sends a WM_SETTEXT message to set the text of a window. This places the WM_SETTEXT in the message queue and goes through the GetMessage( ) loop and is dispatched to hAppHandle's WndMessageProc( ). Now WndMessageProc( ) uses DefWindowProc( ) to get the system to change the Caption buffer and redraw the Caption, You use an Integer variable for the text (which is a memory address for the PChar)} 2: WndMessageProc(hAppHandle,WM_SETTEXT,0,Integer(NewTitle)); {Calling WndMessageProc( ) directly, will bypass the message queue Nothing goes through GetMessage with this method, system messages queues are NOT used. This is similar to delphi Form1.Perform(message,wPar,lPar);} 3: DefWindowProc(hAppHandle,WM_SETTEXT,0,Integer(NewTitle)); {since WndMessageProc( ) does NOT do anything to the WM_SETTEXT message, we can just call DefWindowProc( ) and bypass the Message queue AND WndMessageProc.} 4: CallWindowProc(Pointer(GetWindowLong(hAppHandle{hEdit}, GWL_WNDPROC)), hAppHandle{hEdit}, WM_SETTEXT,0, Integer(NewTitle)); {We can also get the system to tell us the address of the system Message function for a window with GetWindowLong( ) having the GWL_WNDPROC parameter and then use CallWindowProc( ) to call this message function. Try this with hEdit instead of hAppHandle to see if it works for the Edit control also.} end; // case if dMess > 3 then dMess := 0 else Inc(DMess); {it is safer to use the SendMessage( ) and the message queue and have the system allocate processor use, but you might want to bypass the message queue for speed} end; procedure ChangeLabel; begin {here we test for hLabel2, AND we cut down on the number of calls to SetWindowText() by dividing the NumMessages by 4, if you don't reduce the number of calls to SetWindowText then there are so many messages that it will overload and crash} if (hLabel2 <> 0) and (NumMessages Mod 4 =0) then SetWindowText(hLabel2, PChar('Number of Messages '+Int2Str(NumMessages)+ #10'GetMess '+Int2Str(GetMess))); {this static Label will show the number of messages that GetMessage and WndMessageProc have recieved. Do things like drag this form, resize this form, type into hEdit or double click the form to see the difference in the number of messages. Many messages go through the message queue and the GetMessage loop, but many do Not. These messages, which are sent directly to a window procedure, are called nonqueued messages} end; procedure DoChange; begin {there are SendMessage or Functions to get and set properties of windows and controls. Here I want to toggle the hMakeBut with the hChangeBut, I send the WM_GETFONT message and see if it is the ANSI_VAR_FONT to determine which state the toggle is in, I could have used a Boolean variable to record it's state} if SendMessage(hMakeBut,WM_GETFONT,0,0) = Integer(GetStockObject(ANSI_VAR_FONT)) then {SendMessage(hMakeBut,WM_GETFONT,0,0) returns the Handle of the font of hMakeBut you will find out more about fonts and font handles in a later lession} begin SendMessage(hMakeBut,WM_SETFONT,GetStockObject(SYSTEM_FONT),0); SetWindowText(hMakeBut, 'Make Text File'); {change the Text of the hMakeBut button} MoveWindow(hMakeBut,23,128,102,28,True); {change the position and size of hMakeBut button} ShowWindow(hKillBut,SW_SHOW); {show the Delete File button} SetFocus(hExitBut); {Changes the focus to hExitBut} end else begin SendMessage(hMakeBut,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0); SetWindowText(hMakeBut, 'Other Text'); MoveWindow(hMakeBut,13,128,84,23,True); ShowWindow(hKillBut,SW_HIDE); {hide the Delete file button} end; end; function BadWndProc(hWnd: HWND; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UNIT; stdcall; begin {this function is here to try and show you what you get and don't get if your Window Proc does NOT handle messages} Result := 5; end; function WndMessageProc(hWnd: HWND; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UNIT; stdcall; {var MinMax1: TMinMaxInfo;} begin {This is the "Window Proc" to get messages, These messages are how the Operating System tells this program about events} Result := 0; if NumMessages < High(NumMessages)-1 then Inc(NumMessages); {this increases NumMessages each time this function is called for a message} case Msg of WM_COMMAND: if lParam = Integer(hExitBut) then PostMessage(hAppHandle,WM_CLOSE,0,0) {when a button is clicked a WM_COMMAND message is sent with the lParam set to the button's Handle} else if (LParam = Integer(hMakeBut)) and (HIWORD(wParam) = BN_CLICKED) then MakeTextFile {you would not normaly need the (HIWORD(wParam) = BN_CLICKED) but it is here to show you that it is also in the WM_COMMAND message} else if LParam = Integer(hKillBut) then DeleteFile(PChar(DirPath+'test note.txt')) else if LParam = Integer(hChangeBut) then DoChange else if LParam = Integer(hManyBut) then ManyMessage; WM_DESTROY: PostQuitMessage(0); WM_LBUTTONDBLCLK: begin {This Double Ckick message was enabled in the wClass by CS_DBLCLKS in style parameter} MoveWindow(hIcon1, LOWORD(lParam), HIWORD(lParam),32,32, True); {use MoveWindow( ) to reposition and change the size of a window the LOWORD(lParam) and HIWORD(lParam) have the X and Y Cursor Position} end; WM_RBUTTONUP: MessageBox(hAppHandle,PChar('X position is '+Int2Str(LOWORD(lParam))+ ' Y position is '+Int2Str(HIWORD(lParam))), 'WM_RBUTTONUP message', MB_OK or MB_ICONQUESTION); {the WM_RBUTTONUP message is for Right mouse button up event, right click the client area to see this messageBox} WM_SYSCOMMAND: begin {the WM_SYSCOMMAND message is sent when a event from the window (system) menu happens this includes Maximize, Minumize, Restore, Move, Size, Close and others} if (wParam and $FFF0) = SC_MAXIMIZE then MessageBox(hAppHandle,PChar('Mouse X position is '+Int2Str(LOWORD(lParam))+ ' Mouse Y position is '+Int2Str(HIWORD(lParam))+ #10'This is the SC_MAXIMIZE wParam'), 'WM_SYSCOMMAND message', MB_OK or MB_ICONQUESTION) else if (wParam and $FFF0) = SC_MINIMIZE then Exit else {if you Exit and do not call DefWindowProc, this window will not be minumized} if (wParam and $FFF0) = SC_RESTORE then MessageBox(hAppHandle,PChar('Mouse X position is '+ Int2Str(LOWORD(lParam))+' Mouse Y position is '+Int2Str(HIWORD(lParam))+ #10'This is the SC_RESTORE wParam'), 'WM_SYSCOMMAND message', MB_OK or MB_ICONQUESTION); end; WM_WINDOWPOSCHANGING: begin {the WM_WINDOWPOSCHANGING message is sent Before a windows position changes} if PWINDOWPOS(lParam).cx > 450 then PWINDOWPOS(lParam).cx := 450; if PWINDOWPOS(lParam).cy > 300 then PWINDOWPOS(lParam).cy := 300; {typecast the lParam to a PWindowPos and then change the cx and cy to change the width and height of this Form} end; WM_GETMINMAXINFO: begin {WM_GETMINMAXINFO is sent Before a window changes it's dimentions} //PMinMaxInfo(lParam).ptMaxTrackSize.x := 450; //PMinMaxInfo(lParam).ptMaxTrackSize.y := 300; PMinMaxInfo(lParam).ptMinTrackSize.x := 370; PMinMaxInfo(lParam).ptMinTrackSize.y := 240; //PMinMaxInfo(lParam).ptMaxSize.x := 640; //PMinMaxInfo(lParam).ptMaxSize.y := 480; end; end; // case ChangeLabel; {the ChangeLabel procedure changes Label2} Result := DefWindowProc(hWnd,Msg,wParam,lParam); {VERY VERY IMPORTANT - to get normal windows behavior you must call DefWindowProc for that message, if you DO NOT want normal windows behavior then DO NOT let DefWindowProc be called. I have put it at the end of this function, so if you don't want DefWindowProc then you just add and "Exit;" in that message response above} end; begin // main program begin / / / / / / / / / / / / / / / / / / / / / / NumMessages := 0; GetMess := 0; hLabel2 := 0; dMess := 0; wClass.hInstance := hInstance; with wClass do begin {all of the wClass parameters are used here and set to 0 if not used} style := CS_DBLCLKS; hIcon := LoadIcon(hInstance,'MAINICON'); lpfnWndProc := @WndMessageProc; //lpfnWndProc := @BadWndProc; {you can use BadWndProc to see what happens if the Proc does nothing} //lpfnWndProc := @DefWindowProc; {you can use DefWindowProc, but the app will not terminate when the main Form is destroyed because PostQuitMessage( ) is not called and the GetMessage loop keeps on going} hbrBackground:= COLOR_BTNFACE+1; {COLOR_BTNFACE is not a brush, but sets it to a system brush of that Color} lpszClassName:= 'Second Class'; {you may use any class name, but you may want to make it descriptive if you register more than one class} hCursor := LoadCursor(0,IDC_UPARROW); {I use a non standard Cursor just to show you what happens to the cursor for this form, notice that the cursor goes back to the default (arrow) over buttons but not over static controls} lpszMenuName := ''; cbClsExtra := 0; cbWndExtra := 0; end; RegisterClass(wClass); {more than one Class can be Registered, the lpfnWndProc address will be used to send all of the messages for windows of this class when they are dispatched in GetMessage} {this is the First Window created here, and will be the Main Form for this App.} hAppHandle := CreateWindow( wClass.lpszClassName, // PChar for registered class name 'second Window app', // PChar for window name (title bar Caption here) WS_OVERLAPPEDWINDOW{ or WS_VISIBLE}, // window style {WS_OVERLAPPEDWINDOW is the default standard main window with a Title bar and system menu and sizing border, there is No WS_CHILD here} 100, // horizontal position of window 50, // vertical position of window 386, // window width 250, // window height 0, // handle to parent or owner window {this is the MAIN window, so it does not have a parent} 0, // handle to menu or child-window identifier {if you install a main Menu, it's handle goes here} hInstance, // handle to application instance nil // pointer to window-creation data ); {all of the next CreateWindow will have hAppHandle as the Parent window} {this Static control is used as a Label, and it's style includes WS_CHILD, so it's position is on the Client Rect of it's Parent, hAppHandle, not Screen corodinates. It's Height is 42, enought for 2 lines of System Font text, the text will be auto wrapped to 2 lines} CreateWindow('Static', 'Enter the path with \ at the end, below for the Folder you want Created', WS_VISIBLE or WS_CHILD or SS_LEFT,40,10,340,42,hAppHandle,0,hInstance,nil); {notice that there is no variable like "hExitBut" to get this window's handle, because it is not not sent messages or changed and no messages are used from it, and it will be destroyed as a child of hAppHandle on the WM_DESTROY message. But there is a Handle reference in the Windows OS outside of the memory space for this App. So if this App exists without a call to Destroy the hAppHandle window, the Handle for this window will remain in the Windows OS, even though this thread has ended and this App's memory is released} hExitBut := CreateWindow('Button','Exit', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 285,186,64,28,hAppHandle,0,hInstance,nil); {This window is a button that will have Text on it, a button event like left or right click will cause the OS to send a WM_COMMAND message to the WndMessageProc( ) function, with the lParam as hExit to identify which button was clicked. In the HIWORD(wParam) will be BN_CLICKED, but this is for versions before Win3.1 and is not used much} hEdit := CreateWindowEx(WS_EX_CLIENTEDGE,'Edit','C:\Folder path\', WS_VISIBLE or WS_CHILD or ES_LEFT or ES_AUTOHSCROLL, 13,54,310,20,hAppHandle,0,hInstance,nil); {an Edit control can have many styles for different uses. A Control window will use the System Font by default, to have it use a different font use SendMessage( ) with WM_SETFONT} SendMessage(hEdit,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0); {to use window OS standard Objects (fonts, brushs, pens) call GetStockObject with the fnObject as the one you want} hLabel1 := CreateWindow('Static', 'Click the "Make Text File" button to create a Folder above and a "test note.txt" file in it', WS_VISIBLE or WS_CHILD or SS_CENTER,6,88,370,32,hAppHandle,0,hInstance,nil); SendMessage(hLabel1,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0); {unlike the first Static Label, we get a Handle, hLabel1. This is needed to change it's Font to Font1 with SendMessage( )} hLabel2 := CreateWindow('Static', 'Number of Messages', WS_VISIBLE or WS_CHILD,270,128,98,46,hAppHandle,0,hInstance,nil); SendMessage(hLabel2,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0); hMakeBut := CreateWindow('Button','Make Text File', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 23,128,102,28,hAppHandle,0,hInstance,nil); hKillBut := CreateWindow('Button','Delete Text File', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 143,128,120,28,hAppHandle,0,hInstance,nil); hChangeBut := CreateWindow('Button','Change MakeBut', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 10,172,126,26,hAppHandle,0,hInstance,nil); SendMessage(hChangeBut,WM_SETFONT,GetStockObject(ANSI_FIXED_FONT),0); hManyBut := CreateWindow('Button','Many Messages', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 152,172,116,26,hAppHandle,0,hInstance,nil); SendMessage(hManyBut,WM_SETFONT,GetStockObject(ANSI_FIXED_FONT),0); hIcon1 := CreateWindow('Static', 'MAINICON', WS_VISIBLE or WS_CHILD or SS_ICON, 1,1,1,1,hAppHandle,0,hInstance,nil); {a Static control can also display an Icon or Bitmap with the SS_ICON or SS_BITMAP style, you do not have to LoadIcon from resource, just put the resource name in the window name parameter. Notice that the width and height are 1, the OS will auto size the Static control to the size of the Icon or Bitmap} ShowWindow(hAppHandle, SW_SHOWDEFAULT); {GetMessage will return True until it gets a WM_OUIT message} while GetMessage(MainMsg,0,0,0) do begin if GetMess < High(GetMess)-1 then Inc(GetMess); {this increases the GetMess Count each time a message goes through here} TranslateMessage(MainMsg); {Translate any WM_KEYDOWN keyboard Msg to a WM_CHAR message} {the "if not" below tests for a hEdit window message of WM_CHAR, and a wParam of ? this does NOT send that message to hEdit, try to type a ? into the edit box} if not ((MainMsg.hWnd = hEdit) and (MainMsg.message = WM_CHAR) and (MainMsg.wParam = Ord('?'))) then DispatchMessage(MainMsg); {this Sends Msg to the address of the "Window Procedure" set in the Resistered Window's Class for that window, hWnd} end; end. |
When you run this program be sure to double click and Right click the main Form. After you have compiled and run this program, change the wClass lpfnWndProc to "lpfnWndProc := @BadWndProc;" which is commented out. This will show you what happens when the Window Proc does nothing. You can also try "lpfnWndProc := @DefWindowProc;", the Form will appear to be OK, but when you close it with the X close caption button the Form is destroyed, but the program keeps on running because the GetMessage loop is still running. Let's experiment, see if you can add the WM_MOVING message to the WndMessageProc and use the lParam to keep the Form from being moved past 230 Left and 180 Top. Make the Form larger and add a mutiline edit control, can you add scroll bars to the edit control? (hint - look in the style types for Edits in the Win32 API help for CreateWindow). |
You may have noticed that to use functions like "UpperCase", "IntToStr", or "ExtractFileName" you will need the SysUtils unit, If you add this unit it will add about 22 Kb to this app. I did not like that 22 Kb added to my app so I created a smallUtils unit where I copied the commonly used functions in SysUtils but not the Exception messages and othere things to add that 22 Kb. It will be used it in all of the next programs so You MUST to download it with this link -
smallUtils.zip OR see it's code on this page - SmallUtils.pas Utility function Unit |
Next We have used the Window Proc to use messages and used SendMessage to comunicate. You will be using more and more API functions to get or set information, so next we'll look at ways of using pointers and PChar in API functions. 4. PChar, it's a Pointer |