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

Home
DelphiZeus
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;
    MSDN-ShowWindow

function MoveWindow(hWnd: THandle; X, Y, nWidth, nHeight: Integer; bRepaint: Boolean): Boolean;
    MSDN-MoveWindow

function EnableWindow(hWnd: THandle; bEnable: Boolean): Boolean;
    MSDN-EnableWindow

function GetWindowText(hWnd: THandle; lpString: PChar; nMaxCount: Integer): Integer;
    MSDN-GetWindowText

function SetWindowText(hWnd: THandle; lpString: PChar): Boolean;
   MSDN-SetWindowText

function SetFocus(hWnd: THandle): THandle;
    MSDN-SetFocus

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('?'))) then
to 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


       

Lesson -     One  Two  Three  Four  Five  Six  Seven  Eight  Nine  Ten  Eleven  Twelve  Thirteen  Fourteen




H O M E