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

Home
DelphiZeus
2. Creating a GUI Windows Program

Home



There are alot of things used to create a Windows GUI, , , "window" creation has to have many options to give the various looks and funtions of different types of "windows". Before you start into windows creation, you should look at the MSDN web page for an Introduction to "Windows" at MSDN-Intro to Windows. Click the Link on that page to "About Windows" and read what it says. You might also go to the "Using Windows" and "Window Features" links if you have time.
Or you can use your local "Win32 API help" under the index name of "Windows". The first sentance in this help is
"A window in an application written for the Microsoft® Windows®
operating system is a rectangular area of the screen where the application displays output and receives input from the user." Now click the foward arrows at the top of help ( >_> ) and go to the next page "about windows". Read this and then continue to click to the next pages and keep reading. After several pages you will get to "Window Creation" page, consider what it says. Now the next page is "Window Attributes", read this page twice, for useful information. Continue to the next page "Window Handles". Now the next page "Main Window Creation" does not apply for Delphi, since Pascal uses a diferent way than C code. You can keep on reading the many pages after this for some over views of using Win32. The "Windows Styles" info may be helpful for you.
So Many Things
If you are new to windows messaging and window creation, when you look at this page, it may seem like there is alot to know in order to make a program. Much of this will be unclear until you use it in programs and get a feel for what is going on and what you need to do to get the results you want. So read through this web page and try and get an Overview of window creation and some of the aspects and ways windows uses messages. You may not need to know many of the functional details at this time. You will use the code in First Window App for your delphi program and then see if you can learn the methods and stucture used there.

To many programers the Windows API methods and functioning sometimes seem to be illogical, without consistancy, or more complex than neccessary to do "Simple" things. But please keep in mind that the windows API keeps you from having to program in low level, machine level or assembly language, which would be much, much more difficult and time consuming. Also you might complain if the OS did not offer many options for a great variety of methods and display. With more options and diveristy comes added difficulty, complexity, parameters and functions. So with some time, practice, and trial and error, you can learn how to deal with the API.

Steps for Creating a Windows GUI
Here are the steps for createing a GUI program, but you will have to read the explanations below to find out what this does and options for window creation.
To get a windows GUI program to go you will need to -

1. Put the Windows and Messages units in your uses clause.

2. Place a WndMessageProc "Window Proc" function in your Program.
      With at least these 2 statements -
      if Msg = WM_DESTROY then PostQuitMessage(0);
     Result := DefWindowProc(hWnd,Msg,wParam,lParam);

3. Set your Windows Class Parameters and Register that Class.
      With at least these 4 params set -
      wClass.hInstance := hInstance;
      wClass.lpfnWndProc := @MessageProc;
      wClass.hbrBackground:= 16;
      wClass.lpszClassName:= 'Name';

4. Create your "Main" window using the Class that you Registered.

5. Have a  "while GetMessage(Msg,0,0,0) do"  loop to keep your program running.
      With at least this statement -
      DispatchMessage(Msg);


The next code is the minimum needed to get a windows GUI program running. You should just read this code and see if you understand what, why and how the code works.
program Project1;

uses
  Windows, Messages;
  {the Messages unit contains the windows
  Message constants like WM_COMMAND}

{$R *.RES}

var
wClass: TWndClass;
Msg: TMsg;

function WindowProc(hWnd,Msg,wParam,lParam:Integer):Integer; stdcall;
begin
if Msg = WM_DESTROY then PostQuitMessage(0);
Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;

begin
wClass.lpszClassName:= 'CN';
wClass.lpfnWndProc :=  @WindowProc;
{CreateWindow( ) will not work without setting the 
2 wClass parameters above}

wClass.hInstance := hInstance;
wClass.hbrBackground:= 1;
{CreateWindow( ) will still create a window without the
2 wClass parameters above, but they shoud be included}

// wClass.hIcon := LoadIcon(hInstance,'MAINICON');
// wClass.hCursor := LoadCursor(0,IDC_ARROW);

RegisterClass(wClass);

CreateWindow(wClass.lpszClassName,'Title Bar',
             WS_OVERLAPPEDWINDOW or WS_VISIBLE,
             10,10,340,220,0,0,hInstance,nil);

while GetMessage(Msg,0,0,0) do
DispatchMessage(Msg);

