Home |
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 -
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.
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 .
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).
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 senderThe 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. |
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. |
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 |