1. Home
  2. Computing & Technology
  3. Delphi Programming
Creating an API GUI Windows program with message loop
Page 6: Your first Windows API GUI Delphi application
 Win prizes by sharing code!
Do you have some Delphi code you want to share? Are you interested in winning a prize for your work?
Delphi Programming Quickies Contest
 More of this Feature
• Page 1: Preparing to create your first Windows API GUI program
• Page 2: What makes an API application: the window Class
• Page 3: Intro to Windows Messaging
• Page 4: On message loops and WndMessageProc function
• Page 5: Windows handles and the CreateWindow function
 Join the Discussion
Post your views and comments to this chapter of the free "raw API programming" Delphi Course
Discuss!
 Related Resources
• A Guide to raw API programming.TOC

• Delphi form creation tips

Finally, we are ready for some "real" code...

An example
This program will show how to create a GUI program and try to show some of the ways messages are used in its "Window Proc". Since Messages Boxes were used in the non-GUI apps before (previous chapter), I will use them here to show when the message gets to the Window Proc and to change what happens by clicking the messageBox "Yes" or "No" buttons. Communication with the OS is accomplished by changing the WndMessageProc function Result (see WM_CREATE). You can also change behavior of a window by just not calling the DefWindowProc (see WM_CLOSE). It is also possible to change what happens by changing the wParam and lParam before sending them to the DefWindowProc( ), and that will be covered in the next program "More Messages".

Here we will add the Messages unit to our uses so we can use the windows message contants like WM_DESTROY. First we need to put a "Window Proc" function in our code to recieve and handle the messages. Here I use the "WndMessageProc" function to show MessageBoxes for each of the 5 messages it responds to. It is important to have Result := DefWindowProc in this Window Proc function in order to get this window to do all the standard window stuff.
When this window gets the WM_DESTROY message, we need to end our Program by dropping out of the GetMessage( ) loop. So we call PostQuitMessage(0) to send a WM_QUIT message to the GetMessage function which will cause it to returm False and end the loop, allowing the program to terminate. The WM_COMMAND message code shows how to get button click messages with the lParam as the button handle that was clicked. Look at the comments in WndMessageProc for more Info about what the individual messages are for.

Let's look at the first thing after the main program BEGIN, we will set up our Windows Class. The hInstance of the Class is set to our programs hInstance so the class will be UnRegistered when this instance ends. The Class style is set to 0 to keep things simple. The lpfnWndProc is set to @WndMessageProc, which will be this programs "Window Proc" and the class is then registered. Now we will create the programs main window (Form in Delphi terms), using the window handle variable name hAppHandle. To keep this simple I use only the window's style of WS_OVERLAPPEDWINDOW and X, Y, positions of CW_USEDEFAULT ... WS_OVERLAPPEDWINDOW makes a window with the standard sizable borders, caption bar and system menu. Right below the first CreateWindow the is another CreateWindow that is commented out. It creates an identical window to the first, but all the Style bits are listed individually instead of using WS_OVERLAPPEDWINDOW.

The next CreateWindow is for a button, and creates this standard "Control". The "Class" is set to 'Button' and the "or WS_CHILD or BS_PUSHBUTTON or BS_TEXT" is added to the Style. Since the WS_CHILD is added to style, then we need to put hAppHandle in the hWndParent parameter.

Note: you can download the entire code here.

program ADP_111503;
{a very basic GUI window creation application 
that will process several messages in the 
WndMessageProc fuction}

uses
  Windows, Messages;

{$R *.RES}

var
 wClass: TWndClass;
 hAppHandle, hMessBut, hExitBut: HWND;
 Msg: TMSG;

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 &t; 0 do
   begin
      Result := Char((Number mod 10) + Integer('0')) + Result;
      Number := Number div 10;
   end;
   if Minus then
      Result := '-' + Result;
end;

procedure DoMessage(Param: Integer);
var
 Str1: String;
begin
 {the LParam of the WndMessageProc is sent here as Param, 
 this number is shown in the MessageBox along with the 
 WM_COMMAND number and the number for hMessBut}
 
 Str1 := 'A short Message for WM_COMMAND as '+Int2Str(WM_COMMAND)+ 
         ' with LParam as '+ Int2Str(Param)+' and hMessBut as '+Int2Str(hMessBut);
         
 MessageBox(hAppHandle,PChar(Str1),
           'Button Click', 
           MB_OK or MB_ICONQUESTION);
end;