end.
You could use this code and compile this program, but it may not give you any knowledge about what is happening. Try to see why this code will create a main window (Form) that is visible and functional. You will be able to Maximize, Minimize and Resize this Form, and close it with the caption close (X) button. If you are used to Delphi VCL programming, then you may not have a clue about what this is doing. So read through this page and start your coding with the First Window App.

Notice that there are only 2 variables used here,
wClass: TWndClass;
    and
Msg: TMsg;
The first is for a "Windows Class" and the next is for a "Message Record". So we'll talk about windows Class and windows Messages next.

Start with more Easy
There are two things that "window" creation must have. A "window" can not be created without a "window Class" and a function -"Window Proc"- to connect it to the OS messages (this function is set in the Class). So we'll start talking about the windows Class first and then begin to explain windows Messaging.
I did not want to start with the RegisterClassEx(wClassEx); and CreateWindowEx( );
To try and have simpler coding using RegisterClass(wClass); and CreateWindow( ); . . . . The Ex versions are meant for 32-Bit programing, but the non-EX work just as well if you do not need the extra Parameters. More will be said about the Ex versions later.



Windows Class
The windows OS has many window creation options which uses a "Class" to define propeties for that window. Read your local windows API help for "CreateWindow", or the MSDN web site MSDN-CreateWindow, notice the first parameter is for a Class Name. The windows "Class" type used in window creation is different than the delphi "Class" type used for Objects.
If you read the WNDCLASS in your local windows API help or the web page at
MSDN-WNDCLASS and you will see -


typedef struct _WNDCLASS {  
    UINT    style; 
    WNDPROC lpfnWndProc; 
    int     cbClsExtra; 
    int     cbWndExtra; 
    HANDLE  hInstance; 
    HICON   hIcon; 
    HCURSOR hCursor; 
    HBRUSH  hbrBackground; 
    LPCTSTR lpszMenuName; 
    LPCTSTR lpszClassName; 
} WNDCLASS;

using Delphi types

  PWndClassA = ^TWndClassA;
  PWndClass = PWndClassA;

  tagWNDCLASSA = packed record
    style: Cardinal;
    lpfnWndProc: TFNWndProc;  //Pointer
    cbClsExtra: Integer;
    cbWndExtra: Integer;
    hInstance: THandle;
    hIcon: THandle;
    hCursor: THandle;
    hbrBackground: THandle;
    lpszMenuName: PChar;
    lpszClassName: PChar;
  end;

  TWndClassA = tagWNDCLASSA;
  TWndClass = tagWNDCLASSA;

All windows that are created, must use a registered windows Class, because the Class sets the function (Window Proc) that processes the messages to that window. To register a Class, you first need to set it's parameters. The two parameters that must be set in order have CreateWindow( ) succeed are lpszClassName and lpfnWndProc. Set the windows Class hInstance to the memory locacation of your apps hInstance, this associates that Class with your programs thread execution. The registered Class will be then unregistered when that hInstance is terminated. The hIcon and hCursor will define the Icon and Cursor used by windows of this Class. The hbrBackground is the brush handle used to paint the background of windows of this class, you can also use a system color reference number here. The lpszMenuName sets a menu for this class. The "style" sets how the window will update after moving it, how to process double-clicks, the way to allocate space for it's device context, and other properties of the window. Look at the Class styles in WNDCLASS API help or the web page MSDN-WNDCLASS. We will not be using a class style in this first program. The lpfnWndProc is set to the memory Address of the process (function) where windows messages are sent, often called a "Window Proc". Below is the code for a windows GUI app called "First Window App" where you can see how the class parameters are set up and then Registered, look below the Program's BEGIN for  wClass. Since we set   lpfnWndProc := @WndMessageProc;
  we need to have a WndMessageProc function with the (hWnd: HWnd; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UINT; stdcall; parameters to receive the messages. If there is no message function or that message function does not "Handle" (use) any messages, then a working GUI  can not be created. The lpszMenuName is for a PChar name of a Resource menu, we will not use this parameter here and set it to an empty string. The cbClsExtra and cbWndExtra params are for "Extra" information, you can place in the Registered class to read or change later. These will not be used here.

There are "Standard" Win32 classes like "BUTTON", "EDIT", "STATIC", "LISTBOX" and others, so you don't have to create and register those. Look at the Win32 help for "CreateWindow" or the web page MSDN-CreateWindow and review the "BUTTON" class for it's flags, which will be used in the "First Window App" program code below.

For additional information, you may want to look at your local API Help for
RegisterClass or the web page MSDN-RegisterClass
Window Classes or the web page at MSDN-Window Classes Overview and click the "About Window Classes" link.




Intro to Windows Messaging

Does Delphi VCL use windows messages? In Delphi you see that VCL controls have an Event tab in the Object Inspector. If you double click "OnKeyDown" there, then a procedure for that event is automaticly added to your code. You may not know that Delphi recieves and processes a windows system message (WM_KEYDOWN) to triger this event. Windows uses a Message Process ("Window Proc" function) to give all created windows and Apps input for things that are happening in the windows system, this is a fundamental and very important method of the Windows System. A keyboard key press generates an electrical contact which is turned into a scan code (device dependent identifier) for that keyboard. The keyboard device driver uses a scan code to translate (maps) it to a virtual-key code (device-independent value). After translating a scan code, the keyboard driver creates a window's system message that includes the scan code, the virtual-key code, and other information about that keystroke, and then places that message into the system message storage (waiting area or queue, since windows is mutitasking, it must wait for availible processor cycles). Windows removes that keyboard message from the system message storage area (queue) and sends it to the message queue of the thread with the focused window. A program gets these keyboard messages out of it's threads message queue by using a block of code called a "message loop" (see GetMessage loop below). When the message loop processes that message, it sends it to that window's "Window Proc" to use your code for that message or get default processing (see the WndMessageProc a "Window Proc" in the program below). Every window has this "Window Proc", that the OS calls whenever it has input (messages) for the window. A "Window Proc" is a special function that receives and is responsible for processing all messages sent to the window. All windows have a registered Class, and every registered Class has a "Window Proc", and every window created with that class uses that same "Window Proc" to process and respond to messages.

The OS sends a Msg to a "Window Proc" along with the 2 message data integers LParam and WParam. The "Window Proc" then tests the message (Msg parameter) to see if it is one that it wants to use, it then may check the message window identifier (Handle, the hWnd parameter) or, to use the message, tests or translates the LParam and WParam. A "Window Proc" does not usually ignore a message. If it does not use and process a message, it should send the message to the system for it's default processing. A "Window Proc" does this by calling the DefWindowProc( ) function, which performs a default action and returns the default message result. The "Window Proc" should return this value as its own message function result. Many "Window Proc" functions use just a few messages (like WM_COMMAND or WM_CLOSE) and send the others on to the system by calling DefWindowProc(hWnd, Msg, WParam, LParam).

Because a "Window Proc" is shared by all windows belonging to the same class, it can process messages for several different windows. To identify the specific window affected by the message, a "Window Proc" can test the hWnd parameter and execute the code need for that window. But if there is only one window created for that class, you do not need to use the hWnd parameter.

For additional information, you may want to look at your local API Help for
Window Procedures and click the >_> to read the pages there, or the web page MSDN-WindowProc Function, ,
About Window Procedures or the web page at MSDN-About Window Procedures .



Message Structure, the TMsg type
Whenever the user moves the mouse, clicks the mouse buttons, or types on the keyboard, the driver for that device converts the input into messages and places them in the system message queue. Windows removes the messages, one at a time in the order they were put in, from the system message queue, determines the destination window, and then sends them to the message queue of the thread that created the destination window. A thread's message queue gets all the mouse and keyboard messages for all the windows created by that thread. See your local API Help for GetMessage( ) or the web page MSDN-GetMessage. The thread uses a GetMessage( ) function to remove messages from its queue, usually in a while loop so the programs execution will continue until until GetMessage is False. (see GetMessage Loop Below) The system sends a message by filling a MSG (TMsg) structure and then places it in the message queue. Information in MSG includes: the handle (hWnd) of the window for which the message is intended, the message identifier (message), the two message parameters (LParam and WParam), the time (time) the message was posted, and the mouse cursor position (pt).
type

  PMsg = ^TMsg;
  tagMSG = packed record
    hwnd: HWND;      // Handle of destination window
    message: UINT;   // message to be processed
    wParam: WPARAM;  // 4-Byte message information
    lParam: LPARAM;  // 4-Byte message information
    time: DWORD;     // the time the messages was posted
    pt: TPoint;      // screen location for mouse
  end;
  TMsg = tagMSG;
  MSG = tagMSG;

end;
If you look at the
WndMessageProc(hWnd: HWnd; Msg: UINT; WParam: WPARAM;
              LParam: LPARAM): UINT; stdcall;
you will see that it also has a Msg variable, but these are of Different types, and would correspond to the TMsg.message type (UINT). I will use "Msg: TMsg" here, but in the following apps I will switch the variable's name to "mainMSG: TMsg" to help avoid confusing the many references to Msg.

NOTE - The C code variable type of UINT is not supported in the Delphi variable types. The C code UINT can have a single negative value of -1 and a Cardinal value up to 4294967294, there is no Delphi variable availible with this range, so the windows.pas just castes the UINT as a DWORD or Cardinal type. I will use the UINT type for the Message and Result types, in this first program, but will change them to the Integer type in later programs. The Value of a Msg will not go above the 2,147,483,647 Integer limit, so it is safe to do that.

A thread can send a message to its own message queue or to the queue of another thread by using the SendMessage( ) or PostMessage( ) function. These functions will be covered in the next lesson.


Get Message Loop
A rather magical function loop, which keeps all windows programs running and responding to user input is the GetMessage Loop. Without this GetMessage Loop, a program's process would not keep running, because the code progression would continue to the end. of the .DPR file and the process would exit.
A program's main thread can start its message loop,
while GetMessage( ) do
after registering a Class and creating at least one window. The GetMessage( ) function is called by the OS system from a different thread (an Operating system thread) when there is a message for a window in your program. You do not call the GetMessage( ) function in your code, the system will do all of the work for that. GetMessage( ) removes a Keyboard or Mouse message from its thread's message queue (there are functions that get a message without removing it from the queue), a program then uses the DispatchMessage(Msg) function to direct Windows OS to send the message to a "Window Proc". DispatchMessage sends the window handle, the message identifier, and the two message parameters (LParam and WParam) to the "Window Proc", but it does not pass the time the message was posted or mouse cursor position. If the time or cursor position are important to the message (cursor position in a mouse message), then the info is placed in the TMsg, LParam or WParam.

A simple message loop consists of one function call to each of these three functions: GetMessage, TranslateMessage, and DispatchMessage.
  var
  Msg: TMsg;

while GetMessage(Msg,0,0,0) do
  begin
    TranslateMessage(Msg);
{Translate any WM_KEYDOWN keyboard Msg
 to a WM_CHAR message}
    DispatchMessage(Msg);
{this Sends Msg to the address of the
"Window Proc" set in the Resistered
Window's Class for that window, hWnd}
  end;
The GetMessage( ) function returns TRUE unless it gets the WM_QUIT message, when it returns FALSE and ends the while loop, since the GetMessage( ) function is just before the final end. it usually keeps your Process from ending, allowing your program to "Run". A program can end its own GetMessage( ) loop by calling the PostQuitMessage( ) function, usually in response to the WM_DESTROY message in the "Window Proc".
A thread's message loop must include TranslateMessage(Msg) if the thread is to use character input from the keyboard. Windows sends virtual-key messages (WM_KEYDOWN and WM_KEYUP) every time the user presses a key. These virtual-key messages contains a code that identifies which key was pressed, but not its character value (which might depend on the state of the Shift key or the character Language). To get the character value, the message loop uses TranslateMessage( ), which translates the virtual-key codes into a character message (WM_CHAR) and places that into the application's message queue. The WM_CHAR message is then used by DispatchMessage(Msg) and sent to a "Window Proc".

The DispatchMessage( ) function sends a message to the "Window Proc" associated with the window handle (hWnd) specified in the Msg structure. If hWnd is 0, DispatchMessage does nothing with the message.
Only one GetMessage( ) loop is needed for a thread's message queue, even if a program contains many windows. DispatchMessage( ) always sends the message to the correct window; this is because messages in the queue are in a TMsg structure that contains the handle (hWnd) of the destination window.
Not all messages go through the GetMessage loop, some messages are sent directly to the Window Proc. Windows uses two methods to get messages to a Window Proc. One method sends messages to a first-in, first-out waiting area called a message queue (a system memory object that stores messages), the other method sends the messages directly to a window procedure. Messages sent to the message queue are called Queued messages. They are primarily the result of user input with the mouse or keyboard, such as the WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, and WM_CHAR messages. Other queued messages include the timer, paint, and quit messages: WM_TIMER, WM_PAINT, and WM_QUIT. Most other messages, which are sent directly to a window procedure, are called NonQueued messages

Since all queued messages for a thread go through the GetMeaasge( ) loop, you can catch specific messages or certain windows (hWnd) messages Before they are sent to that window, and modify the message or block it.

In your app's registered WndClass you set a "Window Proc" to get and use messages sent by DispatchMessage( ). Window controls like button, edit, static, listbox, ect. have a system "Window Proc" that processes that control's messages, so you do not need to have a Window Proc for system controls.


If you are used to working in the Delphi Form unit, you know you can to call "Application.Terminate" or main form "Close" to cause your program to quit. But this is a .dpr "program", and you can end the program execution simply by letting the code lines finish and the program gets to last line of code .end; So by ending the GetMessage loop, and the "end." line of the program code page will be executed. To end the GetMessage loop send the WM_QUIT with PostQuitMessage, and the GetMessage function will be called and will return False, exiting the loop. When your program exits, the windows system will Unregister your Instance's registered window's classes.

Try NOT to call "Halt;" or "ExitProcess(hInstance);" to stop your program if you have created windows, just call -
PostMessage(hAppHandle, WM_CLOSE, 0, 0);
so the WM_CLOSE can be processed by the DefWindowProc().


And in the WM_DESTROY Msg use PostQuitMessage(0) to end the GetMessage loop, so Windows can clean up. When the WM_CLOSE message is sent to the DefWndProc( ) it calls DestroyWindow( ) for that window. When the DestroyWindow( ) is called, a WM_DESTROY message is created, then the window and all of it's children are destroyed. This releases the window's system memory used to record it's properties (handle, width, height, position, Window Proc, style, children, and many others). If DestroyWindow is not called, then this memory with the objects properties might remain until the system is restarted. Avoid this type of memory leak if posible. If you have a critical error and must Halt the execution, at least try to call
DestroyWindow(hForm1);       for your main window.


WndMessageProc( ), the "Window Proc"
This WndMessageProc( ) function in the program code below, can determine what is displayed on screen for our program's client area and how this program responds to user and system input. I have used the function name of "WndMessageProc" just to show you that you do not have to call it "WndProc", as many code examples seem to imply. There are four parameters and a Result value, that give us the ability to communicate with the Operating System. Be aware that this Window Proc function is called by the Windows Operating System from a different thread (a system thread), but these calls are ALWAYS thread-safe, as if they were called by your thread. Here I use the C code variable types of "HWND", "UINT", "WPARAM" and "LPARAM" to correspond to the Win32 API Help presentation, the HWND and UINT would be Cardinal types in Delphi, and the WPARAM, LPARAM would be Integer types. However, for practical programing usage in Delphi pascal (which does not support a UINT type), they could all be Integer types.
WndMessageProc(hWnd: HWND;  // Handle of window to process message
               Msg: UINT;  // Message to process
               WParam: WPARAM;  // First Information Integer
               LParam: LPARAM  // Second Information Integer
               ): UNIT;  // Result sent back to message sender
The first parameter is hWnd: HWND; , which has the window handle that the message is sent to. In this first GUI program we create only one window of the class 'First Class', there will be only one hWnd value sent - hAppHandle. So we can ignore this value in "First Window App". If there is more that one window of this class created then we would filter the messages with the hWnd value. The next parameter is Msg: UINT; (Cardinal) which will contain a numeric value to indicate the "Message" that is sent to this function. When this function is called by the System, this message is usually a predefined standard Windows Message. We use only 5 messages in this program - WM_CREATE (value 1), WM_DESTROY (value 2), and WM_CLOSE (value 16), WM_COMMAND (value 273), and WM_CHAR (value 258). There are hundreds of Windows Messages (look in the Win32 API help index for WM_ ) and hundreds of specific control messages (like BM_SETCHECK, a button message). Each of the different messages are sent from the OS to a window for a specific event or reason. A Case statement is used to run code for that message. Many messages need additional information to determine what should be done, this info comes in the LParam and WParam parameters. These 2 Integer parameters can contain many different kinds of information and they will be compleatly different information for different types of messages, anything from a number or window handle, to a Pointer value for a TRect, PChar, or a complex record. Look at the WM_CHAR message in WndMessageProc( ), the wParam is the integer number for the charater. Now look at the WM_COMMAND message, notice the lParam is tested for the button's handle indicating that it was clicked. Many times the "Handle" of a windows system object will be used to identify it (like in the WM_COMMAND message). So we'll look at window's Handles now.


Handles
Whenever you use CreateWindow( ) it Returns the Windows Operating System "Handle" for that window. This Handle is a unique number (Cardinal) to identify the system information Stucture (Record) for the properties of this window. Handles are used as a numbered reference identifier for a window's OS object, a Window, Font, File, Brush, Device Context, and many others. A program uses this handle in other API functions to tell the OS which system object to use. A window handle has the HWND (THandle or Cardinal) data type.

Most of the API Create Functions (CreateWindowEx, CreateFont, CreateBrush, CreatePen, CreateFile, CreateDialog and others) return a handle to the object that is created. An Object is a windows structure that represents a system resource, such as a window, font, file, a thread, or a graphic image. A program cannot directly access the internal structure of a window, object or the system resource that an object represents. Unlike Delphi VCL where you use Form1.Top to get or set the Top position of Form1. Instead, the program must use an object handle in functions to examine or modify the system resource. Windows uses handles to limit access to system resources, the use of handles for system objects ensures that developers are not writing code to low-level, internal or device driver structures. This enables Microsoft to add or change functionality of the operating system, as long as the original calling functions are maintained.
When newer versions of the Window's OS are released, programs will gain this new functionality with little or no additional development. Also security can be used for some objects in an access-control list (ACL).

The value 0, zero, is not a window handle, but you can use it in some functions to specify that no window is affected or to reference the base or desktop screen window. For example, specifying 0 for the CreateWindowEx function's hwndParent parameter creates a window that has no parent or owner. Functions may return 0 instead of a handle, indicating that the given action doesn't apply to a specific window. The IsWindow( ) function determines whether a window handle identifies a valid, existing window.

For most objects, there are functions that create the object returning an object handle, close the object handle, and destroy the object. These functions may be combined or unnecessary, depending on the type of object and it's use. Handles and objects consume memory. So to increase system performance, a program should close handles and delete objects as soon as they are not needed. Any time you use a function that returns a "Handle", like "CreateWindow( )" or "GetDC( )", you should make sure to check and see if you need to use a "Destroy???", "Delete???", "Close???" or "Release???" function to remove it and release it's memory in the windows OS.

For additional information, you may want to look at your local API Help for
About Handles and Objects or the web page MSDN-About Handles and Objects




The CreateWindow( ) Function
As you saw in the Windows Class parameters, there are parameters (style) that set some general charateristics for the windows of that wClass. But registering a wClass does not create any windows, you have to use CreateWindow( ) or CreateWindowEX( ) to actually create a window. CreateWindowEX( ) has more parameters and will be used later. When you use CreateWindow( ) you can give much more information about the properties you want the window to have in the parameters of this function. Here's the definition for it -
function CreateWindow(lpClassName: PChar; lpWindowName: PChar;
  dwStyle: DWORD; X, Y, nWidth, nHeight: Integer; hWndParent: HWND;
  hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND;

CreateWindow(
lpClassName: PChar;   // PChar to registered class name
lpWindowName: PChar;  // PChar to window name or text of the window
dwStyle: Cardinal;    // window style bits
X: Integer,           // left horizontal position of window 
Y: Integer,           // top vertical position of window 
nWidth: Integer,      // window width
nHeight: Integer;     // window height 
hWndParent: THandle;  // handle to parent or owner window
hMenu: THandle;       // handle to menu or child-window identifier 
hInstance: THandle;   // handle to application instance 
lpParam: Pointer;     // pointer to window-creation data
): THandle;
You should look at the Win32 API Help for CreateWindow or the web page
MSDN-CreateWindow.

lpClassName is set to the window's Class you want this window to be derived from. There are standard windows control classes like "Button" and "Edit" that you would use for windows controls. For our main window (Form) in "First Window App" we will use the class name we registered with wClass.

lpWindowName is usually the displayed text for that window, if the window does not display text or you don't want any text (an empty Edit) then you set this to an empty PChar '' or nil. Although it is called lpWindowName it is NOT like the Delphi VCL "Name" property, and is NOT used to identify or reference the window with the OS, the window handle is used for that.

dwStyle Look at style for CreateWindow( ) (like WS_BORDER) in the Win32 API Help. The desriptions there are helpful, but you will have to use a style bit flag (and not use a style bit) to see how it affects the look and function of different types of windows. For example, a WS_CAPTION on a Button control will make a strange button with a caption.

X, Y, nWidth, nHeight are integers for position, X (Left), Y (Top) and size.

hWndParent is the Parent of the window if WS_CHILD is in the style or sets the owner window of the window being created. This should be 0, zero, for top level windows.

hMenu Identifies a menu, or specifies a child-window identifier depending on the window style. For an overlapped or pop-up window, hMenu identifies the menu to be used with the window; it can be zero if the class menu is to be used. For a child window, hMenu specifies the child-window identifier, an integer value used to notify its parent about events.

hInstance is set to the process hInstance.

lpParam is a Pointer to a TCreateStruct Record used by the lParam parameter of the WM_CREATE message. I don't use the lpParam here, but you can pass info to the WM_CREATE message with it. This is helpful for MDI window creation, but is generally set to nil otherwise.

Look at the CreateWindow( ) functions in the "Program First Window App" below to get some examples on how to use this function.




    Program First Window App

This program will show how to create a GUI program and try to show some of the ways messages are used in it's "Window Proc". Since Messages Boxes were used in the non-GUI apps before, 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 "function WndMessageProc( )" to show MessageBoxes for each of the 5 messages it responds to. It is important to have Result := DefWindowProc( ) in this Window Proc function inorder 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 sytle 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.

program Project1;
{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 > 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)+#10' 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): UNIT; stdcall;
begin
{This is the "Window Proc" used to communicate with the OS, These messages are how the
Operating System 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 := -1;
                   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,	// pointer to registered class name string
    'first Window app',	// pointer to window name (title bar Caption here)
    WS_OVERLAPPEDWINDOW,	// window style
// WS_OVERLAPPEDWINDOW is the default standard main window with a
// Title bar and system menu and sizing border
    Integer(CW_USEDEFAULT),	// horizontal position of window
    Integer(CW_USEDEFAULT),	// vertical position of window
// CW_USEDEFAULT is from earier 16-bit windows programing. I included it
// here only because most begining programming instuctions use it. But I
// don't see much use for it, you may as well use 0, 0
    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
    // window-creation data can be infromation about
    // creation options or data storage
   );

{below is an Alternate main window Creation, which will give the same window
style as the one above, it includes all the style options of WS_OVERLAPPEDWINDOW
as individual paramerers.
use the one above or this one but not both.}
{hAppHandle := CreateWindow( wClass.lpszClassName, 'first Window app',
    WS_CAPTION or WS_MINIMIZEBOX or WS_MAXIMIZEBOX or WS_SYSMENU or WS_THICKFRAME,
  // these window styles will give the same style as WS_OVERLAPPEDWINDOW
  // if you leave out the WS_MINIMIZEBOX or the WS_MAXIMIZEBOX then
  // those caption buttons will be grayed out (not enabled)
  // if you leave out the WS_THICKFRAME then it will NOT be mouse sizable
    200, 100, 386, 250,
    0,	// handle to parent or owner window
    0,	// handle to menu or child-window identifier
    hInstance,	// handle to application instance
    nil 	// pointer to window-creation data
   );}

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}
  TranslateMessage(Msg);  {Translate any WM_KEYDOWN keyboard Msg 
                             to a WM_CHAR message}
  DispatchMessage(Msg);  {this Sends Msg to the address of the   
                            "Window Procedure" set in the Resistered 
                            Window's Class for that window}
  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.

Afer 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.
You can add these styles to the Static control "SS_CENTER or SS_BLACKFRAME".


                           

Next
We have made a GUI program that creates windows, and has a Message Loop to interact with Windows OS. 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 to know. So the next program 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.
  Lesson 3, More Messages


       

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




H O M E