function WndMessageProc(hWnd: HWND; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UINT; stdcall;
begin
 {This is the "Window Proc" used to communicate with the OS, 
  these messages are how the OS tells this program what has 
  happened, look up the 5 WM_ messages in the Win32 API Help.
  This function must send a Result back to Windows for its 
  "message" back to the OS, which may change what the OS does 
  for that Msg. Each of these messages will produce a MessageBox 
  so you can see when the message gets to this function}

 Result := 0;
 case Msg of
    WM_CREATE: 
    begin
     {the CreateWindow fuction sends this WM_CREATE to it's Class's 
      lpfnWndProc, after the window is created, but Before the 
      CreateWindow function returns}
      if MessageBox(hAppHandle,'A Message for WM_CREATE'#10'Do You want this Program to Open?',
                   'Will OPEN ? ?', MB_YESNO or MB_ICONQUESTION) = IDNO then
      begin
        {if the result is -1 then this new window will be Destroyed before it is
        shown, since this is the MAIN window of this Program a Result of -1 will
        tell the OS to call DestroyWindow(hAppHandle); and end this Program}
        Result := $FFFFFFFF;
        Exit;
        {if you want the Result to be -1 then you MUST Exit here so
         DefWindowProc( ) will NOT be called and change the Result to a 0}
      end;
    end;

    WM_DESTROY: 
    begin
      {WM_DESTROY is sent After the window is hidden but 
      before the window is destroyed}
      MessageBox(0,'A Message for WM_DESTROY'#10'the Window has not been Destroyed yet',
                 'Window is Hidden', MB_OK or MB_ICONQUESTION);
      PostQuitMessage(0);
      {IMPORTANT: to end this Program you need to end your 
       GetMessage Loop. To do this you will have to send the 
       WM_QUIT message with PostQuitMessage(0)}
    end;

    WM_CLOSE: 
    if MessageBox(hAppHandle,'A Message for WM_CLOSE'#10'Do You want 
                         this Program to Close?', 'Will Close ? ?', 
                         MB_YESNO or MB_ICONQUESTION) = IDNO then
     {the WM_CLOSE message is sent to tell your window to CLOSE
      and since this is the Main window, to Exit this processes}
    begin
       {Result := 0;
        Unlike WM_CREATE, the Result of the WM_CLOSE 
        message DOES NOT change what the OS does.
        You must Exit in order to keep DefWindowProc
        from calling DestroyWindow(hAppHandle);}
       Exit;
    end;

    WM_CHAR: 
    MessageBox(hAppHandle, PChar('A Message for WM_CHAR as '+Int2Str(WM_CHAR)
               +#10'you typed a   '+Chr(wParam)),
               'Keyboard Char', MB_OK or MB_ICONQUESTION);
    {the WM_CHAR message is sent for keyboard charater input with the
    charater Ord value in the wParam}

    WM_COMMAND: 
    if lParam = Integer(hMessBut) then DoMessage(lParam)
    else if lParam = Integer(hExitBut) then
      PostMessage(hAppHandle, WM_CLOSE, 0, 0);
     {when a button is clicked a WM_COMMAND message is sent 
     to it's parent's Message Proc with the lParam set to 
     the button's Handle}
    end;
    
 Result := DefWindowProc(hWnd,Msg,wParam,lParam);

 {VERY VERY IMPORTANT - to get normal windows default 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   // then MAIN Program Begin
 wClass.hInstance := hInstance;
 with wClass do
 begin
  {there are more wClass parameters which are 
  not used here to  keep this simple}
    style :=        0;
    hIcon :=        LoadIcon(hInstance,'MAINICON');
    lpfnWndProc :=  @WndMessageProc;
    hbrBackground:= COLOR_BTNFACE+1;
    {COLOR_BTNFACE is not a brush, but sets it 
    to a system brush of that Color}
    lpszClassName:= 'First 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_ARROW);

    cbClsExtra := 0;
    cbWndExtra := 0;
    lpszMenuName := '';
  end;

  RegisterClass(wClass);

 {the First Window created in a Program for a Registered 
  Class will be the apps Main Window or "Form" in Delphi 
  terminology, and will be the Main Form for this App.}

  hAppHandle := CreateWindow(
    wClass.lpszClassName,	
    'first Window app',	
    WS_OVERLAPPEDWINDOW,	
    Integer(CW_USEDEFAULT),	
    Integer(CW_USEDEFAULT),	
    386,	
    250,	
    0,	
    0,	
    hInstance,	
    nil 
   );

 if hAppHandle = 0 then
 begin
  {I do not usually include a "if hAppHandle = 0 then"
   I put it here to show how to test for success or failure
   of the CreateWindow}
   UnRegisterClass(wClass.lpszClassName, hInstance);
   Exit;
 end;

  {all of the next CreateWindow will have hAppHandle 
  as the Parent window}

  hMessBut := CreateWindow('Button','Show Message',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    123,128,112,28,hAppHandle,0,hInstance,nil);

  {notice that the hApphandle is given as the Parent of this button,
   this tells the OS to place it on the Client area of the parent,
   try it with 0 (no Parent) instead of hAppHandle}

  hExitBut := CreateWindow('Button','Exit',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    12,42,60,26,hAppHandle,0,hInstance,nil);

  {add another button Control here and put a statement in 
   the WM_COMMAND of the function WndMessageProc, 
   to get it's click event}

  CreateWindow('Static', 'Use Keyboard to Type a charater',
         WS_VISIBLE or WS_CHILD, 10, 12, 300, 20, hAppHandle, 0, hInstance, nil);

  {this window is a Static control, static means that it DOES NOT 
   get keyboard or mouse input, this is used like a TLabel in the VCL, 
   for text display. NOTICE that the returned Handle for this control is 
   NOT stored in a variable like hMessBut was, I do not need this 
   Control Handle because I do not change this control in this program, If 
   I need to move or change the text of this control, I would get
   the Handle in a variable like hLabel1 }

   {add another Static Control here as a Label for practice}

  ShowWindow(hAppHandle, SW_SHOWNORMAL);

  {the WS_VISIBLE style was NOT set in the Main window creation
   so you need to call ShowWindow( ) to make it visible.
   This "ShowWindow" with SW_SHOWNORMAL is a Standard way to make your 
   program visible, if you use this then your progrm can be started 
   by another program as Maximized or Minimized, otherwize those 
   options will be ignored}

  UpdateWindow(hAppHandle);

  {the update line above is not needed here because the message loop
   has not started yet, but I have added it to show that you need to
   update to get changes to a window to be visible after the message loop starts}

  while GetMessage(Msg,0,0,0) do
  begin
    {GetMessage will return True until it gets a WM_OUIT message. 
     So this program will keep running untill you Post a Quit Message}
     
    {Translate any WM_KEYDOWN keyboard Msg to a WM_CHAR message} 
    TranslateMessage(Msg);  
                             
    {this Sends Msg to the address of the 
     "Window Procedure" set in the Resistered
     Window's Class for that window}
    DispatchMessage(Msg);
  end;

  {There are 3 calls for CreateWindow for a child window, but
   we do not have to use DestroyWindow, when the
   DefWindowProc(hWnd,Msg,wParam,lParam);
   gets the WM_CLOSE message it will call DestroyWindow( ) for that 
   window (hWnd). The system will also destroy all child windows when
   it destroys the parent. There is a complex structure (Record) setup
   in the OS for each window, which records it's height and width, and
   it's Parent and Children, and MANY other things.}
end.

Compile and run, and "play"...

Your first GUI API program starting

Your first GUI API program running

After you compile and run this program, try to add a third button and use the WM_COMMAND message to show a messageBox when it is clicked. Now add a second static control as another Label.

Let's find out more about the Style bits in CreateWindow for hAppHandle. Comment out the first hAppHandle := CreateWindow( ) for the main Form, and use the second CreateWindow for the Form that was commented out. Compile and run it, is the form the same? Take out the WS_MINIMIZEBOX style and see if it changes the Form. Put the WS_MINIMIZEBOX back in and take out the WS_MAXIMIZEBOX style bit, to see the difference. Do this with all the style bits to see how they affect the Form.

Let's experiment some more - in the hMessBut := CreateWindow( ) add the WS_DISABLED style bit and run the program, what happened? Remove the WM_DISABLED and change the BS_PUSHBUTTON style bit to BS_AUTOCHECKBOX and run the program. Did it change hMessBut? Change it back to BS_PUSHBUTTON. Now add the WS_CAPTION to hMessBut and increase it's height to 50, so you have "WS_CAPTION or WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT" - thats RIGHT, a Caption on a button. Now run the program. You get a Button with a Caption that you can drag around by the caption just like a form. Strange but true. And that's all folks for today ;)

To the next chapter: A Guide to raw API programming
We have made a GUI program that creates windows, and has a message loop to interact with Windows. We only used a few of the basic windows messages in this program and there are many more things about the use of messages that you should know. So the next chapters will show some more ways to use messages, not only receiving them in a Window Proc, but also sending them to change a windows properties ... stay tuned.

If you need any kind of help at this point, please post to the Delphi Programming Forum where all the questions are answered and beginners are treated as experts.

First page > Preparing to create your first Windows API GUI program > Page 1, 2, 3, 4, 5, 6

A guide to developing Delphi programs in raw Windows API: Next Chapter >>
>> The TOC

Explore Delphi Programming
About.com Special Features

Holiday Central

What to eat, where to go, fun things to do and how to save money on the perfect gifts. More >

Family Tech Center

Stay connected and entertained with reviews on tips on the latest HDTVs, cellphones and more. More >

  1. Home
  2. Computing & Technology
  3. Delphi Programming

©2009 About.com, a part of The New York Times Company.

All rights reserved.