/*
ProgEx40 -- Form3: Volume Of A Box. Decorating Your Form With User Interface
Controls Such As Edit Boxes, Buttons, And Combo Boxes.
I'm going to try something different. Usually when I write tutorials I come up with an
idea for a program; I write the whole program; then I post the whole thing with comments
and discussion throughout. In this case, I have the idea for a good learning program,
but I'll post it in segments as it develops. In that way you'll hopefully be able to
grasp the individual pieces as it develops, rather than being intimidated by all the
code bulk of the final product.
Lets start with something ridiculously simple. My idea is a form with several text
boxes where one can enter the length, width, and height of a box. There will be two
buttons on it. The 1st button will calculate the volume of the box and display it in
another text box labeled 'Volume'. The 2nd button named 'Display' will output the entry
data and volume to a programatically created output screen which the user can dismiss
when finished viewing the output. Finally, the main Form will have a drop-down combo
box near the top with several selections in it regarding the kind of box we are working
with.
We already have a template program we can start with, and that would be Form1 of
ProgEx39c. Here are some of the things I did to that example so it could be used for
this little project;
1) Remove all the MessageBoxes;
2) Replace the WM_LBUTTONDOWN message handling with a WM_COMMAND handler;
3) Add a bit more complexity to the message loop down in WinMain() to deal with tab
order of child window controls on the Main Form. We need to use IsDialogMessage()
for that;
4) Add code in WM_CREATE to create our child window controls, i.e., text boxes, buttons,
etc.
Believe it or not, that's it! Take a look at the code below and compare it with
ProgEx39c and you'll see I speak the truth. Better yet, compile the code and run it and
you should see a nice little program that at this point doesn't calculate anything, but
at least most of the user interface is working and the tab order works.
Of course, fnWndProc_OnCreate() is pretty unrecognizable. That's where some big time
stuff happened. Lets take it one line and one concept at a time, and quite soon you'll
see that fnWndProc_OnCreate() is where this app gets put together.
This line...
TCHAR* szBuffer[]=
{
(TCHAR*)_T("Ordinary Box"),
(TCHAR*)_T("Box Of Junk"),
(TCHAR*)_T("Beer Crate"),
(TCHAR*)_T("Wine Box"),
(TCHAR*)_T("Candy Box")
};
creates a TCHAR character string array of the text strings that are to go in our combo-box.
This line...
DWORD dwStyle=WS_CHILD|WS_VISIBLE|WS_TABSTOP;
creates some useful Window Style bits for inclusion in the many CreateWindow() and
CreateWindowEx() calls below. Here momentarily we'll discuss bits and Window Styles.
This line...
HWND hCtl;
declares an object of HWND (handle to a window) that will be returned from the various
CreateWindow() calls of our child control creation.
Right at the top of the WM_CREATE handler you see this as the 1st line of code...
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
If you look down in WinMain() at the parameter list to the function you'll see that
something termed a...
HINSTANCE hInstance
was the 1st parameter in the parameter list, and that it was used in several places
there. Most importantly for us here, it is the next to last parameter of the big
CreateWindow() call. So we need it in fnWndProc_OnCreate() for the exact same reason
it was needed down in WinMain(), that is, as the next to lat parameter of more
CreateWindow() calls. However, its essentially a local variable in WinMain() and we're
working under the rather strict regimen here of not allowing ourselves global variables.
So how are we going to get it? Well, as it turns out there are no end of ways to get
it.
When you make a CreateWindow() call Windows stores all the many parameters of the call
internally in something named a CREATESTRUCT...
typedef struct tagCREATESTRUCT
{ // cs
LPVOID lpCreateParams;
HINSTANCE hInstance;
HMENU hMenu;
HWND hwndParent;
int cy;
int cx;
int y;
int x;
LONG style;
LPCTSTR lpszName;
LPCTSTR lpszClass;
DWORD dwExStyle;
}CREATESTRUCT;
Note that the 2nd member is the HINSTANCE of the application. And when Windows sends a
WM_CREATE message, the lParam is a pointer to this CREATESTRUCT. In terms of the
awkward syntax with the casting, you'll just have to learn to live with it. Afterall,
we're doing C++ here! Its not supposed to be nice or friendly! In PowerBASIC it just
looks like this...
Local lpCreateStruct As CREATESTRUCT Ptr
lpCreateStruct=Wea.lParam
Wea.hInst=@lpCreateStruct.hInstance
...but we're not doing PowerBASIC right now. Anyway, the HINSTANCE could also be gotten
using...
Wea->hIns=GetModuleHandle(NULL);
or even...
Wea->hIns=(HINSTANCE)GetWindowLong(Wea->hWnd,GWL_HINSTANCE);
Enough said about that I guess! So now we're at our 1st CreateWindow() call! And for a
lowly label no less!
hCtl=
CreateWindow
(
_T("static"), //Class name of Window to be instantiated
_T("Choose Kind of Box"), //Caption if any
WS_CHILD|WS_VISIBLE, //Window styles desired bit wise or'ed together
10, //x pixel location desired of window
10, //y pixel --------------------------
140, //Width of Window
25, //Height --------
Wea->hWnd, //Parent of Window or 0=HWND_DESKTOP if top level main Window
(HMENU)-1, //Either menu identifier or Control ID if child Window
Wea->hIns, //Process instance handle, i.e., hInstance
0 //Pointer to Creation Parameters if any or NULL
);
You may have noted that sometimes I use CreateWindow() and at other times
CreateWindowEx(). Truth be told I should probably always use CreateWindowEx() because it
superceded CreateWindow() some time ago. However, the only difference between the two
is that an extra Window Style parameter was added named dwExStyle. For the label
control above the extended styles don't do much so I just used CreateWindow(). Had I
used CreateWindowEx() in the call above I'd have just put a zero in the unused parameter
place.
Since these calls figure pretty prominently in the code we're discussing, here is the
Api help on CreateWindowEx (which you've seen before if you've gotten this far) ...
HWND CreateWindowEx
(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);
Have you figured out yet that to do this kind of coding you need to constantly have a
reference to the Windows Api functions handy? I bet you have!
The "static" term in our 1st child window CreateWindow() call refers to the class name
for a label. If you check out the Api documentation on CreateWindow() or
CreateWindowEx() you'll find all the various class names. The only ones we're using
here are "static", "button", "edit", and "combobox".
In case you're curious, note that in WinMain() we Registered with Windows our own class
name when we filled out the members of a WNDCLASSEX struct and registered it with
RegisterClassEx(). Back there we used the class name "Form3" and in our CreateWindow()
call we put "Form3" in the parameter location we're putting "static", "edit", etc.,
here. With regard to this, pause for a moment and think about the big picture of what's
going on here. When we filled out those WNDCLASSEX fields of that struct down in
WinMain() and registered that class, one of the fields we filled out was the pointer to
the Window Procedure. And we're now coding more CreateWindow() calls in the WM_CREATE
handler of that Window Procedure for the Form3 Window - our main program window. Has it
occurred to you yet as to where the Window Procedure might be for these "static",
"button", etc., windows we're here creating? Search as you may you won't find them here.
These are controls predefined by Microsoft and the Window Procedures for these controls
are within Windows itself. We don't have to create buttons, edit controls, etc.,
ourselves. Microsoft has already done it. However, if we wish to build a main
application window or a custom control in which are located other simpler controls and
which has its own Window Procedure, we are free to do so.
Lets move on to the Style parameter of the CreateWindow() call. This is quite important
and in the Windows Api documentation you'll find myriads of styles for the various
controls and quite a few for even top level main program windows. For our lowly static
control, i.e., label, the ones most frequently used are WS_CHILD | WS_VISIBLE. If you
look down in WinMain our main Form3 program window used a style of WS_OVERLAPPEDWINDOW.
In this latter case WS_OVERLAPPEDWINDOW is a shorthand composite style comprised of...
WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, and WS_MAXIMIZEBOX
Moving on to the 4th, 5th, 6th, and 7th parameters of our 1st CreateWindow() call in
fnWndProc_OnCreate(), these are nothing more than the x/y pixel coordinate position
where we want the upper left hand corner of our window instantiated with regard to its
parent, and the pixel width/height desired for the window.
The 8th parameter is the HWND of the parent of the window. For our label its the main
program window represented by Wea->hWnd (at this point you're probably tired of handles.
After all, what are they? Best description would probably be opaque pointers. They
reference private data internal to windows that we Microsoft outsiders don't need to
know about). Down in WinMain() for our main program window we just used HWND_DESKTOP
which is an equate equal to zero (in other words, it doesn't have a parent, or the
parent is the desktop).
The next parameter - the 9th, is a menu handle for the window if it has a menu or a
child window numeric identifier. The latter applies in our case as the label doesn't
have a menu nor even our main program window. However, the parameter is typed as taking
a handle to a menu ( HMENU ) which is a specific Windows data type defined in Windows
headers, so we need the HMENU cast you see. And in the case of labels the numeric value
-1 can be used telling Windows to not bother tracking the child window id of the
control. However, this is a very important topic even though in this specific instance
we're more or less ignoring the issue with the (HMENU)-1 value. For you see, all child
windows such as buttons, edit boxes, etc., need an integral numeric value associated
with the specific instance of the control - whatever it may be. Please take a look at
ProgEx40.h and right near the top you'll see this...
#define IDC_COMBO_TYPE 1500
#define IDC_EDIT_LENGTH 1505
#define IDC_EDIT_WIDTH 1510
#define IDC_EDIT_HEIGHT 1515
#define IDC_EDIT_VOLUME 1520
#define IDC_BUTTON_CALCULATE 1525
#define IDC_BUTTON_DISPLAY 1530
I've assigned numeric values starting at 1500 and incrementing by 5 for all the controls
on the main form ( Form3 ) except for the label controls adjacent to the combo box and
the four edit controls. All this relates pretty closely to the fact that when I created
all the child window controls with no less than 12 CreateWindow() or CreateWindowEx()
Api calls I disgarded each and every returned hCtl from each function call rather than
saving it in a specifically declared unique HWND variable such as hWndLength for the
Window Handle of the Length textbox; hWndWidth for the Width textbox, etc. I put each
one in hCtl and it was immediately lost with the next call. The reason I did this is
that if you know the child control id of a control and its parent, you can always
retrieve the control's HWND from...
hWndChildControl = GetDlgItem(hParent, CHILD_WINDOW_CONTROL_ID);
Window handles are an extremely important and needed variable in Windows programming,
but you never need make global variables out of them for this reason. In this sense,
you can think of these child window control ids as proxies for Window Handles. They are
global elements with the attribute of being equeate/defines which aren't altered. In
that sense I wouldn't have even needed assigning the return to each CreateWindow() call
to hCtl; I could have just did a bare CreateWindow() call as a sub-routine without a
return; however, I'm used to seeing return values on CreateWindow() calls so I hesitate
to do that.
The next parameter - the 10th, is the HINSTANCE we needed and got from the lParam using
the CREATESTRUCT pointer. This would be the same HINSTANCE that came through in the 1st
parameter of the WinMain() call and was used there for our main Window.
The last parameter - the 11th, we just assigned NULL. We didn't use it. However its
typed like so...
LPVOID lpParam // pointer to window-creation data
This is a pointer parameter and it can be a pointer to anything whatsoever you have a
mind to pass to the WM_CREATE handler of your Window Procedure. Its another global
killer is what it is. I'm not going to say anymore about it here because we're not
using it in this first version of this app. Just tuck away for future reference that
its very important (and we'll be using it soon).
Whoa! That was a lot of description for a lowly label/static control! I'd better speed
things up and tell you that the above descriptions apply in a general sense to all the
other CreateWindow() calls! You'll note in all the other calls that aren't labels the
control id defines from ProgEx40.h are placed in the HMENU/Control Id parameter
position. Also, the various types of controls have the required styles to give them the
functionality they need. For example, I used CreateWindowEx() calls for the edit
controls because I wanted a nice 3D sunken edge effect for them, and for that I needed
this extended style...
WS_EX_CLIENTEDGE
Also, I wanted some sort of reasonable tabbing functionality so I included the
WS_TABSTOP style in all the non-label controls as well as styles to make them child
controls that were visible.
The last major thing I want to cover in this 1st installment of Form3 is the
SendMessage() call that put our box types in the combo box. This is a really important
Windows Api call. First note (pull up MSDN or your Api Docs on SendMessage) that the
parameters of the call exactly match the function signature of a Window Procedure. Here
I'm not speaking of my event handlers such as fnWndProc_OnCreate() or so on and so forth
but the main program's Window Procedure - fnWndProc(), whose signature is thus...
HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam
In object oriented programming objects communicate with each other by sending messages
to one another (Yes, we're doing a kind of OOP here even though we're essentially using
C manerisms to do it). In this sequence of code...
for(unsigned int i=0; i<5; i++)
SendMessage(hCtl,CB_INSERTSTRING,(WPARAM)-1,(LPARAM)szBuffer[i]);
...the hCtl parameter is the Window Handle for the just created Combo Box. Note I put
this loop that uses the HWND of the Combo Box right after the CreateWindow() call that
created the Combo Box because I knew I'd lose the HWND after the next CreateWindow()
call so I wanted to use it while I still had it. Now do a search in your documentation
for either Combo Box, Combo Box Messages, Default Combo Box Behavior or whatever until
you come up with a reference to Combo Box specific messages. There are a lot of them
and examples would be CB_ADDSTRING, CB_DELETESTRING, CB_INSERTSTRING, CB_GETCOUNT, etc.
If you do a search for CB_INSERTSTRING you'll eventually come to this...
--Microsoft Documentation--
CB_INSERTSTRING
An application sends a CB_INSERTSTRING message to insert a string into the list box of a
combo box. Unlike the CB_ADDSTRING message, the CB_INSERTSTRING message does not cause a
list with the CBS_SORT style to be sorted.
CB_INSERTSTRING
wParam = (WPARAM) index; // item index
lParam = (LPARAM) (LPCTSTR) lpsz; // address of string to insert
Parameters
index Value of wParam. Specifies the zero-based index of the position at which to
insert the string. If this parameter is –1, the string is added to the end of
the list.
lpsz Value of lParam. Pointer to the null-terminated string to be inserted. If you
create the combo box with an owner-drawn style but without the CBS_HASSTRINGS
style, the value of the lpsz parameter is stored rather than the string it would
otherwise point to.
Return Values
The return value is the index of the position at which the string was inserted. If an
error occurs, the return value is CB_ERR. If there is insufficient space available to
store the new string, it is CB_ERRSPACE.
--End Microsoft--
So, you can see where the other parameters came from. But the important point to grasp
here is that a Combo Box is a very specific Window Class designed by Microsoft, and its
Window Procedure the code of which imbues it with all the behaviors we've come to expect
of Combo Boxes, is within Windows Operating System Dlls and we communicate with that code
as here with SendMessage function calls. This is in fact a generalized mechanism for the
interaction of controls, i.e., windows, throughout the operating system, and the function
signature of Window Procedures and SendMessage calls is something you'll run into in many
places.
Finally, near the bottom of fnWndProc_OnCreate I use SetWindowText() to alter the
caption of the main program window ( Form3 ), and SetFocus() to set the focus on the
top most control on the Form which is the Box Type Combo Box. Note in that call I used
the GetDlgItem() call to retrieve the HWND of the Combo Box which I so foolishly and in
such a cavalier manner disgarded.
In the next version of this program we'll flesh out some of the additional functionality
we need to start getting things to work when we type text and click buttons.
*/
//Main.cpp
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>
#include "ProgEx40.h"
long fnWndProc_OnCreate(lpWndEventArgs Wea) //When the main program window receives its one and only
{ //WM_CREATE message, we'll take that oportunity to make
TCHAR* szBuffer[]= //more CreateWindowEx() calls so as to create our child
{ //window controls such as labels, text boxes, and combo
(TCHAR*)_T("Ordinary Box"), //boxes. An important concept in Windows programming are
(TCHAR*)_T("Box Of Junk"), //parent - child relationships. In our previous programs
(TCHAR*)_T("Beer Crate"), //our main windows contained no child window controls.
(TCHAR*)_T("Wine Box"), //This program has quite a few. Note that the same
(TCHAR*)_T("Candy Box") //function call that creates a main program window, i.e.,
}; //CreateWindowEx(), is used to create child window
DWORD dwStyle=WS_CHILD|WS_VISIBLE|WS_TABSTOP; //controls. In fact, as you should have seen, one of the
HWND hCtl; //parameters of the CreateWindowEx() call is the handle
//of the parent window.
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance; //In WM_CREATE, lParam is a pointer to a CREATESTRUCT. Look up CREATESTRUCT!
hCtl=CreateWindow(_T("static"),_T("Choose Kind of Box"),WS_CHILD|WS_VISIBLE,10,10,140,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindow(_T("combobox"),_T(""),dwStyle|CBS_DROPDOWNLIST|WS_VSCROLL,150,10,140,100,Wea->hWnd,(HMENU)IDC_COMBO_TYPE,Wea->hIns,0);
for(unsigned int i=0; i<5; i++)
SendMessage(hCtl,CB_INSERTSTRING,(WPARAM)-1,(LPARAM)szBuffer[i]);
SendMessage(hCtl,CB_SETCURSEL,(WPARAM)0,0);
hCtl=CreateWindow(_T("static"),_T("Length of Box"),WS_CHILD|WS_VISIBLE,10,53,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Width of Box"),WS_CHILD|WS_VISIBLE,10,93,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,90,70,25,Wea->hWnd,(HMENU)IDC_EDIT_WIDTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Height of Box"),WS_CHILD|WS_VISIBLE,10,133,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,130,70,25,Wea->hWnd,(HMENU)IDC_EDIT_HEIGHT,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Volume"),WS_CHILD|WS_VISIBLE,10,183,70,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,80,180,300,25,Wea->hWnd,(HMENU)IDC_EDIT_VOLUME,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Calculate"),dwStyle,245,65,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_CALCULATE,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Display"),dwStyle,245,110,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_DISPLAY,Wea->hIns,0);
SetWindowText(Wea->hWnd,_T("Volume Of Your Box"));
SetFocus(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE));
return 0;
}
long fnWndProc_OnCommand(lpWndEventArgs Wea) //We'll be working with this later.
{
return 0;
}
long fnWndProc_OnClose(lpWndEventArgs Wea) //When you click the x button Windows sends a WM_CLOSE message. If
{ //you return 0 you'll need to call DestroyWindow() because Windows
DestroyWindow(Wea->hWnd); //won't follow through with its own DestroyWindow() call in that case.
PostQuitMessage(0); //WM_CLOSE is really the place to ask the user if he/she wishes to
return 0; //close the app or window.
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea; //This procedure loops through the EVENTHANDER array
//of structs to try to make a match with the msg parameter
for(unsigned int i=0; i<dim(EventHandler); i++) //of the WndProc. If a match is made the event handling
{ //procedure is called through a function pointer -
if(EventHandler[i].iMsg==msg) //(EventHandler[i].fnPtr). If no match is found the
{ //msg is passed onto DefWindowProc().
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*EventHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form3");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof(WNDCLASSEX); wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=0;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,200,400,400,250,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{ //Is it not a Dialog Message?
if(!IsDialogMessage(hWnd,&messages)) //Added this because it does special character code translations that
{ //allows the use of the tab key to move focus among child window
TranslateMessage(&messages); //controls with the WS_TABSTOP style on the main Form.
DispatchMessage(&messages);
}
}
return messages.wParam;
}
//ProgEx40.h
#ifndef PROGEX40_H
#define PROGEX40_H
#define IDC_COMBO_TYPE 1500
#define IDC_EDIT_LENGTH 1505
#define IDC_EDIT_WIDTH 1510
#define IDC_EDIT_HEIGHT 1515
#define IDC_EDIT_VOLUME 1520
#define IDC_BUTTON_CALCULATE 1525
#define IDC_BUTTON_DISPLAY 1530
typedef struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
}WndEventArgs, *lpWndEventArgs;
long fnWndProc_OnCreate (lpWndEventArgs Wea);
long fnWndProc_OnCommand (lpWndEventArgs Wea);
long fnWndProc_OnClose (lpWndEventArgs Wea);
struct EVENTHANDLER
{
unsigned int iMsg;
long (*fnPtr)(lpWndEventArgs);
};
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_CLOSE, fnWndProc_OnClose}
};
#define dim(x) (sizeof(x) / sizeof(x[0]))
#endif
/*
ProgEx40a -- Form3: Volume Of A Box. Responding To Button Presses.
The only changes to this installment of our Form3 Volume Of Box program of ProgEx40 is
the fleshing out of some button click logic in our WM_COMMAND message handler. The
totality of the changes are these 20 or so new lines of code...
void btnCalculate_OnClick(lpWndEventArgs Wea)
{
MessageBox(Wea->hWnd, "You've Clicked The Calculate Button!", "Button Click Report", MB_OK);
}
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
MessageBox(Wea->hWnd, "You've Clicked The Display Button!", "Button Click Report", MB_OK);
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_BUTTON_CALCULATE:
btnCalculate_OnClick(Wea);
break;
case IDC_BUTTON_DISPLAY:
btnDisplay_OnClick(Wea);
break;
}
return 0;
}
Child window controls like we added to the Form in the WM_CREATE message handler
communicate with their parent by sending WM_COMMAND messages. Of course, we've learned
that associated with each message a Window Procedure receives is the handle of the
Window the message (1st parameter) applies to, as well as the two WPARAM and LPARAM
message specific details which carry additonal message specific information. Since
Windows was designed to run with high efficiency these 32 bit WPARAM and LPARAM
parameters often carry their payloads combined into 16 byte packets of information as
we found out in our Form2 where we used the HIWORD and LOWORD macros to extract 16 bit
chunks. When a button control is clicked, for example, it sends its parent's Window
Procedure a WM_COMMAND message and the LOWORD of the WPARAM will be the Control ID of
the button which was clicked. Below is the Microsoft Documentation on the WM_COMMAND
message...
--Microsoft--
WM_COMMAND
The WM_COMMAND message is sent when the user selects a command item from a menu, when a
control sends a notification message to its parent window, or when an accelerator
keystroke is translated.
WM_COMMAND
wNotifyCode = HIWORD(wParam); // notification code
wID = LOWORD(wParam); // item, control, or accelerator identifier
hwndCtl = (HWND) lParam; // handle of control
Parameters
wNotifyCode Value of the high-order word of wParam. Specifies the notification code if
the message is from a control. If the message is from an accelerator, this
parameter is 1. If the message is from a menu, this parameter is 0.
wID Value of the low-order word of wParam. Specifies the identifier of the menu
item, control, or accelerator.
hwndCtl Value of lParam. Handle to the control sending the message if the message
is from a control. Otherwise, this parameter is NULL.
Return Values
If an application processes this message, it should return zero.
--End Microsoft--
Their wID above is the equate/define I've been speaking of in terms of Control IDs. Note
that there is other information contained in both wParam and lParam which my code above
seems to be ignoring. Specifically, the HIWORD(wParam) contains what is termed the
notification code. In the case of a button when it has been pressed that would be a
define named BN_CLICKED. Buttons are somewhat unusual among the other controls in that
the BN_CLICKED message is oftentimes the only one of interest although there are several
others. So while its not absolutely perfect what I've done above, its what you'll see
most often with regard to buttons. In most other cases though, you have to test both
the control ID and the notification code. Take the Combo Box on this Form for example.
Combo Boxes are somewhat more complex than buttons. When the arrow expander is clicked
to open up a Combo Box the CBN_DROPDOWN notification is sent along with the control id
of the combobox. An app may need to run specific code in response to that. Once the user
makes a selection from the dropdown list, the CBN_SELCHANGE notification is sent. So,
I'm just wanting to make you aware of this even though this app doesn't make use of any
such functionality.
Now that we've got a little bit of this sort of internal plumbing in place, we can begin
to think about collecting information the user may have entered, and calculating some
volumes. Please compile & run this example though so you have a feel for what's working
now and what isn't.
*/
//Main.cpp
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>
#include "ProgEx40a.h"
long fnWndProc_OnCreate(lpWndEventArgs Wea) //When the main program window receives its one and only
{ //WM_CREATE message, we'll take that oportunity to make
TCHAR* szBuffer[]= //more CreateWindowEx() calls so as to create our child
{ //window controls such as labels, text boxes, and combo
(TCHAR*)_T("Ordinary Box"), //boxes. An important concept in Windows programming are
(TCHAR*)_T("Box Of Junk"), //parent - child relationships. In our previous programs
(TCHAR*)_T("Beer Crate"), //our main windows contained no child window controls.
(TCHAR*)_T("Wine Box"), //This program has quite a few. Note that the same
(TCHAR*)_T("Candy Box") //function call that creates a main program window, i.e.,
}; //CreateWindowEx(), is used to create child window
DWORD dwStyle=WS_CHILD|WS_VISIBLE|WS_TABSTOP; //controls. In fact, as you should have seen, one of the
HWND hCtl; //parameters of the CreateWindowEx() call is the handle
//of the parent window.
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance; //In WM_CREATE, lParam is a pointer to a CREATESTRUCT. Look up CREATESTRUCT!
hCtl=CreateWindow(_T("static"),_T("Choose Kind of Box"),WS_CHILD|WS_VISIBLE,10,10,140,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindow(_T("combobox"),_T(""),dwStyle|CBS_DROPDOWNLIST|WS_VSCROLL,150,10,140,100,Wea->hWnd,(HMENU)IDC_COMBO_TYPE,Wea->hIns,0);
for(unsigned int i=0; i<5; i++)
SendMessage(hCtl,CB_INSERTSTRING,(WPARAM)-1,(LPARAM)szBuffer[i]);
SendMessage(hCtl,CB_SETCURSEL,(WPARAM)0,0);
hCtl=CreateWindow(_T("static"),_T("Length of Box"),WS_CHILD|WS_VISIBLE,10,53,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Width of Box"),WS_CHILD|WS_VISIBLE,10,93,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,90,70,25,Wea->hWnd,(HMENU)IDC_EDIT_WIDTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Height of Box"),WS_CHILD|WS_VISIBLE,10,133,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,130,70,25,Wea->hWnd,(HMENU)IDC_EDIT_HEIGHT,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Volume"),WS_CHILD|WS_VISIBLE,10,183,70,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,80,180,300,25,Wea->hWnd,(HMENU)IDC_EDIT_VOLUME,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Calculate"),dwStyle,245,65,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_CALCULATE,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Display"),dwStyle,245,110,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_DISPLAY,Wea->hIns,0);
SetWindowText(Wea->hWnd,_T("Volume Of Your Box"));
SetFocus(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE));
return 0;
}
void btnCalculate_OnClick(lpWndEventArgs Wea)
{
MessageBox(Wea->hWnd, _T("You've Clicked The Calculate Button!"), _T("Button Click Report"), MB_OK);
}
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
MessageBox(Wea->hWnd, _T("You've Clicked The Display Button!"), _T("Button Click Report"), MB_OK);
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_BUTTON_CALCULATE:
btnCalculate_OnClick(Wea);
break;
case IDC_BUTTON_DISPLAY:
btnDisplay_OnClick(Wea);
break;
}
return 0;
}
long fnWndProc_OnClose(lpWndEventArgs Wea) //When you click the x button Windows sends a WM_CLOSE message. If
{ //you return 0 you'll need to call DestroyWindow() because Windows
DestroyWindow(Wea->hWnd); //won't follow through with its own DestroyWindow() call in that case.
PostQuitMessage(0); //WM_CLOSE is really the place to ask the user if he/she wishes to
return 0; //close the app or window.
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea; //This procedure loops through the EVENTHANDER array
//of structs to try to make a match with the msg parameter
for(unsigned int i=0; i<dim(EventHandler); i++) //of the WndProc. If a match is made the event handling
{ //procedure is called through a function pointer -
if(EventHandler[i].iMsg==msg) //(EventHandler[i].fnPtr). If no match is found the
{ //msg is passed onto DefWindowProc().
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*EventHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form3");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof(WNDCLASSEX); wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=0;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,200,400,400,250,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{ //Is it not a Dialog Message?
if(!IsDialogMessage(hWnd,&messages)) //Added this because it does special character code translations that
{ //allows the use of the tab key to move focus among child window
TranslateMessage(&messages); //controls with the WS_TABSTOP style on the main Form.
DispatchMessage(&messages);
}
}
return messages.wParam;
}
//Header File For Above
//ProgEx40a.h
#ifndef PROGEX40_H
#define PROGEX40_H
#define IDC_COMBO_TYPE 1500
#define IDC_EDIT_LENGTH 1505
#define IDC_EDIT_WIDTH 1510
#define IDC_EDIT_HEIGHT 1515
#define IDC_EDIT_VOLUME 1520
#define IDC_BUTTON_CALCULATE 1525
#define IDC_BUTTON_DISPLAY 1530
typedef struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
}WndEventArgs, *lpWndEventArgs;
long fnWndProc_OnCreate (lpWndEventArgs Wea);
long fnWndProc_OnCommand (lpWndEventArgs Wea);
long fnWndProc_OnClose (lpWndEventArgs Wea);
struct EVENTHANDLER
{
unsigned int iMsg;
long (*fnPtr)(lpWndEventArgs);
};
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_CLOSE, fnWndProc_OnClose}
};
#define dim(x) (sizeof(x) / sizeof(x[0]))
#endif
/*
ProgEx40b -- Form3: Volume Of Your Box. Retrieving Data From Edit Controls and Combo
Box, Doing Calculations, And Returning Results To Volume Edit Control.
At this point we have the button click procedures working, so all we need to do now is
figure out how to get data out of the edit controls so we can do some calculations. As
it turns out there is a SendMessage() call that will extract info out of an edit
control but GetWindowText() is usually easier...
--Microsoft--
int GetWindowText
(
HWND hWnd, // handle to window or control with text
LPTSTR lpString, // address of buffer for text
int nMaxCount // maximum number of characters to copy
);
Parameters
hWnd Handle to the window or control containing the text.
lpString Pointer to the buffer that will receive the text.
nMaxCount Specifies the maximum number of characters to copy to the buffer, including
the NULL character. If the text exceeds this limit, it is truncated.
--End Microsoft--
From reading the above documentation it looks like we need the window handles of all the
edit controls to use the function. All those I threw away back in fnWndProc_OnCreate()
where they were all returned to me in the CreateWindow() calls that created them.
'Waste not, want not' is the old saying, and I guess its true! I wasted them and now I
need them and they're gone!
Well, I'm as big a belierer in frugality and the 'waste not, want not' philosophy as
anyone (you ought to see my celler and outbuildings), but as you know we can retrieve
these window handles anytime we want as long as we have their control ids and parent
window handle and we indeed have those. Without further ado here is the next version of
our program and all the changes from the last are in...
void btnCalculate_OnClick(lpWndEventArgs Wea)
Oh! That's not completely true! I had to add the stdio.h header for the sprintf
function which I needed to convert a double representing the volume back into a
character string. I didn't include my String Class here because I thought it was a
little overkill for the fairly lightweight string minipulations we needed here.
And still another thing I almost forgot! I added a struct named ProgramData to
ProgEx40b.h. It just amalgamates the characteristics of a box into a struct...
struct ProgramData
{
double dblLength;
double dblWidth;
double dblHeight;
double dblVolume;
char szBoxType[16];
};
Its used in btnCalculate_OnClick().
Our next job will be to look at what we need to do to get a display screen up and
running. By the way, I just compiled this with CodeBlocks, and the executable is up
to a whopping 9K! Talk about code bloat!
*/
//Main.cpp
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "ProgEx40b.h"
long fnWndProc_OnCreate(lpWndEventArgs Wea) //When the main program window receives its one and only
{ //WM_CREATE message, we'll take that oportunity to make
TCHAR* szBuffer[]= //more CreateWindowEx() calls so as to create our child
{ //window controls such as labels, text boxes, and combo
(TCHAR*)_T("Ordinary Box"), //boxes. An important concept in Windows programming are
(TCHAR*)_T("Box Of Junk"), //parent - child relationships. In our previous programs
(TCHAR*)_T("Beer Crate"), //our main windows contained no child window controls.
(TCHAR*)_T("Wine Box"), //This program has quite a few. Note that the same
(TCHAR*)_T("Candy Box") //function call that creates a main program window, i.e.,
}; //CreateWindowEx(), is used to create child window
DWORD dwStyle=WS_CHILD|WS_VISIBLE|WS_TABSTOP; //controls. In fact, as you should have seen, one of the
HWND hCtl; //parameters of the CreateWindowEx() call is the handle
//of the parent window.
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance; //In WM_CREATE, lParam is a pointer to a CREATESTRUCT. Look up CREATESTRUCT!
hCtl=CreateWindow(_T("static"),_T("Choose Kind of Box"),WS_CHILD|WS_VISIBLE,10,10,140,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindow(_T("combobox"),_T(""),dwStyle|CBS_DROPDOWNLIST|WS_VSCROLL,150,10,140,100,Wea->hWnd,(HMENU)IDC_COMBO_TYPE,Wea->hIns,0);
for(unsigned int i=0; i<5; i++)
SendMessage(hCtl,CB_INSERTSTRING,(WPARAM)-1,(LPARAM)szBuffer[i]);
SendMessage(hCtl,CB_SETCURSEL,(WPARAM)0,0);
hCtl=CreateWindow(_T("static"),_T("Length of Box"),WS_CHILD|WS_VISIBLE,10,53,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Width of Box"),WS_CHILD|WS_VISIBLE,10,93,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,90,70,25,Wea->hWnd,(HMENU)IDC_EDIT_WIDTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Height of Box"),WS_CHILD|WS_VISIBLE,10,133,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,130,70,25,Wea->hWnd,(HMENU)IDC_EDIT_HEIGHT,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Volume"),WS_CHILD|WS_VISIBLE,10,183,70,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,80,180,300,25,Wea->hWnd,(HMENU)IDC_EDIT_VOLUME,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Calculate"),dwStyle,245,65,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_CALCULATE,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Display"),dwStyle,245,110,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_DISPLAY,Wea->hIns,0);
SetWindowText(Wea->hWnd,_T("Volume Of Your Box"));
SetFocus(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE));
return 0;
}
void btnCalculate_OnClick(lpWndEventArgs Wea)
{
TCHAR szBuffer[64],szText[64];
ProgramData ProDta;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight = _tstof(szBuffer);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight; //Calculate Volume
_stprintf(szBuffer,_T("%8.2f"),ProDta.dblVolume); //Write Volume back to string representation
_tcscpy(szText,_T("Your ")); //Build a string using C Runtime Stuff
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16); //Get user's selection out of combo box for box type
_tcscat(szText,ProDta.szBoxType); //Concatenate it into szText
_tcscat(szText,_T(" Has A Volume Of ")); //Keep building the string
_tcscat(szText,szBuffer); //Finally concatenate Volume into it at end.
SetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_VOLUME),szText); //Put it in the IDC_EDIT_VOLUME text box (edit control).
}
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
MessageBox(Wea->hWnd, _T("You've Clicked The Display Button!"), _T("Button Click Report"), MB_OK);
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_BUTTON_CALCULATE:
btnCalculate_OnClick(Wea);
break;
case IDC_BUTTON_DISPLAY:
btnDisplay_OnClick(Wea);
break;
}
return 0;
}
long fnWndProc_OnClose(lpWndEventArgs Wea) //When you click the x button Windows sends a WM_CLOSE message. If
{ //you return 0 you'll need to call DestroyWindow() because Windows
DestroyWindow(Wea->hWnd); //won't follow through with its own DestroyWindow() call in that case.
PostQuitMessage(0); //WM_CLOSE is really the place to ask the user if he/she wishes to
return 0; //close the app or window.
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea; //This procedure loops through the EVENTHANDER array
//of structs to try to make a match with the msg parameter
for(unsigned int i=0; i<dim(EventHandler); i++) //of the WndProc. If a match is made the event handling
{ //procedure is called through a function pointer -
if(EventHandler[i].iMsg==msg) //(EventHandler[i].fnPtr). If no match is found the
{ //msg is passed onto DefWindowProc().
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*EventHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form3");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof(WNDCLASSEX); wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=0;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,200,400,400,250,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{ //Is it not a Dialog Message?
if(!IsDialogMessage(hWnd,&messages)) //Added this because it does special character code translations that
{ //allows the use of the tab key to move focus among child window
TranslateMessage(&messages); //controls with the WS_TABSTOP style on the main Form.
DispatchMessage(&messages);
}
}
return messages.wParam;
}
//Header File For Above
//ProgEx40b.h
#ifndef PROGEX40_H
#define PROGEX40_H
#define IDC_COMBO_TYPE 1500
#define IDC_EDIT_LENGTH 1505
#define IDC_EDIT_WIDTH 1510
#define IDC_EDIT_HEIGHT 1515
#define IDC_EDIT_VOLUME 1520
#define IDC_BUTTON_CALCULATE 1525
#define IDC_BUTTON_DISPLAY 1530
#ifdef _UNICODE
#define _tstof _wtof
#else
#define _tstof atof
#endif
#define dim(x) (sizeof(x) / sizeof(x[0]))
typedef struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
}WndEventArgs, *lpWndEventArgs;
long fnWndProc_OnCreate (lpWndEventArgs Wea);
long fnWndProc_OnCommand (lpWndEventArgs Wea);
long fnWndProc_OnClose (lpWndEventArgs Wea);
struct EVENTHANDLER
{
unsigned int iMsg;
long (*fnPtr)(lpWndEventArgs);
};
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_CLOSE, fnWndProc_OnClose}
};
struct ProgramData
{
double dblLength;
double dblWidth;
double dblHeight;
double dblVolume;
TCHAR szBoxType[16];
};
long fnWndProc_OnCreate(lpWndEventArgs);
long fnWndProc_OnCommand(lpWndEventArgs);
long fnWndProc_OnClose(lpWndEventArgs);
void btnCalculate_OnClick(lpWndEventArgs);
void btnDisplay_OnClick(lpWndEventArgs);
LRESULT CALLBACK fnWndProc(HWND,unsigned int,WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int);
#endif
/*
ProgEx40c -- Form3: Volume Of Your Box. Creating An Output Screen And Adding
It To The Project.
I hope you realize this next step is a significant undertaking! Gone are the days
of Visual Basic where you just clicked a few menu items to add a new Form to a project.
Gone are the days of having to wade through one mysterious file after another filled
with MFC Wizard Code! Gone are the days of clicking on a .NET executable and having
to listen to the hard drive churn away forever it seemed as it loaded its massive
framework into memory with the same speed it seemed that an old time steam locomotive
pulled a coal train uphill. Real men do Sdk!!!
So we need to set up the internal plumbing here for another Form - our Output Screen.
So far we've created a Window Class for the main program down in WinMain() and the
name of that class was Form3. We also used other Window Classes created by Microsoft
that are part of the operating System itself for our child window controls such as
edit boxes and buttons.
What we have to do to create another Form/Window/Dialog in the program is follow
roughly the same steps we did in creating the main program window. We first have to
declare a variable of type WNDCLASSEX somewhere and fill in the fields that will
describe our new Window Class. Then we have to register the class with Windows as we
did with Form3. If you recall, one of the fields/members of the WNDCLASSEX struct is
the pointer member that holds the address of the Window Procedure for the class.
Yes, unfortunately, what we're doing here will require a second Window Procedure in
this program! Like I said, this is a major re-engineering of this little project.
And if we have another Window Procedure we're going to have to decide what messages
we want to handle for the functionality we want out of the new class because we have
to set up our function pointer array of message handling functions. And of course
we have to decide where all this additional code goes.
Since this has become something of a major undertaking lets just concentrate on geting
the wiring/plumbing part working first, and not worry too much in this ProgEx about
getting Box output on the new output form. Lets just try getting the new form added
and displayed. That part is a little more managable.
So, with all that to do, where should we begin? First thing is to register the new
Window Class. We have two choices of where we can do that as our app now stands. We
can do it in WinMain() where we registered our Form3 class, or we can do it in
fnWndProc_OnCreate() which is the WM_CREATE message handler for the main program
window Form3. My personal preference is to do as little as possible in my WinMain()
function leaving that function's purpose to just get my main program window up and
running and to establish the message pump. I prefer to begin my app customization work
in message handling routines, and the WM_CREATE message handler for the main form will
only execute once. And that is just enough for our purposes because a new Window Class
only needs to be registered once. So we'll add these two declarations to the variable
list in ProgEx40b's fnWndProc_OnCreate() function...
TCHAR szClassName[]="Output";
WNDCLASSEX wc;
and we'll fill out the new class definition and register it at bottom after all the
CreateWindow() calls that created our child window controls...
//Register Output Screen Class
wc.lpszClassName=szClassName, wc.lpfnWndProc=fnOutputProc;
wc.cbSize=sizeof (WNDCLASSEX), wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION), wc.hInstance=Wea->hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION), wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH), wc.cbWndExtra=4;
wc.lpszMenuName=NULL, wc.cbClsExtra=0;
RegisterClassEx(&wc);
What I've changed above with regard to how the WNDCLASSEX struct was filled out for
the Form3 Class is that I specified the hbrBackground field which takes a handle to
a brush ( HBRUSH - a GDI object ) as being white, i.e., WHITE_BRUSH. This is of course
a Windows define and the GetStockObject() Api function knows how to create or return
one for us. Also, I named the class "Output" and specified the lpfnWndProc name as
fnOutputProc. So if we don't define that function of Window Procedure type the
compiler won't compile this program. Finally, I added four bytes to the cbWndExtra
bytes. You saw me do that back in Form2. We stored pointers there. Please examine
the fnWndProc_OnCreate() code so you can see these changes.
Since I just mentioned that the program won't compile anymore with the addition of that
new class definition due to its naming a function that doesn't as yet exist, i.e.,
fnOutputProc, lets set out on rectifying that issue so that we at least have a compilable
program even if not a perfectly working one. And unfortunately, that raises another
issue - Where should we put it? We could add it to Main.cpp but I'll be honest. My
preference when creating programs with more than one window is to place the code for each
window/form/dialog in a seperate code file and have a seperate header file for equates,
function prototypes, and such needed by the new window. I then include these new files
in the existing project.
So to keep up with where I'm at with whatever IDE you are using, you'll want to go to
your project explorer and tell it you want to include more files in the project. I
named the additional files Output.cpp and Output.h. So for this ProgEx40c you'll need
Main.cpp and Output.cpp as your source files and ProgEx40c.h and Output.h as your header
files. You can either create these files as new blank files and paste the code into the
files from where you copied it, or if you have the files themselves you can create the
project named ProgEx40c and paste the files into the program directory, then include the
files into the project from the project explorer. This is usually done by right
clicking on the Project node in whatever IDE you are using.
OK, now for the details. In the new Window Procedure for our "Output" class the struct
variable I used to hold the pointers to the message handling function is named
OutputHandler, and in ProgEx40c.h you can see this additional code to relate the messages
to be handled with the addresses of the message handling functions...
long fnOutputProc_OnCreate (lpWndEventArgs Wea); //message handling function
long fnOutputProc_OnPaint (lpWndEventArgs Wea); //prototypes
long fnOutputProc_OnClose (lpWndEventArgs Wea);
const EVENTHANDLER OutputHandler[]=
{
{WM_CREATE, fnOutputProc_OnCreate}, //assign defines - addresses
{WM_PAINT, fnOutputProc_OnPaint}, //to EVENTHANDLER const array
{WM_CLOSE, fnOutputProc_OnClose}
};
The entirety of the code in Output.cpp are these four functions with which by this point
you should be familiar...
//Output.cpp
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "ProgEx40c.h"
#include "Output.h"
long fnOutputProc_OnCreate(lpWndEventArgs Wea)
{
return 0;
}
long fnOutputProc_OnPaint(lpWndEventArgs Wea)
{
PAINTSTRUCT ps; //Don't ever create a stubbed out WM_PAINT handler
HDC hDC; //with just a return 0; in it! If you do, Windows...
hDC=BeginPaint(Wea->hWnd,&ps); //...will punish you severely! Take my
EndPaint(Wea->hWnd,&ps); //word for it as I've been punished...
return 0; //...and still bear the scars!
}
long fnOutputProc_OnClose(lpWndEventArgs Wea)
{
DestroyWindow(Wea->hWnd);
return 0;
}
LRESULT CALLBACK fnOutputProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(OutputHandler); i++)
{
if(OutputHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*OutputHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
OK, that's the worst of it! Not a lot of code you havn't seen before, but just a
different way of organizing it that raises some tricky questions and issues I wanted to
address. You'll note in the code above that the procedures are mostly 'stubs'. As I
mentioned, on this first cut through this material of adding another form, I just wanted
to get the thing running any way I could. We'll worry about getting it to do what we
want once we can get a clean compile and run.
With these changes the program will compile and actually produce an instance of the
Output Screen when you click the 'Display' button on the main Form. In fact, it will
create as many instances of the Output Screen as you have patience to keep clicking the
'Display' button. But they'll all mterialize in the same location so you'll have to
drag them from on top of one another.
Which finally brings us to the Display procedure itself - btnDisplay_OnClick(). If you
recall in the last program we just had a MessageBox pop up when the button was clicked.
In this program I added the following code and removed the MessageBox()...
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
ProgramData ProDta;
char szBuffer[64];
HINSTANCE hIns;
HWND hWnd;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight;
hIns=GetModuleHandle(0);
hWnd=CreateWindow("Output","Output Screen",WS_OVERLAPPEDWINDOW,650,300,350,200,0,0,hIns,NULL);
ShowWindow(hWnd,SW_SHOWNORMAL);
}
So now you have the last piece of the puzzel of how to get a second form running in a
program. The top 60% of the code is just a rehash of btnCalculate_OnClick() which you've
already seen and which just does some calculations with data extracted from text boxes.
The bottom three lines shows the setup of the CreateWindow() call that displays our new
Output screen which we've struggled so mightily to create. By now I'd think you are
beginning to see the versatility and power of the programming paradigm unfolding before
your eyes involving the concepts of the Window Class and CreateWindow function calls. I've
decided to include a whole other program at the end of this thread to further illustrate
the concepts involved in getting multiple forms/windows running in a project, so watch out
for that if you are interested. I'll name that project Form4.
Compile and run this program to make sure you've got it working and understand the parts.
In the next example we'll try to get our Box data displayed on our new Output Screen.
Perhaps you might in the meanwhile give some thought as to how it might be managed given
that we've so far accomplished what we have without the use of one global variable, one
line of wizard code, or one .NET machine instruction. And the program is now up to 8704
bytes using CodeBlocks and compiling for small code - compiler switches -s -Os.
*/
//Main.cpp
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "ProgEx40c.h"
#include "Output.h"
long fnWndProc_OnCreate(lpWndEventArgs Wea)
{
TCHAR* szBuffer[]=
{
(TCHAR*)_T("Ordinary Box"),
(TCHAR*)_T("Box Of Junk"),
(TCHAR*)_T("Beer Crate"),
(TCHAR*)_T("Wine Box"),
(TCHAR*)_T("Candy Box")
};
DWORD dwStyle=WS_CHILD|WS_VISIBLE|WS_TABSTOP;
TCHAR szClassName[]=_T("Output");
WNDCLASSEX wc;
HWND hCtl;
//Create Child Window Controls On Main Form
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
hCtl=CreateWindow(_T("static"),_T("Choose Kind of Box"),WS_CHILD|WS_VISIBLE,10,10,140,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindow(_T("combobox"),_T(""),dwStyle|CBS_DROPDOWNLIST|WS_VSCROLL,150,10,140,100,Wea->hWnd,(HMENU)IDC_COMBO_TYPE,Wea->hIns,0);
for(unsigned int i=0; i<5; i++)
SendMessage(hCtl,CB_INSERTSTRING,(WPARAM)-1,(LPARAM)szBuffer[i]);
SendMessage(hCtl,CB_SETCURSEL,(WPARAM)0,0); //|WS_TABSTOP
hCtl=CreateWindow(_T("static"),_T("Length of Box"),WS_CHILD|WS_VISIBLE,10,53,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Width of Box"),WS_CHILD|WS_VISIBLE,10,93,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,90,70,25,Wea->hWnd,(HMENU)IDC_EDIT_WIDTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Height of Box"),WS_CHILD|WS_VISIBLE,10,133,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,130,70,25,Wea->hWnd,(HMENU)IDC_EDIT_HEIGHT,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Volume"),WS_CHILD|WS_VISIBLE,10,183,50,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,60,180,335,25,Wea->hWnd,(HMENU)IDC_EDIT_VOLUME,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Calculate"),dwStyle,245,65,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_CALCULATE,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Display"),dwStyle,245,110,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_DISPLAY,Wea->hIns,0);
SetWindowText(Wea->hWnd,_T("Volume Of Your Box"));
SetFocus(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE));
//Register Output Screen Class
wc.lpszClassName=szClassName, wc.lpfnWndProc=fnOutputProc;
wc.cbSize=sizeof (WNDCLASSEX), wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION), wc.hInstance=Wea->hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION), wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH), wc.cbWndExtra=4;
wc.lpszMenuName=NULL, wc.cbClsExtra=0;
RegisterClassEx(&wc);
return 0;
}
void btnCalculate_OnClick(lpWndEventArgs Wea)
{
TCHAR szBuffer[64],szText[64];
ProgramData ProDta;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight = _tstof(szBuffer);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight; //Calculate Volume
_stprintf(szBuffer,_T("%8.2f"),ProDta.dblVolume); //Write Volume back to string representation
_tcscpy(szText,_T("Your ")); //Build a string using C Runtime Stuff
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16); //Get user's selection out of combo box for box type
_tcscat(szText,ProDta.szBoxType); //Concatenate it into szText
_tcscat(szText,_T(" Has A Volume Of ")); //Keep building the string
_tcscat(szText,szBuffer); //Finally concatenate Volume into it at end.
SetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_VOLUME),szText); //Put it in the IDC_EDIT_VOLUME text box (edit control).
}
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
ProgramData ProDta; //This button click procedure will create an instance of our newly created 'Output'
TCHAR szBuffer[64]; //class. We registered the class up in our WM_CREATE handler, and we are creating
HINSTANCE hIns; //an instance of it just below through a CreateWindow() Api call.
HWND hWnd;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight;
hIns=GetModuleHandle(0);
hWnd=CreateWindow //That _tstof() stuff just above and right is a bit strange. In my CodeBlocks
( //10.05 with MinGW they don't seem to have a function defined in the tchar.h
_T("Output"), //macros to convert a floating point number to a double. I found two in my
_T("Output Screen"), //Visual Studio Professional 2008 installation, but none in the MinGW. So in
WS_OVERLAPPEDWINDOW, //ProgEx40b and ProgEx40c I added them. Just thought I'd give you a 'heads up'
650, //on that. Looking just left you can see the CreateWindow() call that will
300, //create an instance of the "Output" class
350,
200,
0,
0,
hIns,
NULL
);
ShowWindow(hWnd,SW_SHOWNORMAL);
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_BUTTON_CALCULATE:
btnCalculate_OnClick(Wea);
break;
case IDC_BUTTON_DISPLAY:
btnDisplay_OnClick(Wea);
break;
}
return 0;
}
long fnWndProc_OnClose(lpWndEventArgs Wea)
{
DestroyWindow(Wea->hWnd);
PostQuitMessage(0);
return 0;
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(MainHandler); i++)
{
if(MainHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*MainHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form3");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX); wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=4;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,100,400,410,250,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{ //Is it not a Dialog Message?
if(!IsDialogMessage(hWnd,&messages)) //Added this because it does special character code translations that
{ //allows the use of the tab key to move focus among child window
TranslateMessage(&messages); //controls with the WS_TABSTOP style on the main Form.
DispatchMessage(&messages);
}
}
return messages.wParam;
}
//ProgEx40c.h
#ifndef PROGEX40C_H
#define PROGEX40C_H
#define IDC_COMBO_TYPE 1500
#define IDC_EDIT_LENGTH 1505
#define IDC_EDIT_WIDTH 1510
#define IDC_EDIT_HEIGHT 1515
#define IDC_EDIT_VOLUME 1520
#define IDC_BUTTON_CALCULATE 1525
#define IDC_BUTTON_DISPLAY 1530
#ifdef _UNICODE
#define _tstof _wtof
#else
#define _tstof atof
#endif
#define dim(x) (sizeof(x) / sizeof(x[0]))
typedef struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
}WndEventArgs, *lpWndEventArgs;
struct EVENTHANDLER
{
unsigned int iMsg;
long (*fnPtr)(lpWndEventArgs);
};
long fnWndProc_OnCreate (lpWndEventArgs);
long fnWndProc_OnCommand (lpWndEventArgs);
long fnWndProc_OnClose (lpWndEventArgs);
const EVENTHANDLER MainHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_CLOSE, fnWndProc_OnClose}
};
long fnOutputProc_OnCreate (lpWndEventArgs Wea);
long fnOutputProc_OnPaint (lpWndEventArgs Wea);
long fnOutputProc_OnClose (lpWndEventArgs Wea);
const EVENTHANDLER OutputHandler[]=
{
{WM_CREATE, fnOutputProc_OnCreate},
{WM_PAINT, fnOutputProc_OnPaint},
{WM_CLOSE, fnOutputProc_OnClose}
};
struct ProgramData
{
double dblLength;
double dblWidth;
double dblHeight;
double dblVolume;
TCHAR szBoxType[16];
};
void btnCalculate_OnClick (lpWndEventArgs);
void btnDisplay_OnClick (lpWndEventArgs);
LRESULT CALLBACK fnWndProc (HWND, unsigned int, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
LRESULT CALLBACK fnOutputProc (HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam);
#endif
//Output.cpp
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "ProgEx40c.h"
#include "Output.h"
long fnOutputProc_OnCreate(lpWndEventArgs Wea)
{
return 0;
}
long fnOutputProc_OnPaint(lpWndEventArgs Wea)
{
PAINTSTRUCT ps; //Don't ever create a stubbed out WM_PAINT handler
HDC hDC; //with just a return 0; in it! If you do, Windows...
hDC=BeginPaint(Wea->hWnd,&ps); //...will punish you severely! Take my
EndPaint(Wea->hWnd,&ps); //word for it as I've been punished...
return 0; //...and still bear the scars!
}
long fnOutputProc_OnClose(lpWndEventArgs Wea)
{
DestroyWindow(Wea->hWnd);
return 0;
}
LRESULT CALLBACK fnOutputProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(OutputHandler); i++)
{
if(OutputHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*OutputHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
//Output.h
#ifndef OUTPUT_H
#define OUTPUT_H
long fnOutputProc_OnCreate(lpWndEventArgs);
long fnOutputProc_OnPaint(lpWndEventArgs);
long fnOutputProc_OnClose(lpWndEventArgs);
LRESULT CALLBACK fnOutputProc(HWND, unsigned int, WPARAM, LPARAM);
#endif
Here is a little project that shows how to create additional forms/windows in addition to your main program window. You can create three types of additional windows. The first kind is a modal window that inactivates the main program window until it is dismissed. The second type is like the first, but instead of making the main program window inactive - it makes it invisible until dismissed. The third type of window performs neither of these two feats, and in fact you can create as many of them as you like....
//Main.cpp //Program displays a main Form/Window/Dialog With Three Buttons on it
#include <windows.h> //to simulate a program started from a main Form with three modules/
#include <tchar.h> //options/modalities within which it operates. When you click the top
#include <stdio.h> //button a new Form/Window/Dialog is created with CreateWindow() of
#include "Main.h" //the "Form1" Window Class, and in the WM_CREATE handler of this new
#include "Form1.h" //window it disables the main form with the three buttons and therefore
#include "Form2.h" //is an example of a modal dialog. When you dismiss this modal dialog
#include "Form3.h" //and click on Button/Option #2 on the Main Form, a CreateWindow() call
//creates a window of "Form2" Window Class, and in the WM_CREATE handler
long fnWndProc_OnCreate(lpWndEventArgs Wea) //for this window it hides or makes invisible the main
{ //window. After dismssing this window you'll find that
DWORD dwStyle=WS_CHILD|WS_VISIBLE; //you can click on the Option #3 button as many times as
TCHAR szClassName[16]; //you like because the window-form-dialog it creates neither
WNDCLASSEX wc; //disables nor makes invisible the main window. Further
HWND hCtl; //note that these Option #3 windows can be interacted with
//irregardless of what ever is going on with the other windows.
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
hCtl=CreateWindow(_T("button"),_T("Option #1"),dwStyle,65,15,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_FORM1,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Option #2"),dwStyle,65,55,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_FORM2,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Option #3"),dwStyle,65,95,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_FORM3,Wea->hIns,0);
//Register Window Classes For Form1, Form2 and Form3
wc.cbSize=sizeof(WNDCLASSEX), wc.style=CS_HREDRAW | CS_VREDRAW;
wc.cbClsExtra=0, wc.cbWndExtra=0;
wc.hInstance=Wea->hIns, wc.hIcon=LoadIcon(NULL, IDI_APPLICATION);
wc.hIconSm=0, wc.hCursor=LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH), wc.lpszMenuName=NULL;
_tcscpy(szClassName,_T("Form1")), wc.lpszClassName=szClassName;
wc.lpfnWndProc=fnForm1_WndProc;
RegisterClassEx(&wc);
_tcscpy(szClassName,_T("Form2"));
wc.lpfnWndProc=fnForm2_WndProc;
wc.lpszClassName=szClassName; //Note that a WM_CREATE call is akin to a constructor call in typical
RegisterClassEx(&wc); //C++ class architecture. When you receive this call/message Windows
//has finished doing what it needs to support the Window object, and
_tcscpy(szClassName,_T("Form3")); //is 'passing the ball' to you. In my apps with multiple windows I
wc.lpszClassName=szClassName; //typically use the WM_CREATE handler to register any window classes
wc.lpfnWndProc=fnForm3_WndProc; //I need in the app, so that I can make CreateWindow() calls when I
RegisterClassEx(&wc); //need to instantiate a window of some type.
return 0;
}
long btnForm1_Click(lpWndEventArgs Wea)
{
HWND hWnd;
hWnd=CreateWindow(_T("Form1"),_T("Form1"),WS_OVERLAPPEDWINDOW,50,25,310,185,Wea->hWnd,(HMENU)0,GetModuleHandle(0),Wea->hWnd);
ShowWindow(hWnd,SW_SHOWNORMAL);
UpdateWindow(hWnd);
return 0;
}
long btnForm2_Click(lpWndEventArgs Wea)
{
HWND hWnd;
hWnd=CreateWindow(_T("Form2"),_T("Form2"),WS_OVERLAPPEDWINDOW,200,250,310,185,Wea->hWnd,(HMENU)0,GetModuleHandle(0),Wea->hWnd);
ShowWindow(hWnd,SW_SHOWNORMAL);
UpdateWindow(hWnd);
return 0;
}
long btnForm3_Click(lpWndEventArgs Wea)
{
HWND hWnd;
hWnd=CreateWindow(_T("Form3"),_T("Form3"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,300,260,0,(HMENU)0,GetModuleHandle(0),Wea->hWnd);
ShowWindow(hWnd,SW_SHOWNORMAL);
UpdateWindow(hWnd);
return 0;
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_BUTTON_FORM1:
return btnForm1_Click(Wea);
case IDC_BUTTON_FORM2:
return btnForm2_Click(Wea);
case IDC_BUTTON_FORM3:
return btnForm3_Click(Wea);
}
return 0;
}
long fnWndProc_OnClose(lpWndEventArgs Wea) //Search And Destroy Mission For Any Form3
{ //Windows Hanging Around.
HWND hForm;
do
{
hForm=FindWindow(_T("Form3"),_T("Form3"));
if(hForm)
SendMessage(hForm,WM_CLOSE,0,0);
else
break;
}while(TRUE);
DestroyWindow(Wea->hWnd);
PostQuitMessage(0);
return 0;
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(EventHandler); i++)
{
if(EventHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*EventHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Multiple Forms");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX); wc.style=CS_DBLCLKS;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=0;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,250,500,260,170,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
//Main.h
#ifndef MAIN_H
#define MAIN_H
#define dim(x) (sizeof(x) / sizeof(x[0]))
#define IDC_BUTTON_FORM1 1600
#define IDC_BUTTON_FORM2 1605
#define IDC_BUTTON_FORM3 1610
typedef struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
}WndEventArgs, *lpWndEventArgs;
struct EVENTHANDLER
{
unsigned int iMsg;
long (*fnPtr)(lpWndEventArgs);
};
long fnWndProc_OnCreate (lpWndEventArgs Wea); //We need foreward declarations of
long fnWndProc_OnCommand (lpWndEventArgs Wea); //the various functions in a main
long fnWndProc_OnClose (lpWndEventArgs Wea); //header file so that the event
//handlers can be attached below.
long fnForm1_OnCreate (lpWndEventArgs);
long fnForm1_OnPaint (lpWndEventArgs);
long fnForm1_OnClose (lpWndEventArgs);
long fnForm2_OnCreate (lpWndEventArgs);
long fnForm2_OnPaint (lpWndEventArgs);
long fnForm2_OnClose (lpWndEventArgs);
long fnForm3_OnCreate (lpWndEventArgs);
long fnForm3_OnPaint (lpWndEventArgs);
long fnForm3_OnClose (lpWndEventArgs);
const EVENTHANDLER EventHandler[]= //Since we foreward declared above
{ //the various event handling functions
{WM_CREATE, fnWndProc_OnCreate}, //of the various forms, windows, dialogs
{WM_COMMAND, fnWndProc_OnCommand}, //above, we can fill out the fields of
{WM_CLOSE, fnWndProc_OnClose} //our EVENTHANDLER structures for the
}; //various objects.
const EVENTHANDLER Form1EventHandler[]=
{
{WM_CREATE, fnForm1_OnCreate},
{WM_PAINT, fnForm1_OnPaint},
{WM_CLOSE, fnForm1_OnClose}
};
const EVENTHANDLER Form2EventHandler[]=
{
{WM_CREATE, fnForm2_OnCreate},
{WM_PAINT, fnForm2_OnPaint},
{WM_CLOSE, fnForm2_OnClose}
};
const EVENTHANDLER Form3EventHandler[]=
{
{WM_PAINT, fnForm3_OnPaint},
{WM_CLOSE, fnForm3_OnClose}
};
#endif
//Form1.cpp
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>
#include "Form1.h"
#include "Main.h"
long fnForm1_OnCreate(lpWndEventArgs Wea)
{
CREATESTRUCT *pCreateStruct;
HWND hMain;
pCreateStruct=(CREATESTRUCT*)Wea->lParam;
hMain=(HWND)pCreateStruct->lpCreateParams;
SetWindowLongPtr(Wea->hWnd,GWLP_USERDATA,(LONG_PTR)hMain);
EnableWindow(hMain,FALSE);
return 0;
}
long fnForm1_OnPaint(lpWndEventArgs Wea)
{
PAINTSTRUCT ps;
HDC hDC;
hDC=BeginPaint(Wea->hWnd,&ps);
TextOut(hDC,0,0,_T("This Is Form1. It Disables The Main"),36);
TextOut(hDC,0,16,_T("Window, And That Makes It Modal. Note"),38);
TextOut(hDC,0,32,_T("That We Passed The Handle Of The Main"),37);
TextOut(hDC,0,48,_T("Window In The Last Parameter Of The"),35);
TextOut(hDC,0,64,_T("CreateWindow() Call, And Retrieved It In"),40);
TextOut(hDC,0,80,_T("fnForm1_OnCreate(). We Then Stored It So"),41);
TextOut(hDC,0,96,_T("We Could EnableWindow(TRUE) The Main"),36);
TextOut(hDC,0,112,_T("Window When This Modal Form Is"),30);
TextOut(hDC,0,128,_T("Dismissed."),10);
EndPaint(Wea->hWnd,&ps);
return 0;
}
long fnForm1_OnClose(lpWndEventArgs Wea)
{
HWND hMain;
hMain=(HWND)GetWindowLongPtr(Wea->hWnd,GWLP_USERDATA);
EnableWindow(hMain,TRUE);
DestroyWindow(Wea->hWnd);
return 0;
}
LRESULT CALLBACK fnForm1_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(Form1EventHandler); i++)
{
if(Form1EventHandler[i].iMsg==msg)
{
Wea.hWnd=hWnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*Form1EventHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hWnd, msg, wParam, lParam));
}
//Form1.h //Needs to be included in Main.cpp because the WM_CREATE handler there
#ifndef FORM1_H //references fnForm1_WndProc as the Window Procedure for the Form1 Class.
#define FORM1_H //This would be needed to register the class.
LRESULT CALLBACK fnForm1_WndProc(HWND, unsigned int, WPARAM, LPARAM);
#endif
//Form2.cpp
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>
#include "Main.h"
#include "Form2.h"
long fnForm2_OnCreate(lpWndEventArgs Wea)
{
CREATESTRUCT *pCreateStruct;
HWND hMain;
pCreateStruct=(CREATESTRUCT*)Wea->lParam;
hMain=(HWND)pCreateStruct->lpCreateParams;
SetWindowLongPtr(Wea->hWnd,GWLP_USERDATA,(LONG_PTR)hMain);
ShowWindow(hMain,SW_HIDE);
return 0;
}
long fnForm2_OnPaint(lpWndEventArgs Wea)
{
PAINTSTRUCT ps;
HDC hDC;
hDC=BeginPaint(Wea->hWnd,&ps);
TextOut(hDC,0,0,_T("This Is Form2. It SW_HIDEs The Main"),36);
TextOut(hDC,0,16,_T("Window, And SW_SHOWs It Upon Closing."),37);
TextOut(hDC,0,32,_T("This Technique Can Be Used Similiarly"),37);
TextOut(hDC,0,48,_T("To A Modal Dialog If It Isn't Necessary To"),42);
TextOut(hDC,0,64,_T("View Simultaneously A Form Underneath The"),41);
TextOut(hDC,0,80,_T("Dialog With Which You Can't Interact"),36);
TextOut(hDC,0,96,_T("Anyway"),6);
EndPaint(Wea->hWnd,&ps);
return 0;
}
long fnForm2_OnClose(lpWndEventArgs Wea)
{
HWND hMain;
hMain=(HWND)GetWindowLongPtr(Wea->hWnd,GWLP_USERDATA);
EnableWindow(hMain,TRUE);
DestroyWindow(Wea->hWnd);
ShowWindow(hMain,TRUE);
return 0;
}
LRESULT CALLBACK fnForm2_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(Form2EventHandler); i++)
{
if(Form2EventHandler[i].iMsg==msg)
{
Wea.hWnd=hWnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*Form2EventHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hWnd, msg, wParam, lParam));
}
//Form2.h //Needs to be included in Main.cpp because the WM_CREATE handler there
#ifndef FORM2_H //references fnForm2_WndProc as the Window Procedure for the Form1 Class.
#define FORM2_H //This would be needed to register the class.
LRESULT CALLBACK fnForm2_WndProc(HWND, unsigned int, WPARAM, LPARAM);
#endif
//Form3.cpp
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>
#include "Main.h"
#include "Form3.h"
long fnForm3_OnPaint(lpWndEventArgs Wea)
{
PAINTSTRUCT ps;
HDC hDC;
hDC=BeginPaint(Wea->hWnd,&ps);
TextOut(hDC,0,0,_T("This Is Form3. Not Only Does It Neither"),40);
TextOut(hDC,0,16,_T("Hide Nor Disable The Main Window, But"),37);
TextOut(hDC,0,32,_T("You'll Find That You Can Create As Many"),39);
TextOut(hDC,0,48,_T("Of These As You Like By Continually"),35);
TextOut(hDC,0,64,_T("Clicking The Bottom Button On The Main"),38);
TextOut(hDC,0,80,_T("Form. However, You'll Have To Drag One"),39);
TextOut(hDC,0,96,_T("From On Top Of The Other Because They"),37);
TextOut(hDC,0,112,_T("All Appear In The Same Location (I"),34);
TextOut(hDC,0,128,_T("Changed That). You May Further Note"),36);
TextOut(hDC,0,144,_T("That Since These Windows Are Neither"),36);
TextOut(hDC,0,160,_T("Disabled Nor Hidden At Any Time, You"),36);
TextOut(hDC,0,176,_T("May Interact With Them Irregardless Of"),38);
TextOut(hDC,0,192,_T("The State Of Form1 Or Form2. Pretty"),35);
TextOut(hDC,0,208,_T("Neat, Don't You Think?"),22);
EndPaint(Wea->hWnd,&ps);
return 0;
}
long fnForm3_OnClose(lpWndEventArgs Wea)
{
HWND hMain;
MessageBox
(
Wea->hWnd,
_T("Good Way To Release Any Resources, Memory, etc., You May Have Allocated"),
_T("Window Close Report!"),
MB_OK
);
hMain=(HWND)GetWindowLongPtr(Wea->hWnd,GWLP_USERDATA);
EnableWindow(hMain,TRUE);
DestroyWindow(Wea->hWnd);
ShowWindow(hMain,TRUE);
return 0;
}
LRESULT CALLBACK fnForm3_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(Form3EventHandler); i++)
{
if(Form3EventHandler[i].iMsg==msg)
{
Wea.hWnd=hWnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*Form3EventHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hWnd, msg, wParam, lParam));
}
//Form3.h //Needs to be included in Main.cpp because the WM_CREATE handler there
#ifndef FORM3_H //references fnForm3_WndProc as the Window Procedure for the Form1 Class.
#define FORM3_H //This would be needed to register the class.
LRESULT CALLBACK fnForm3_WndProc(HWND, unsigned int, WPARAM, LPARAM);
#endif
/*
ProgEx40d -- Form3: Volume Of A Box. Getting Box Data To The Output Screen
And Displaying It.
Have you given some thought as to how we're going to get the Box data calculations displayed to the
output screen? And don't forget! No globals! If we had a handle to the main program window we
would be able to get the data out of the text boxes the way we did in btnCalculate_OnClick(). But
we don't have any such here in the Output Screen Window.
Remember something I said about the last parameter of the CreateWindow() call back in ProgEx40 - the
1st program in this series? Here it is again to refresh your memory...
The last parameter - the 11th, we just assigned NULL. We didn't use it. However its
typed like so...
LPVOID lpParam // pointer to window-creation data
This is a pointer parameter and it can be a pointer to anything whatsoever you have a
mind to pass to the WM_CREATE handler of your Window Procedure. Its another global
killer is what it is. I'm not going to say anymore about it here because we're not
using it in this first version of this app. Just tuck away for future reference that
its very important (and we'll be using it soon).
--End Quote--
Look for a minute back in btnDisplay_OnClick() of the last example. Even better, here it is...
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
ProgramData ProDta;
char szBuffer[64];
HINSTANCE hIns;
HWND hWnd;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight;
hIns=GetModuleHandle(0);
hWnd=CreateWindow("Output","Output Screen",WS_OVERLAPPEDWINDOW,650,300,350,200,0,0,hIns,NULL);
ShowWindow(hWnd,SW_SHOWNORMAL);
}
Note that we have a struct of ProgramData type declared and its instance ProDta is holding all the
information we would like to display in the Output Screen. Also note that there is a big fat useless
NULL sitting in that last Window Creation Data pointer discussed above. If that big fat useless NULL
is getting a free ride over to fnOutputProc_OnCreate() I see no reason we shouldn't give the address
of that ProgramData instance its place instead. The NULL is doing us no good and that ProgramData
instance has every last bit of information we need. Its a no brainer. So all we need to do is attach
an ampersand '&' operator in front of the ProDta instance and its there! And no need for global
variables! Isn't that better?
Something else I've mentioned before but I'll elaborate just a bit. When that CreateWindow() call
executes obviously code inside Windows will be running and somewhere in that process a WM_CREATE
message will show up in the Window Procedure of the class passed as the szClassName parameter of the
CreateWindow() call. The CreateWindow() call itself will not return until the WM_CREATE message has
been completely processed. Momentarily we will be extracting that ProgramData pointer from the
CREATESTRUCT we'll have access to in fnOutputProc_OnCreate(), but during the time our code is running
in our OnCreate() handler, we'll also be able to pass any data we would like back through the
CreateWindow() call to the point of that call in btnDisplay_OnClick(). Because, don't forget, when we
do finally get access to the ProgramData ProDta object, its actually allocated on the stack back in
btnDisplay_OnClick(). Its not some kind of copy being passed to us. To give a more concrete example,
had we not calculated the volume of the box back in btnDisplay_OnClick, and had only passed the length,
width, and height, the volume could have been calculated and stored through the pointer received in
fnOutputProc_OnCreate, and when the CreateWindow() call would have returned, ProDta.dblVolume would
have been calculated. I'd recommend you think about that.
Moving on to fnOutputProc_OnCreate, all we need to do to get our box data is dereference the lParam
pointer passed to us, cast it to a ProgramData*, and retrieve our info. Here is fnOutputProc_OnCreate...
long fnOutputProc_OnCreate(lpWndEventArgs Wea)
{
CREATESTRUCT *pCreateStruct;
ProgramData* pProgramData;
ProgramData* pMain;
pCreateStruct=(CREATESTRUCT*)Wea->lParam;
pMain=(ProgramData*)pCreateStruct->lpCreateParams;
pProgramData=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
if(pProgramData)
{
SetWindowLong(Wea->hWnd,0,(long)pProgramData);
pProgramData->dblLength=pMain->dblLength;
pProgramData->dblWidth=pMain->dblWidth;
pProgramData->dblHeight=pMain->dblHeight;
pProgramData->dblVolume=pMain->dblVolume;
strcpy(pProgramData->szBoxType,pMain->szBoxType);
}
else //If we can't allocate the memory there isn't much use
return -1; //continuing so -1 causes CreateWindow to fail and there
//won't be any output screen.
return 0;
}
However, here is where one must think things over seriously! What we've got is a program that calculates
something - in our case the volumes of silly boxes, but don't forget that there is a Display button that
allows the user to display the calculations for many boxes one after the other; certainly more than one;
perhaps as many as could fill the computer screen and then some yet minimized! The user might simply
want to compare the dimensions and volumes of a number of boxes at once. Related to this issue is that
here in the fnOutputProc_OnCreate() handler we're in no position to even display this data even though
we've now got it because windows are not yet visible during their WM_CREATE message. Windows don't in
fact become visible until several messages later among them WM_SIZE and WM_PAINT. And long before the
window becomes visible our fnOutputProc_OnCreate() will return; the CreateWindow() call back in
btnDisplay_OnClick() will return; and that procedure itself will exit and that ProgramData object on the
stack will be gone! Some apples huh!
All this points up to the fact that in fnOutputProc_OnCreate() when we momentarily, for perhaps 1/10000 th
second, obtain that pointer to a ProgramData object, we had better store our own copy of it as fast as we
can. And we're going to have to store it in such a way that our window will have access to it when it
receives a WM_PAINT message from Windows. Remember what we did in our various versions of Form2 where we
stored then displayed mouse movements, window resizing, typed characters, etc? We received this infor-
mation from Windows in one message, stored it for awhile, then TextOut'ed it during the WM_PAINT we forced.
We need to do the same thing here except that we don't need to do any InvalidateRect() to force repaints.
We'll get a WM_PAINT when the ShowWindow() call right beneath the CreateWindow() in btnDisplay_OnClick()
executes.
The only other issue is that we're going to have to allocate memory dynamically with some memory alloc-
ation function or other to store the data because all we have at the point of reception of the
CREATSTRUCT data is a pointer that will very soon be invalid. Look up in the code just posted and you
can see what I've done. I allocated two ProgramData pointers in the procedure; one for the incoming
CREATSTRUCT data - pMain; and another to reference my memory allocation - pProgramData. Then I copy the
data from Form3 into my allocated memory and store the pointer at offset zero in the four bytes of
cbWndExtra bytes I requested of Windows when we registered the "Output" Window Class.
Its truely amazing how complicated even a program to calculate the volume of a box can become isn't it?
Well, I truely believe the worst is over because at this point all that's left to do is retrieve the data
stored at our pointer in the WM_PAINT handler and TextOut() some data; the only real wrinkle being that
with default non-fixed width fonts our numbers don't line up! So take a look at fnOutputProc_OnPaint()
and you'll see I had to use a truely ugly Api function named CreateFont() to create a suitable fixed width
font for text drawing purposes. If you are new to this its also rather ugly because Once you create any
GDI ( Graphics Device Interface ) object you need to first select it into the device context, save what
comes out of the device context when your new stuff goes in, then when you're done with the whole thing
select the old value back in the device context, then delete the GDI object you created. I guess this is
why we lose so many good folks to MFC, .NET, etc. Anyway, you can see the TextOut() calls that produce
the output.
Finally, all that's left to do is write a WM_CLOSE handler to destroy the window when the user [x]'s out
and release the memory allocation done in the WM_CREATE handler. But will the user [X] out??? What if
he/she/it makes five or six Box output Windows and doesn't close any of them and just [x]'s out of the
whole application of Form3 leaving the Output Windows hanging? If you try it you'll see that when you
close the main Form any remaining Output Windows go with it. But will WM_CLOSE calls come to do the
memory cleanup? I don't believe so. Perhaps WM_DESTROY messages will come through but I don't believe
WM_CLOSE will. To take care of that scenerio back in Form3's WM_CLOSE I run a do loop with a FindWindowEx()
call in it that loops searching the system for any living "Output" class windows and kills them.
With CodeBlocks we're up to 10,240 bytes!
*/
//Main.cpp
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "ProgEx40d.h"
#include "Output.h"
long fnWndProc_OnCreate(lpWndEventArgs Wea)
{
TCHAR* szBuffer[]=
{
(TCHAR*)_T("Ordinary Box"),
(TCHAR*)_T("Box Of Junk"),
(TCHAR*)_T("Beer Crate"),
(TCHAR*)_T("Wine Box"),
(TCHAR*)_T("Candy Box")
};
DWORD dwStyle=WS_CHILD|WS_VISIBLE|WS_TABSTOP;
TCHAR szClassName[]=_T("Output");
WNDCLASSEX wc;
HWND hCtl;
//Create Child Window Controls On Main Form
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
hCtl=CreateWindow(_T("static"),_T("Choose Kind of Box"),WS_CHILD|WS_VISIBLE,10,10,140,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindow(_T("combobox"),_T(""),dwStyle|CBS_DROPDOWNLIST|WS_VSCROLL,150,10,140,100,Wea->hWnd,(HMENU)IDC_COMBO_TYPE,Wea->hIns,0);
for(unsigned int i=0; i<5; i++)
SendMessage(hCtl,CB_INSERTSTRING,(WPARAM)-1,(LPARAM)szBuffer[i]);
SendMessage(hCtl,CB_SETCURSEL,(WPARAM)0,0); //|WS_TABSTOP
hCtl=CreateWindow(_T("static"),_T("Length of Box"),WS_CHILD|WS_VISIBLE,10,53,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Width of Box"),WS_CHILD|WS_VISIBLE,10,93,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,90,70,25,Wea->hWnd,(HMENU)IDC_EDIT_WIDTH,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Height of Box"),WS_CHILD|WS_VISIBLE,10,133,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,130,70,25,Wea->hWnd,(HMENU)IDC_EDIT_HEIGHT,Wea->hIns,0);
hCtl=CreateWindow(_T("static"),_T("Volume"),WS_CHILD|WS_VISIBLE,10,183,50,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,60,180,335,25,Wea->hWnd,(HMENU)IDC_EDIT_VOLUME,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Calculate"),dwStyle,245,65,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_CALCULATE,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Display"),dwStyle,245,110,120,30,Wea->hWnd,(HMENU)IDC_BUTTON_DISPLAY,Wea->hIns,0);
SetWindowText(Wea->hWnd,_T("Volume Of Your Box"));
SetFocus(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE));
//Register Output Screen Class
wc.lpszClassName=szClassName, wc.lpfnWndProc=fnOutputProc;
wc.cbSize=sizeof (WNDCLASSEX), wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION), wc.hInstance=Wea->hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION), wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH), wc.cbWndExtra=4;
wc.lpszMenuName=NULL, wc.cbClsExtra=0;
RegisterClassEx(&wc);
return 0;
}
void btnCalculate_OnClick(lpWndEventArgs Wea)
{
TCHAR szBuffer[64],szText[64];
ProgramData ProDta;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight = _tstof(szBuffer);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight; //Calculate Volume
_stprintf(szBuffer,_T("%8.2f"),ProDta.dblVolume); //Write Volume back to string representation
_tcscpy(szText,_T("Your ")); //Build a string using C Runtime Stuff
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16); //Get user's selection out of combo box for box type
_tcscat(szText,ProDta.szBoxType); //Concatenate it into szText
_tcscat(szText,_T(" Has A Volume Of ")); //Keep building the string
_tcscat(szText,szBuffer); //Finally concatenate Volume into it at end.
SetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_VOLUME),szText); //Put it in the IDC_EDIT_VOLUME text box (edit control).
}
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
ProgramData ProDta; //This button click procedure will create an instance of our newly created 'Output'
TCHAR szBuffer[64]; //class. We registered the class up in our WM_CREATE handler, and we are creating
HINSTANCE hIns; //an instance of it just below through a CreateWindow() Api call.
HWND hWnd;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight;
hIns=GetModuleHandle(0);
hWnd=CreateWindow(_T("Output"),_T("Output Screen"),WS_OVERLAPPEDWINDOW,650,300,360,210,0,0,hIns,&ProDta);
ShowWindow(hWnd,SW_SHOWNORMAL);
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_BUTTON_CALCULATE:
btnCalculate_OnClick(Wea);
break;
case IDC_BUTTON_DISPLAY:
btnDisplay_OnClick(Wea);
break;
}
return 0;
}
long fnWndProc_OnClose(lpWndEventArgs Wea)
{
HWND hOutput;
do //Search And Destroy Mission For Any Output Windows Hanging Around
{
hOutput=FindWindowEx(0,0,_T("Output"),_T("Output Screen"));
if(hOutput)
SendMessage(hOutput,WM_CLOSE,0,0);
else
break;
}while(TRUE);
DestroyWindow(Wea->hWnd);
PostQuitMessage(0);
return 0;
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<3; i++)
{
if(MainHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*MainHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form3");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX); wc.style=CS_DBLCLKS;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=4;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,100,400,425,260,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{ //Is it not a Dialog Message?
if(!IsDialogMessage(hWnd,&messages)) //Added this because it does special character code translations that
{ //allows the use of the tab key to move focus among child window
TranslateMessage(&messages); //controls with the WS_TABSTOP style on the main Form.
DispatchMessage(&messages);
}
}
return messages.wParam;
}
//ProgEx40d.h
#ifndef PROGEX40D_H
#define PROGEX40D_H
#define IDC_COMBO_TYPE 1500
#define IDC_EDIT_LENGTH 1505
#define IDC_EDIT_WIDTH 1510
#define IDC_EDIT_HEIGHT 1515
#define IDC_EDIT_VOLUME 1520
#define IDC_BUTTON_CALCULATE 1525
#define IDC_BUTTON_DISPLAY 1530
#ifdef _UNICODE
#define _tstof _wtof
#else
#define _tstof atof
#endif
#define dim(x) (sizeof(x) / sizeof(x[0]))
typedef struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
}WndEventArgs, *lpWndEventArgs;
struct EVENTHANDLER
{
unsigned int iMsg;
long (*fnPtr)(lpWndEventArgs);
};
long fnWndProc_OnCreate (lpWndEventArgs);
long fnWndProc_OnCommand (lpWndEventArgs);
long fnWndProc_OnClose (lpWndEventArgs);
const EVENTHANDLER MainHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_CLOSE, fnWndProc_OnClose}
};
long fnOutputProc_OnCreate (lpWndEventArgs Wea);
long fnOutputProc_OnPaint (lpWndEventArgs Wea);
long fnOutputProc_OnClose (lpWndEventArgs Wea);
const EVENTHANDLER OutputHandler[]=
{
{WM_CREATE, fnOutputProc_OnCreate},
{WM_PAINT, fnOutputProc_OnPaint},
{WM_CLOSE, fnOutputProc_OnClose}
};
struct ProgramData
{
double dblLength;
double dblWidth;
double dblHeight;
double dblVolume;
TCHAR szBoxType[16];
};
void btnCalculate_OnClick (lpWndEventArgs);
void btnDisplay_OnClick (lpWndEventArgs);
LRESULT CALLBACK fnWndProc (HWND, unsigned int, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
LRESULT CALLBACK fnOutputProc (HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam);
#endif
//Output.cpp
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "ProgEx40d.h"
#include "Output.h"
long fnOutputProc_OnCreate(lpWndEventArgs Wea)
{
CREATESTRUCT *pCreateStruct;
ProgramData* pProgramData;
ProgramData* pMain;
pCreateStruct=(CREATESTRUCT*)Wea->lParam;
pMain=(ProgramData*)pCreateStruct->lpCreateParams;
pProgramData=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
if(pProgramData)
{
SetWindowLong(Wea->hWnd,0,(long)pProgramData);
pProgramData->dblLength=pMain->dblLength;
pProgramData->dblWidth=pMain->dblWidth;
pProgramData->dblHeight=pMain->dblHeight;
pProgramData->dblVolume=pMain->dblVolume;
_tcscpy(pProgramData->szBoxType,pMain->szBoxType);
}
else //If we can't allocate the memory there isn't much use
return -1; //continuing so -1 causes CreateWindow to fail.
return 0;
}
long fnOutputProc_OnPaint(lpWndEventArgs Wea)
{
TCHAR szBuffer[64],szNumber[16];
ProgramData* pProgramData;
HFONT hFont,hTmp;
PAINTSTRUCT ps;
HDC hDC;
hDC=BeginPaint(Wea->hWnd,&ps);
pProgramData=(ProgramData*)GetWindowLong(Wea->hWnd,0);
hFont=CreateFont(20,0,0,0,FW_BOLD,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH,_T("Courier New"));
hTmp=(HFONT)SelectObject(hDC,hFont);
_tcscpy(szBuffer,_T("Box Type = ")), _tcscat(szBuffer,pProgramData->szBoxType);
TextOut(hDC,40,20,szBuffer,_tcslen(szBuffer));
_stprintf(szNumber,_T("%8.2f"),pProgramData->dblLength);
_tcscpy(szBuffer,_T("Length = ")), _tcscat(szBuffer,szNumber);
TextOut(hDC,40,40,szBuffer,_tcslen(szBuffer));
_stprintf(szNumber,_T("%8.2f"),pProgramData->dblWidth);
_tcscpy(szBuffer,_T("Width = ")), _tcscat(szBuffer,szNumber);
TextOut(hDC,40,60,szBuffer,_tcslen(szBuffer));
_stprintf(szNumber,_T("%8.2f"),pProgramData->dblHeight);
_tcscpy(szBuffer,_T("Height = ")), _tcscat(szBuffer,szNumber);
TextOut(hDC,40,80,szBuffer,_tcslen(szBuffer));
_stprintf(szNumber,_T("%8.2f"),pProgramData->dblVolume);
_tcscpy(szBuffer,_T("Volume = ")), _tcscat(szBuffer,szNumber);
TextOut(hDC,40,100,szBuffer,_tcslen(szBuffer));
SelectObject(hDC,hTmp);
DeleteObject(hFont);
EndPaint(Wea->hWnd,&ps);
return 0;
}
long fnOutputProc_OnClose(lpWndEventArgs Wea)
{
ProgramData* pProgramData=NULL;
pProgramData=(ProgramData*)GetWindowLong(Wea->hWnd,0);
if(pProgramData)
GlobalFree(pProgramData);
DestroyWindow(Wea->hWnd);
return 0;
}
LRESULT CALLBACK fnOutputProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(OutputHandler); i++)
{
if(OutputHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*OutputHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
//Output.h
#ifndef OUTPUT_H
#define OUTPUT_H
long fnOutputProc_OnCreate(lpWndEventArgs);
long fnOutputProc_OnPaint(lpWndEventArgs);
long fnOutputProc_OnClose(lpWndEventArgs);
LRESULT CALLBACK fnOutputProc(HWND,unsigned int,WPARAM,LPARAM);
#endif
/*
ProgEx40f -- Form3: Volume Of A Box. Adding Sophisticated Keyboard Navigation
Through Edit And Button Control Subclassing.
I actually thought I was through with this Box Example Series and I was for a short while
satisfied with it (note that I have not added anything to it for a few weeks). But then I
got to asking myself, "Would I give a program such as this to my users?", I unfortunately
had to answer no. I felt the program was just too awkward to use. For example, if you
had several boxes for which you needed to do calculations, you had to manually clear out
the text boxes for each new entry. In other words, the program could use a 'Clear'
button.
The next issue concerns data entry validation. As I'm sure you are aware, bad data is the
source of most problems with programs. And this program, while its only useful input are
numbers upon which calculations must be done, will accept any input whatsoever including
your birthday and home address. Not good.
Another issue that may be a problem for some of us and for others perhaps not concerns
navigation between text boxes and buttons. At present the tab key is the only way to move
between controls. For those of us with roots way back in the world of DOS, we kind of feel
more at home using the [ENTER] key after making a data entry. The program as it now stands
doesn't respond in any way to the [ENTER] key (in terms of the text boxes, at least), nor
to cursor or arrow navigation keys.
These are all somewhat complex problems whose solution however is relatively easy. The
reason I say they are complex though is that understanding their solution involves some
fairly 'deep' Windows architecture issues involving Window Classes and Window Procedures.
Lets take a brief look at the way the program as it now stands uses the several distinct
Window Classes it contains.
The "Form3" Window Class was defined & registered in WinMain(), and one instance of it was
created there. It is the main program window containing the text boxes and buttons.
fnWndProc() is its Window Procedure, and our code in that Window Procedure responds to
WM_CREATE, WM_COMMAND, and WM_CLOSE messages.
During the processing of this aforementioned window's WM_CREATE message we register another
class named "Output" and that class becomes our "Output" window. Any number of these are
created by clicking the 'Display' buton, and fnOutputProc() is the Window Procedure for
all windows of this class.
However, during the processing of the main program's WM_CREATE message we instantiated
several windows of 'system global classes' such as edit, button, and combobox controls.
While these windows were created with CreateWindow() function calls and exist as child
windows on our main program's form, we do not have the Window Procedures for these windows
in our application. Think about it this way; in our various Form2 programs (remember the
one which reported mouse movements, window resizing, button clicks, typed text, awhile back
in ProgEx39), we tested in the main window procedure for WM_CHAR messages. When we got
a WM_CHAR message we took the LOWORD(wParam) and concatenated it to our String variable,
then wrote that String to the Window with TextOut(). Well, we certainly didn't have to
do anything like that here to get text into our various length, width & height text boxes!
If a textbox has the focus and we type text it just mysteriously appears there. The reason
it just mysteriously appears there is because in the text box's window procedure (which we
don't have in our application) there are various routines it runs - probably in WM_CHAR and
WM_PAINT handlers - to put it there. Luckily for us this control and its window procedure
produce pretty adequate results. Unluckily for us, if there is some behavior of the control
we don't like - such as the fact that our text boxes are to contain only numbers and the
default text boxes will accept any character entered, well, we're just out of luck. We
don't have the Window Procedure with the 'raw' incoming data in our app to tinker with.
Or are we?
Well, as it turns out, Windows is a pretty remarkable & advanced operating system, and its
Application Programming Interface contains several functions that actually give us the
address of the internal (within Windows itself) Window Procedure for a specific standard
system global widnow class such as edit controls, buttons, etc; however, it will also
give us the Window Procedure address for any registered control for which we have the
class name. Now do you see why I've spent as much time as I have on function pointers?
They are pretty important in Windows!
So, the technique goes by the name 'subclassing', and the function we need to use to get
access to these internal Window Procedures is named SetWindowLong(). Before I get into
the specific semantics, here is some info directly from Microsoft on Subclassing...
--Microsoft--
Subclassing Defined
Subclassing is a technique that allows an application to intercept messages destined for
another window. An application can augment, monitor, or modify the default behavior of a
window by intercepting messages meant for another window. Subclassing is an effective way
to change or extend the behavior of a window without redeveloping the window. Subclassing
the default control window classes (button controls, edit controls, list controls, combo
box controls, static controls, and scroll bar controls) is a convenient way to obtain the
functionality of the control and to modify its behavior. For example, if a multiline edit
control is included in a dialog box and the user presses the ENTER key, the dialog box
closes. By subclassing the edit control, an application can have the edit control insert a
carriage return and line feed into the text without exiting the dialog box. An edit control
does not have to be developed specifically for the needs of the application.
The Basics
The first step in creating a window is registering a window class by filling a WNDCLASS
structure and calling RegisterClass. One element of the WNDCLASS structure is the address of
the window procedure for this window class. When a window is created, the 32-bit versions of
the Microsoft Windows™ operating system take the address of the window procedure in the
WNDCLASS structure and copy it to the new window's information structure. When a message is
sent to the window, Windows calls the window procedure through the address in the window's
information structure. To subclass a window, you substitute a new window procedure that
receives all the messages meant for the original window by substituting the window procedure
address with the new window procedure address.
When an application subclasses a window, it can take three actions with the message: (1) pass
the message to the original window procedure; (2) modify the message and pass it to the
original window procedure; (3) not pass the message.
--End Microsoft--
So there you have it. Lets do some subclassing. The way it works is fairly simple. When
a CreateWindow() call is made to create some child window control, we have returned to us
the window handle. What we do next is call SetWindowLong() passing into it the window handle
just received, the equate/define GWL_WNDPROC, and the address of the new window procedure.
Here is the SetWindowLong() documentation, again from Microsoft...
--Microsoft--
SetWindowLong The SetWindowLong function changes an attribute of the specified window. The
function also sets a 32-bit (long) value at the specified offset into the extra
window memory of a window.
LONG SetWindowLong
(
HWND hWnd, // handle of window
int nIndex, // offset of value to set
LONG dwNewLong // new value
);
Parameters
hWnd Handle to the window and, indirectly, the class to which the window belongs.
nIndex Specifies the zero-based offset to the value to be set. Valid values are in the range
zero through the number of bytes of extra window memory, minus 4; for example, if you
specified 12 or more bytes of extra memory, a value of 8 would be an index to the third
32-bit integer. To set any other value, specify one of the following values:
Value Action
GWL_EXSTYLE Sets a new extended window style.
GWL_STYLE Sets a new window style.
GWL_WNDPROC Sets a new address for the window procedure.
GWL_HINSTANCE Sets a new application instance handle.
GWL_ID Sets a new identifier of the window.
GWL_USERDATA Sets the 32-bit value associated with the window. Each window
has a corresponding 32-bit value intended for use by the
application that created the window.
dwNewLong Specifies the replacement value.
Remarks
If you use SetWindowLong with the GWL_WNDPROC index to replace the window procedure, the window
procedure must conform to the guidelines specified in the description of the WindowProc callback
function...
Calling SetWindowLong with the GWL_WNDPROC index creates a subclass of the window class used to
create the window. An application can subclass a system class, but should not subclass a window
class created by another process. The SetWindowLong function creates the window subclass by
changing the window procedure associated with a particular window class, causing the system to
call the new window procedure instead of the previous one. An application must pass any messages
not processed by the new window procedure to the previous window procedure by calling CallWindowProc.
This allows the application to create a chain of window procedures.
--End Microsoft--
You might be wondering how one might manage to pass into the function the address of the new
window procedure. Its quite easy. Lets take our txtLength edit control, for example. The first
thing you need to do in C/C++ is define a special function pointer variable that will hold the
address returned from the SetWindowLong() function call that subclasses the edit control. The
type is WNDPROC and here is what the global declaration would look like...
WNDPROC fnEditWndProc; //original window procedure of control which we are replacing; but we
//need to save it in this variable/function pointer
We're not adding a global variable to the program through this declaration, but rather a function
address, and all our functions are essentially global. Next, create the global callback type
window rocedure such as this...
long __stdcall fnEditSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
Note that this new procedure has a function signature with which you should be familiar. Its a
Window Procedure. Remember I told you this was important! You'll encounter this type of function
signature time and time again in Windows programming. This procedure will be in our application,
and once it gets 'set' with SetWindowLong(), every message pertaining to the edit control in
question will be 'hooked' into our application through this function. In the above example I
simply have a return statement that calls CallWindowProc(), but in our actual application we'll
be fleshing it out with various tests of the wParam and msg parameters to determine what we want
to do with specific messages.
And here is the remaining step to set it all up for our length edit control...
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","",dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
fnEditWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnEditSubClass);
The above two lines are right from our new modified fnWndProc_OnCreate() function where we create
the length edit control then immediately subclass it through SetWindowLong(). Note that the return
value from SetWindowLong() when the 2nd parameter is GWL_WNDPROC is nothing less than the address
within Windows of the edit control's Window Procedure! Please take a long look at the voluminous
code just below and when your confusion reaches intolerabe proportions, return to here and I'll
try to explain further what I am doing (what's going on).
When you 1st subclass a control, I believe its a good idea to make your subclass window procedure
as simple as possible such as this one for the edit control...
long __stdcall fnEditSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
Then run the program to see if it runs or if it crashes in which case you've screwed something
up. Also make sure your subclassed control is working normally with the subclass procedure in
place. Usually when I'm writing/debugging an application I have a debug output file opened which
I write output to. If the control has the focus and you have an output statement in it going into
a debug file, be prepared to have it execute several hundred times per second though! I just
thought I'd warn you about that because if you get up from your computer and come back to the
running program an hour later your hard drive might be full!
Now, as for what is going on in the program below, I wished to make the following modifications
to the program...
1) Prevent any characters from being entered in the text boxes except numbers and the American
decimal point;
2) Re-create the tab order to just involve the following sequence, leaving out of this sequence
the combo box and the 'Volume' edit control...
1 Length Text Box
2 Width Text Box
3 Height Text Box
4 Calculate Button
5 Display Button
6 Clear Button;
3) Enable the [ENTER] key to move along in the above [TAB] sequence;
4) Enable the cursor up and cursor down keys to move backwards and forwards through the above
sequence.
To accomplish this it was necessary to subclass both the three edit controls and the three button
controls. You can see that down in fnWndProc_OnCreate(). The button is a seperate class from
edit controls, and therefore its internal window procedure address is also different. That is
why these two WNDPROC variables had to be defined...
WNDPROC fnEditWndProc, fnButtonWndProc; //to save internal WndProc addresses for edit & button
Here are the two actual sub class procedures as it was necessary to create them for this program
in terms of my requirements as set out above...
long __stdcall fnEditSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return fnEditSubClass_OnKeyPress(hwnd,msg,wParam,lParam);
case WM_KEYDOWN:
return fnEditSubClass_OnKeyDown(hwnd,msg,wParam,lParam);
}
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
long __stdcall fnButtonSubClass(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return fnButtonSubClass_OnKeyPress(hwnd,msg,wParam,lParam);
case WM_KEYDOWN:
return fnButtonSubClass_OnKeyDown(hwnd,msg,wParam,lParam);
}
return CallWindowProc(fnButtonWndProc,hwnd,msg,wParam,lParam);
}
Note that they first filter the msg parameter for WM_CHAR and WM_KEYDOWN messages. The WM_CHAR
filter is necessary because [TAB], [ENTER], and numeric key presses are things we are interested
in in terms of my requirements as set out above. The WM_KEYDOWN message is received when users
press the arrow keys, function keys, Delete, Insert, etc. These are not picked up in WM_CHAR,
and we want to be able to change focus with the up and down navigation keys. The main subclass
procedures above then re-route program execution to other procedures with more refined filtering
algorithms when the msg is one of these two.
To procede from here lets follow through the algorithms with examples of what happens when
various keys are pressed. Lets assume the cursor is blinking in the Width text box. Lets further
assume the user hits the '3' key.
Whatever code machinery within Windows that monitors external hardware will detect that a keyboard
key was pressed and if this program presently is the active program and one of the text boxes has
the focus Windows will access its internal data structures for our text boxes to find the Window
Procedure to which the WM_CHAR message it will generate should be sent. It will find that
fnEditSubClass() is that procedure and fnEditSubClass will receive the WM_CHAR message. Having
been 'called', fnEditSubClass() will determine through its switch logic structure that the message
is a WM_CHAR message so it will immediately call fnEditSubClass_OnKeyPress() passing the message
and associated parameters on unchanged. fnEditSubClass_OnKeyPress() will use its switch construct
to examine the wParam and we'll immediatly be in the various cases from 46 to 57 as the number '3'
has an asci code of 51. You can see how the switch logic construct works in terms of consecutive
elements. Unless a 'break;' statement is hit, execution will keep falling through the adjacent cases.
However, it will soon hit the return statement which calls CallWindowProc() and what this statement
does is pass all the so far unaltered message parameters to fnEditWndProc, which is the original
Window Procedure for the edit controls which we replaced in the SetWindowLong() calls, but the
address of which we saved in WNDPROC variable fnEditWndProc. Look below to assure yourself that
the 1st parameter of CallWindowProc() is a Window Procedure address and in our case its
fnEditWndProc. At this point, albiet some rather complex indirection was involved, the character
'3' will show up in the Width text box as if nothing unusual had happened! It should be clear from
this that Window Procedures both in our application and within Windows itself had access to this
character message generated by the press of a key. Had we not subclassed these text boxes, our
application would not have had access to the message before the internal edit control Window
Procedure.
Lets now take a look at what happens if, for example, the 'F' key is pressed, which key has no valid
use in this program.
Well, hitting the 'F' key while the cursor is in the Width text box will generate a WM_CHAR message
and it will be received in fnEditSubClass(). Because of the switch there program execution will be
routed to fnEditSubClass_OnKeyPress(). The switch there will then try to match up the wParam which
holds the key code with the various cases in the switch statement. The letter 'F' has an asci code
of 70 and therefore no match is made; execution will fall into the 'default' case and the function
will return with zero. This result of zero will also be returned from fnEditSubClass(). However,
the important point isn't what is returned by these functions in this case of when the 'F' key was
pressed but what isn't returned - the CallWindowProc() function doesn't get called in this case and
the keypress message carrying the letter F never makes it to the edit control Window Procedure! You
can hold down the letter F all day and none will ever show up in the text box. The reason is that
Windows is no longer giving the former edit control window procedure in our program the results of
keyboard entry. Its all being given to us; if we pass the key on to the original window procedure
by calling CallWindowProc() then the letter will show up. In this case though we just exit from the
functions with an arbitrary return value of zero. The point to take home here is that if we want
the text box to perform its normal role its our responsibility to pass messages on to it by calling
CallWindowProc().
There is a rather funny terminology programmers have adopted with regard to all this and the
terminology makes reference to something all of us do and easily understand and that is eat. Imagine
for a moment that several of us have sat down at the dinner table to eat. There is a lone biscuit
on the biscuit plate being passed around. When we are passed the plate with the lone biscuit our
choice is to either remove and eat the biscuit, or pass it on down the line so that someone else
might take it. That's kind of the situation with the 'chains' of Window Procedures of which Microsoft
is referring to above. In the case of the 'F' key above, it was passed to us and we 'ate' it; or
in programmer speak, we 'ate the message'. We didn't pass it on down the line by giving it to
CallWindowProc(), so therefore the message ended with us and the 'F' never showed up in the text box.
Enough of my heavy handed attempt at humor!
In the case of pressing the [ENTER] key while the cursor is blinking in the Width text box, our edit
control subclass KeyPress() procedure will again be called, and we'll soon be in the case IDC_EDIT_WIDTH.
All that needs to be done there is to recognize that the hwnd that came through in the parameter list
is the hwnd of the edit control which received the key press, and that its parent obtainable with the
GetParent() function will give us the main dialog's hWnd. With that we can find the HANDLE of the
Height text box using GetDlgItem() and the control id of the Height text box. Then its a simple matter
of using the SetFocus() function to set the focus there. Note that I didn't bother passing the [ENTER]
keypress on to the original edit control WndProc. I 'ate' it. Passing it on wouldn't have really hurt;
its just that nothing was to be gained through it, and on my computers anyway I ended up with the
'bong' sound; which I don't like.
In the case of WM_KEYDOWN messages with the edit controls, the logic is similiar to the [ENTER] and [TAB]
key cases. Just a matter of setting focus to the next/previous control in the tab sequence.
The button KeyPress() logic is a bit interesting - buttons do receive keypresses - not just mouse clicks.
SendMessage(hwnd,BM_CLICK,0,0);
However, if you are a real glutton for punishment you can send a complete WM_COMMAND message to the
main window complete with notification codes, control ids, the works...
SendMessage(hParent,WM_COMMAND,MAKEWPARAM(IDC_BUTTON_CALCULATE,BM_CLICK),(LPARAM)GetDlgItem(hParent,IDC_BUTTON_CALCULATE));
But I've a feeling you'll like the former better!
Finally, note that down in WinMain() the IsDialogMessage() function call is gone; that's because we've
picked up here with our subclassing our specialized tabbing and control navigation needs. The
IsDialogMessage() function gave us a fairly decent tab order, and it makes sense to use that if
possible. But if your app would strongly benifit from an arrangement like we've done here, subclassing
might be the way to go.
One final issue; some folks like to replace the original Window Procedure back in Windows internal data
structure when the app closes. My personal opinion is that this is more a matter of form rather than
an absolute requirement; but you've got the addresses saved in fnEditWndProc and fnButtonWndProc if you
feel compelled to do so with SetWindowLong(). I'm laboring under no such compulsion so I didn't do it
here, but wanted to make you aware of the issue.
*/
//Main.cpp
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "ProgEx40f.h"
#include "Output.h"
WNDPROC fnEditWndProc, fnButtonWndProc;
long fnEditSubClass_OnKeyPress(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(wParam)
{
case 9: //tab key
case 13: //enter key
{
int iCtrlId=GetDlgCtrlID(hwnd);
HWND hParent=GetParent(hwnd);
switch(iCtrlId)
{
case IDC_EDIT_LENGTH:
SetFocus(GetDlgItem(hParent,IDC_EDIT_WIDTH));
return 0;
case IDC_EDIT_WIDTH:
SetFocus(GetDlgItem(hParent,IDC_EDIT_HEIGHT));
return 0;
case IDC_EDIT_HEIGHT:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CALCULATE));
return 0;
}
}
case 46: //Americam Decimal Point Or Period
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
case VK_BACK: //Backspace
case 48: //0
case 49: //1
case 50: //2 In these cases just pass the key on to
case 51: //3 the edit control's Window Procedure
case 52: //4
case 53: //5
case 54: //6
case 55: //7
case 56: //8
case 57: //9
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
default: //Eat the key!
return 0;
}
}
long fnEditSubClass_OnKeyDown(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
int iCtrlId=GetDlgCtrlID(hwnd);
HWND hParent=GetParent(hwnd);
switch(iCtrlId)
{
case IDC_EDIT_LENGTH:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CLEAR));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_EDIT_WIDTH));
return 0;
}
case IDC_EDIT_WIDTH:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_EDIT_LENGTH));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_EDIT_HEIGHT));
return 0;
}
case IDC_EDIT_HEIGHT:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_EDIT_WIDTH));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CALCULATE));
return 0;
}
}
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
long fnButtonSubClass_OnKeyPress(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
int iCtrlId=GetDlgCtrlID(hwnd);
HWND hParent=GetParent(hwnd);
switch(wParam)
{
case 9:
{
switch(iCtrlId)
{
case IDC_BUTTON_CALCULATE:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_DISPLAY));
return 0;
case IDC_BUTTON_DISPLAY:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CLEAR));
return 0;
case IDC_BUTTON_CLEAR:
SetFocus(GetDlgItem(hParent,IDC_EDIT_LENGTH));
return 0;
}
break;
}
case 13:
{
SendMessage(hwnd,BM_CLICK,0,0);
switch(iCtrlId)
{
case IDC_BUTTON_CALCULATE:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_DISPLAY));
return 0;
case IDC_BUTTON_DISPLAY:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CLEAR));
return 0;
case IDC_BUTTON_CLEAR:
SetFocus(GetDlgItem(hParent,IDC_EDIT_LENGTH));
return 0;
}
break;
}
}
return CallWindowProc(fnButtonWndProc,hwnd,msg,wParam,lParam);
}
long fnButtonSubClass_OnKeyDown(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
int iCtrlId=GetDlgCtrlID(hwnd);
HWND hParent=GetParent(hwnd);
switch(iCtrlId)
{
case IDC_BUTTON_CALCULATE:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_EDIT_HEIGHT));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_DISPLAY));
return 0;
}
case IDC_BUTTON_DISPLAY:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CALCULATE));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CLEAR));
return 0;
}
case IDC_BUTTON_CLEAR:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_DISPLAY));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_EDIT_LENGTH));
return 0;
}
}
return CallWindowProc(fnButtonWndProc,hwnd,msg,wParam,lParam);
}
long __stdcall fnEditSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return fnEditSubClass_OnKeyPress(hwnd,msg,wParam,lParam);
case WM_KEYDOWN:
return fnEditSubClass_OnKeyDown(hwnd,msg,wParam,lParam);
}
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
long __stdcall fnButtonSubClass(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return fnButtonSubClass_OnKeyPress(hwnd,msg,wParam,lParam);
case WM_KEYDOWN:
return fnButtonSubClass_OnKeyDown(hwnd,msg,wParam,lParam);
}
return CallWindowProc(fnButtonWndProc,hwnd,msg,wParam,lParam);
}
long fnWndProc_OnCreate(lpWndEventArgs Wea)
{
TCHAR* szBuffer[]=
{
(TCHAR*)_T("Ordinary Box"),
(TCHAR*)_T("Box Of Junk"),
(TCHAR*)_T("Beer Crate"),
(TCHAR*)_T("Wine Box"),
(TCHAR*)_T("Candy Box")
};
DWORD dwStyle=WS_CHILD|WS_VISIBLE;
TCHAR szClassName[]=_T("Output");
WNDCLASSEX wc;
HWND hCtl;
//Create Child Window Controls On Main Form
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
hCtl=CreateWindow(_T("static"),_T("Choose Kind of Box"),WS_CHILD|WS_VISIBLE,10,10,140,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindow(_T("combobox"),_T(""),dwStyle|CBS_DROPDOWNLIST|WS_VSCROLL,190,10,140,100,Wea->hWnd,(HMENU)IDC_COMBO_TYPE,Wea->hIns,0);
for(unsigned int i=0; i<5; i++)
SendMessage(hCtl,CB_INSERTSTRING,(WPARAM)-1,(LPARAM)szBuffer[i]);
SendMessage(hCtl,CB_SETCURSEL,(WPARAM)0,0); //|WS_TABSTOP
hCtl=CreateWindow(_T("static"),_T("Length of Box"),WS_CHILD|WS_VISIBLE,10,53,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
fnEditWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnEditSubClass);
hCtl=CreateWindow(_T("static"),_T("Width of Box"),WS_CHILD|WS_VISIBLE,10,93,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,90,70,25,Wea->hWnd,(HMENU)IDC_EDIT_WIDTH,Wea->hIns,0);
fnEditWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnEditSubClass);
hCtl=CreateWindow(_T("static"),_T("Height of Box"),WS_CHILD|WS_VISIBLE,10,133,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,150,130,70,25,Wea->hWnd,(HMENU)IDC_EDIT_HEIGHT,Wea->hIns,0);
fnEditWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnEditSubClass);
hCtl=CreateWindow(_T("static"),_T("Volume"),WS_CHILD|WS_VISIBLE,10,183,50,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),dwStyle,62,180,335,25,Wea->hWnd,(HMENU)IDC_EDIT_VOLUME,Wea->hIns,0);
hCtl=CreateWindow(_T("button"),_T("Calculate"),dwStyle,245,50,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_CALCULATE,Wea->hIns,0);
fnButtonWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnButtonSubClass);
hCtl=CreateWindow(_T("button"),_T("Display"),dwStyle,245,90,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_DISPLAY,Wea->hIns,0);
fnButtonWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnButtonSubClass);
hCtl=CreateWindow(_T("button"),_T("Clear"),dwStyle,245,130,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_CLEAR,Wea->hIns,0);
fnButtonWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnButtonSubClass);
SetWindowText(Wea->hWnd,_T("Volume Of Your Box"));
SetFocus(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE));
//Register Output Screen Class
wc.lpszClassName=szClassName, wc.lpfnWndProc=fnOutputProc;
wc.cbSize=sizeof (WNDCLASSEX), wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION), wc.hInstance=Wea->hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION), wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH), wc.cbWndExtra=4;
wc.lpszMenuName=NULL, wc.cbClsExtra=0;
RegisterClassEx(&wc);
return 0;
}
void btnCalculate_OnClick(lpWndEventArgs Wea)
{
TCHAR szBuffer[64],szText[64];
ProgramData ProDta;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight = _tstof(szBuffer);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight; //Calculate Volume
_stprintf(szBuffer,_T("%8.2f"),ProDta.dblVolume); //Write Volume back to string representation
_tcscpy(szText,_T("Your ")); //Build a string using C Runtime Stuff
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16); //Get user's selection out of combo box for box type
_tcscat(szText,ProDta.szBoxType); //Concatenate it into szText
_tcscat(szText,_T(" Has A Volume Of ")); //Keep building the string
_tcscat(szText,szBuffer); //Finally concatenate Volume into it at end.
SetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_VOLUME),szText); //Put it in the IDC_EDIT_VOLUME text box (edit control).
}
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
ProgramData ProDta; //This button click procedure will create an instance of our newly created 'Output'
TCHAR szBuffer[64]; //class. We registered the class up in our WM_CREATE handler, and we are creating
HINSTANCE hIns; //an instance of it just below through a CreateWindow() Api call.
HWND hWnd;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16), ProDta.dblLength = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16), ProDta.dblWidth = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16), ProDta.dblHeight = _tstof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight;
hIns=GetModuleHandle(0);
hWnd=CreateWindow(_T("Output"),_T("Output Screen"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,360,210,0,0,hIns,&ProDta);
ShowWindow(hWnd,SW_SHOWNORMAL);
}
void btnClear_OnClick(lpWndEventArgs Wea)
{
TCHAR szBuffer[4];
HWND hWnd;
szBuffer[0]=0;
hWnd=GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH);
SetWindowText(hWnd,szBuffer);
hWnd=GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT);
SetWindowText(hWnd,szBuffer);
hWnd=GetDlgItem(Wea->hWnd,IDC_EDIT_VOLUME);
SetWindowText(hWnd,szBuffer);
hWnd=GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH);
SetWindowText(hWnd,szBuffer);
SetFocus(hWnd);
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_BUTTON_CALCULATE:
btnCalculate_OnClick(Wea);
break;
case IDC_BUTTON_DISPLAY:
btnDisplay_OnClick(Wea);
break;
case IDC_BUTTON_CLEAR:
btnClear_OnClick(Wea);
break;
}
return 0;
}
long fnWndProc_OnClose(lpWndEventArgs Wea)
{
HWND hOutput;
do //Search And Destroy Mission For Any Output Windows Hanging Around
{
hOutput=FindWindowEx(0,0,_T("Output"),_T("Output Screen"));
if(hOutput)
SendMessage(hOutput,WM_CLOSE,0,0);
else
break;
}while(TRUE);
DestroyWindow(Wea->hWnd);
PostQuitMessage(0);
return 0;
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(MainHandler); i++)
{
if(MainHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*MainHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form3");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX); wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=0;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,100,400,415,250,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
//ProgEx40f.h
#ifndef PROGEX40F_H
#define PROGEX40F_H
#define IDC_COMBO_TYPE 1500
#define IDC_EDIT_LENGTH 1505
#define IDC_EDIT_WIDTH 1510
#define IDC_EDIT_HEIGHT 1515
#define IDC_EDIT_VOLUME 1520
#define IDC_BUTTON_CALCULATE 1525
#define IDC_BUTTON_DISPLAY 1530
#define IDC_BUTTON_CLEAR 1535
#ifdef _UNICODE
#define _tstof _wtof
#else
#define _tstof atof
#endif
#define dim(x) (sizeof(x) / sizeof(x[0]))
typedef struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
}WndEventArgs, *lpWndEventArgs;
struct EVENTHANDLER
{
unsigned int iMsg;
long (*fnPtr)(lpWndEventArgs);
};
long fnWndProc_OnCreate (lpWndEventArgs);
long fnWndProc_OnCommand (lpWndEventArgs);
long fnWndProc_OnClose (lpWndEventArgs);
const EVENTHANDLER MainHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_CLOSE, fnWndProc_OnClose}
};
long fnOutputProc_OnCreate (lpWndEventArgs Wea);
long fnOutputProc_OnPaint (lpWndEventArgs Wea);
long fnOutputProc_OnClose (lpWndEventArgs Wea);
const EVENTHANDLER OutputHandler[]=
{
{WM_CREATE, fnOutputProc_OnCreate},
{WM_PAINT, fnOutputProc_OnPaint},
{WM_CLOSE, fnOutputProc_OnClose}
};
struct ProgramData
{
double dblLength;
double dblWidth;
double dblHeight;
double dblVolume;
TCHAR szBoxType[16];
};
void btnCalculate_OnClick (lpWndEventArgs);
void btnDisplay_OnClick (lpWndEventArgs);
LRESULT CALLBACK fnWndProc (HWND, unsigned int, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
LRESULT CALLBACK fnOutputProc (HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam);
#endif
//Output.cpp
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "ProgEx40f.h"
long fnOutputProc_OnCreate(lpWndEventArgs Wea)
{
CREATESTRUCT *pCreateStruct;
ProgramData* pProgramData;
ProgramData* pMain;
pCreateStruct=(CREATESTRUCT*)Wea->lParam;
pMain=(ProgramData*)pCreateStruct->lpCreateParams;
pProgramData=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
if(pProgramData)
{
SetWindowLong(Wea->hWnd,0,(long)pProgramData);
pProgramData->dblLength=pMain->dblLength;
pProgramData->dblWidth=pMain->dblWidth;
pProgramData->dblHeight=pMain->dblHeight;
pProgramData->dblVolume=pMain->dblVolume;
_tcscpy(pProgramData->szBoxType,pMain->szBoxType);
}
else //If we can't allocate the memory there isn't much use
return -1; //continuing so -1 causes CreateWindow to fail.
return 0;
}
long fnOutputProc_OnPaint(lpWndEventArgs Wea)
{
TCHAR szBuffer[64],szNumber[16];
ProgramData* pProgramData;
HFONT hFont,hTmp;
PAINTSTRUCT ps;
HDC hDC;
hDC=BeginPaint(Wea->hWnd,&ps);
pProgramData=(ProgramData*)GetWindowLong(Wea->hWnd,0);
hFont=CreateFont(20,0,0,0,FW_BOLD,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH,_T("Courier New"));
hTmp=(HFONT)SelectObject(hDC,hFont);
_tcscpy(szBuffer,_T("Box Type = ")), _tcscat(szBuffer,pProgramData->szBoxType);
TextOut(hDC,40,20,szBuffer,_tcslen(szBuffer));
_stprintf(szNumber,_T("%8.2f"),pProgramData->dblLength);
_tcscpy(szBuffer,_T("Length = ")), _tcscat(szBuffer,szNumber);
TextOut(hDC,40,40,szBuffer,_tcslen(szBuffer));
_stprintf(szNumber,_T("%8.2f"),pProgramData->dblWidth);
_tcscpy(szBuffer,_T("Width = ")), _tcscat(szBuffer,szNumber);
TextOut(hDC,40,60,szBuffer,_tcslen(szBuffer));
_stprintf(szNumber,_T("%8.2f"),pProgramData->dblHeight);
_tcscpy(szBuffer,_T("Height = ")), _tcscat(szBuffer,szNumber);
TextOut(hDC,40,80,szBuffer,_tcslen(szBuffer));
_stprintf(szNumber,_T("%8.2f"),pProgramData->dblVolume);
_tcscpy(szBuffer,_T("Volume = ")), _tcscat(szBuffer,szNumber);
TextOut(hDC,40,100,szBuffer,_tcslen(szBuffer));
SelectObject(hDC,hTmp);
DeleteObject(hFont);
EndPaint(Wea->hWnd,&ps);
return 0;
}
long fnOutputProc_OnClose(lpWndEventArgs Wea)
{
ProgramData* pProgramData=NULL;
pProgramData=(ProgramData*)GetWindowLong(Wea->hWnd,0);
if(pProgramData)
GlobalFree(pProgramData);
DestroyWindow(Wea->hWnd);
return 0;
}
LRESULT CALLBACK fnOutputProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;
for(unsigned int i=0; i<dim(OutputHandler); i++)
{
if(OutputHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*OutputHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
//Output.h
#ifndef OUTPUT_H
#define OUTPUT_H
long fnOutputProc_OnCreate(lpWndEventArgs);
long fnOutputProc_OnPaint(lpWndEventArgs);
long fnOutputProc_OnClose(lpWndEventArgs);
LRESULT CALLBACK fnOutputProc(HWND,unsigned int,WPARAM,LPARAM);
#endif
'ProgEx40g -- PowerBASIC 10.02 Version Of Our BoxVolume/Form3 Program With Extensive Subclassing
'
' In this PowerBASIC version of the program I subclassed the Length, Width, and
' Height text boxes, and the Calculate and Display buttons. Also, I added a
' Clear button and subclassed that too. In addition, I added some simple data
' entry validation to the text boxes in that they will no longer accept non-
' numeric input.
'
' Due to the subclassing, you can now use the [ENTER] key to navigate through the
' controls, as well as the up and down arrow keys.
'
#Compile Exe
#Dim All
%UNICODE = 1
#If %Def(%UNICODE)
Macro ZStr = WStringz
Macro BStr = WString
%SIZEOF_CHAR = 2
#Else
Macro ZStr = Asciiz
Macro BStr = String
%SIZEOF_CHAR = 1
#EndIf
#Include "Win32api.inc"
#Include "BoxVolume.inc"
#Include "MsgHdlrs.inc"
#Include "frmOutput.inc"
Function fnEditSubClass_OnKeyPress(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local iCtrlId,hParent As Dword
Select Case As Long wParam
Case 8 'Backspace
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
Exit Function
Case 9, 13 '[TAB], [ENTER]
iCtrlId=GetDlgCtrlID(hWnd)
hParent=GetParent(hWnd)
Select Case As Long iCtrlId
Case %IDC_EDIT_LENGTH
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_WIDTH))
Exit Function
Case %IDC_EDIT_WIDTH
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_HEIGHT))
Exit Function
Case %IDC_EDIT_HEIGHT
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CALCULATE))
Exit Function
End Select
Case 46
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
Exit Function
Case 48 To 57
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
Exit Function
End Select
Function=0
End Function
Function fnEditSubClass_OnKeyDown(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local hParent As Dword
hParent=GetParent(hWnd)
Select Case As Long GetDlgCtrlID(hWnd)
Case %IDC_EDIT_LENGTH
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CLEAR))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_WIDTH))
Function=0 : Exit Function
End Select
Case %IDC_EDIT_WIDTH
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_LENGTH))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_HEIGHT))
Function=0 : Exit Function
End Select
Case %IDC_EDIT_HEIGHT
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_WIDTH))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CALCULATE))
Function=0 : Exit Function
End Select
End Select
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnButtonSubClass_OnKeyPress(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local hParent As Dword
hParent=GetParent(hWnd)
If wParam=9 Or wParam=13 Then
If wParam=13 Then
Call SendMessage(hWnd,%BM_CLICK,0,0)
End If
Select Case As Long GetDlgCtrlID(hWnd)
Case %IDC_BUTTON_CALCULATE
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_DISPLAY))
Function=0 : Exit Function
Case %IDC_BUTTON_DISPLAY
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CLEAR))
Function=0 : Exit Function
Case %IDC_BUTTON_CLEAR
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_LENGTH))
Function=0 : Exit Function
End Select
End If
Function=CallWindowProc(fnButtonWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnButtonSubClass_OnKeyDown(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local hParent As Dword
hParent=GetParent(hWnd)
Select Case As Long GetDlgCtrlID(hWnd)
Case %IDC_BUTTON_CALCULATE
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_HEIGHT))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_DISPLAY))
Function=0 : Exit Function
End Select
Case %IDC_BUTTON_DISPLAY
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CALCULATE))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CLEAR))
Function=0 : Exit Function
End Select
Case %IDC_BUTTON_CLEAR
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_DISPLAY))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_LENGTH))
Function=0 : Exit Function
End Select
End Select
Function=CallWindowProc(fnButtonWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnEditSubClass(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case As Long wMsg
Case %WM_CHAR
Function=fnEditSubClass_OnKeyPress(hWnd,wMsg,wParam,lParam)
Exit Function
Case %WM_KEYDOWN
Function=fnEditSubClass_OnKeyDown(hWnd,wMsg,wParam,lParam)
Exit Function
End Select
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnButtonSubClass(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case As Long wMsg
Case %WM_CHAR
Function=fnButtonSubClass_OnKeyPress(hWnd,wMsg,wParam,lParam)
Case %WM_KEYDOWN
Function=fnButtonSubClass_OnKeyDown(hWnd,wMsg,wParam,lParam)
End Select
Function=CallWindowProc(fnButtonWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnWndProc_OnCreate(Wea As WndEventArgs) As Long
Local pCreateStruct As CREATESTRUCT Ptr
Local szClassName As ZStr*16
Local szBuffer() As ZStr*16
Local hCtl,dwStyle As Dword
Local wc As WNDCLASSEX
Register i As Long
pCreateStruct=Wea.lParam
Wea.hInst=@pCreateStruct.hInstance
dwStyle=%WS_CHILD Or %WS_VISIBLE
Redim szBuffer(4) As ZStr*16
szBuffer(0)="Ordinary Box" : szBuffer(1)="Box Of Junk" : szBuffer(2)="Beer Crate" : szBuffer(3)="Wine Box" : szBuffer(4)="Candy Box"
hCtl=CreateWindow("static","Choose Kind of Box",%WS_CHILD Or %WS_VISIBLE,10,10,140,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindow("combobox","",dwStyle Or %CBS_DROPDOWNLIST Or %WS_VSCROLL,150,10,140,100,Wea.hWnd,%IDC_COMBO_TYPE,Wea.hInst,Byval 0)
For i=0 To 4
SendMessage(hCtl,%CB_INSERTSTRING,-1,Varptr(szBuffer(i)))
Next i
SendMessage(hCtl,%CB_SETCURSEL,0,0)
Erase szBuffer
hCtl=CreateWindow("static","Length of Box",%WS_CHILD Or %WS_VISIBLE,10,53,125,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",dwStyle,150,50,70,25,Wea.hWnd,%IDC_EDIT_LENGTH,Wea.hInst,Byval 0)
fnEditWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnEditSubClass))
hCtl=CreateWindow("static","Width of Box",%WS_CHILD Or %WS_VISIBLE,10,93,125,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",dwStyle,150,90,70,25,Wea.hWnd,%IDC_EDIT_WIDTH,Wea.hInst,Byval 0)
fnEditWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnEditSubClass))
hCtl=CreateWindow("static","Height of Box",%WS_CHILD Or %WS_VISIBLE,10,133,125,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",dwStyle,150,130,70,25,Wea.hWnd,%IDC_EDIT_HEIGHT,Wea.hInst,Byval 0)
fnEditWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnEditSubClass))
hCtl=CreateWindow("static","Volume",%WS_CHILD Or %WS_VISIBLE,10,183,50,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",dwStyle,60,180,335,25,Wea.hWnd,%IDC_EDIT_VOLUME,Wea.hInst,Byval 0)
hCtl=CreateWindow("button","Calculate",dwStyle,245,48,120,30,Wea.hWnd,%IDC_BUTTON_CALCULATE,Wea.hInst,Byval 0)
fnButtonWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnButtonSubClass))
hCtl=CreateWindow("button","Display",dwStyle,245,88,120,30,Wea.hWnd,%IDC_BUTTON_DISPLAY,Wea.hInst,Byval 0)
fnButtonWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnButtonSubClass))
hCtl=CreateWindow("button","Clear",dwStyle,245,128,120,30,Wea.hWnd,%IDC_BUTTON_CLEAR,Wea.hInst,Byval 0)
fnButtonWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnButtonSubClass))
Call SetWindowText(Wea.hWnd,"Box Volume With Child Control Subclassing")
'Register Output Screen Class
szClassName="Output"
wc.lpszClassName=Varptr(szClassName) : wc.lpfnWndProc=CodePtr(fnOutputProc)
wc.cbSize=Sizeof(WNDCLASSEX) : wc.style=%CS_DBLCLKS
wc.hIcon=LoadIcon(%NULL,ByVal %IDI_APPLICATION) : wc.hInstance=Wea.hInst
wc.hIconSm=LoadIcon(%NULL, ByVal %IDI_APPLICATION) : wc.hCursor=LoadCursor(%NULL,ByVal %IDC_ARROW)
wc.hbrBackground=GetStockObject(%WHITE_BRUSH) : wc.cbWndExtra=4
wc.lpszMenuName=%NULL : wc.cbClsExtra=0
Call RegisterClassEx(wc)
fnWndProc_OnCreate=0
End Function
Function btnCalculate_OnClick(Wea As WndEventArgs) As Long
Local szBuffer As ZStr*64
Local bx As CBox
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_LENGTH),szBuffer,64) : bx.dblLength=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_WIDTH),szBuffer,64) : bx.dblWidth=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_HEIGHT),szBuffer,64) : bx.dblHeight=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_COMBO_TYPE),bx.szBoxType,16)
bx.dblVolume = bx.dblLength * bx.dblWidth * bx.dblHeight
szBuffer="The Volume Of Your " & bx.szBoxType & " Is " & Str$(bx.dblVolume) & "."
Call SetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_VOLUME),szBuffer)
Function=0
End Function
Function btnDisplay_OnClick(Wea As WndEventArgs) As Long
Local szBuffer As ZStr * 64
Local hIns,hOutput As Dword
Local box As CBox
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_LENGTH),szBuffer,64) : box.dblLength=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_WIDTH),szBuffer,64) : box.dblWidth=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_HEIGHT),szBuffer,64) : box.dblHeight=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_COMBO_TYPE),box.szBoxType,16)
box.dblVolume = box.dblLength * box.dblWidth * box.dblHeight
hIns=GetModuleHandle("")
hOutput=CreateWindowEx(0,"Output","Output Screen",%WS_OVERLAPPEDWINDOW,%CW_USEDEFAULT,%CW_USEDEFAULT,350,200,0,0,hIns,Byval Varptr(box))
Call ShowWindow(hOutput,%SW_SHOWNORMAL)
Function=0
End Function
Function btnClear_OnClick(Wea As WndEventArgs) As Long
Local hEdit As Dword
hEdit=GetDlgItem(Wea.hWnd,%IDC_EDIT_WIDTH)
Call SetWindowText(hEdit,"")
hEdit=GetDlgItem(Wea.hWnd,%IDC_EDIT_HEIGHT)
SetWindowText(hEdit,"")
hEdit=GetDlgItem(Wea.hWnd,%IDC_EDIT_VOLUME)
SetWindowText(hEdit,"")
hEdit=GetDlgItem(Wea.hWnd,%IDC_EDIT_LENGTH)
SetWindowText(hEdit,"")
SetFocus(hEdit)
Function=0
End Function
Function fnWndProc_OnCommand(Wea As WndEventArgs) As Long
Select Case As Long Lowrd(Wea.wParam)
Case %IDC_BUTTON_CALCULATE
Call btnCalculate_OnClick(Wea)
Case %IDC_BUTTON_DISPLAY
Call btnDisplay_OnClick(Wea)
Case %IDC_BUTTON_CLEAR
Call btnClear_OnClick(Wea)
End Select
fnWndProc_OnCommand=0
End Function
Function fnWndProc_OnClose(wea As WndEventArgs) As Long
Local hOutput As Dword
Do
hOutput=FindWindowEx(0,0,"Output","Output Screen")
If hOutput Then
Call SendMessage(hOutput,%WM_CLOSE,0,0)
Else
Exit Do
End If
Loop
Call PostQuitMessage(0)
fnWndProc_OnClose=0
End Function
Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
Local wea As WndEventArgs
Register iReturn As Long
Register i As Long
For i=0 To 2
If wMsg=MsgHdlr(i).wMessage Then
wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
Call Dword MsgHdlr(i).dwFnPtr Using FnPtr(wea) To iReturn
fnWndProc=iReturn
Exit Function
End If
Next i
fnWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function
Function WinMain(ByVal hIns As Long, ByVal hPrev As Long, ByVal lpCmdLn As WStringz Ptr, ByVal iShow As Long) As Long
Local szAppName As ZStr*16
Local wc As WndClassEx
Local hWnd As Dword
Local Msg As tagMsg
szAppName="Form3" : Call AttachMessageHandlers()
wc.lpszClassName=VarPtr(szAppName) : wc.lpfnWndProc=CodePtr(fnWndProc)
wc.cbClsExtra=0 : wc.cbWndExtra=0
wc.style=%CS_HREDRAW Or %CS_VREDRAW : wc.hInstance=hIns
wc.cbSize=SizeOf(wc) : wc.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)
wc.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW) : wc.hbrBackground=%COLOR_BTNFACE+1
wc.lpszMenuName=%NULL
Call RegisterClassEx(wc)
hWnd=CreateWindow(szAppName,"Form3",%WS_OVERLAPPEDWINDOW,100,400,420,250,0,0,hIns,ByVal 0)
Call ShowWindow(hWnd,iShow)
While GetMessage(Msg,%NULL,0,0)
Call TranslateMessage(Msg)
Call DispatchMessage(Msg)
Wend
Function=msg.wParam
End Function
'MsgHdlrs.inc
Sub AttachMessageHandlers() 'Associate Windows Message With Message Handlers
Dim OutputHdlr(2) As Global MessageHandler
Dim MsgHdlr(2) As Global MessageHandler
MsgHdlr(0).wMessage=%WM_CREATE : MsgHdlr(0).dwFnPtr=CodePtr(fnWndProc_OnCreate)
MsgHdlr(1).wMessage=%WM_COMMAND : MsgHdlr(1).dwFnPtr=CodePtr(fnWndProc_OnCommand)
MsgHdlr(2).wMessage=%WM_CLOSE : MsgHdlr(2).dwFnPtr=CodePtr(fnWndProc_OnClose)
OutputHdlr(0).wMessage=%WM_CREATE : OutputHdlr(0).dwFnPtr=CodePtr(fnOutputProc_OnCreate)
OutputHdlr(1).wMessage=%WM_PAINT : OutputHdlr(1).dwFnPtr=CodePtr(fnOutputProc_OnPaint)
OutputHdlr(2).wMessage=%WM_CLOSE : OutputHdlr(2).dwFnPtr=CodePtr(fnOutputProc_OnClose)
End Sub
'BoxVolume.inc
%IDC_COMBO_TYPE = 1500
%IDC_EDIT_LENGTH = 1505
%IDC_EDIT_WIDTH = 1510
%IDC_EDIT_HEIGHT = 1515
%IDC_EDIT_VOLUME = 1520
%IDC_BUTTON_CALCULATE = 1525
%IDC_BUTTON_DISPLAY = 1530
%IDC_BUTTON_CLEAR = 1535
Type WndEventArgs
wParam As Long
lParam As Long
hWnd As Dword
hInst As Dword
End Type
Type MessageHandler
wMessage As Long
dwFnPtr As Dword
End Type
Type CBox
dblLength As Double
dblWidth As Double
dblHeight As Double
dblVolume As Double
szBoxType As ZStr*16
End Type
Global fnEditWndProc As Dword
Global fnButtonWndProc As Dword
Declare Function FnPtr(wea As WndEventArgs) As Long
'frmOutput.inc
Function fnOutputProc_OnCreate(Wea As WndEventArgs) As Long
Local ptrBox As CBox Ptr, ptrOutputBox As CBox Ptr
Local pCreateStruct As CREATESTRUCT Ptr
pCreateStruct=Wea.lParam
ptrBox=@pCreateStruct.lpCreateParams
ptrOutputBox=GlobalAlloc(%GPTR,SizeOf(CBox))
If ptrOutputBox Then
Type Set @ptrOutputBox = @ptrBox
Call SetProp(Wea.hWnd,"Our Box",ptrOutputBox)
Else
Function=-1
End If
Function=0
End Function
Function fnOutputProc_OnPaint(Wea As WndEventArgs) As Long
Local szBuffer As ZStr*64
Local strField As BStr*8
Local hFont,hTmp As Dword
Local ptrBox As CBox Ptr
Local ps As PAINTSTRUCT
Local hDC As Dword
hDC=BeginPaint(Wea.hWnd,ps)
hFont=CreateFont _
( _
20,0,0,0,%FW_BOLD,0,0,0,%ANSI_CHARSET, _
%OUT_DEFAULT_PRECIS,%CLIP_DEFAULT_PRECIS, _
%PROOF_QUALITY,%DEFAULT_PITCH,"Courier New" _
)
hTmp=SelectObject(hDC,hFont)
ptrBox=GetProp(Wea.hWnd,"Our Box")
szBuffer="Box Type = " & @ptrBox.szBoxType
TextOut(hDC,40,20,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblLength,"###0.#0")
szBuffer="Length = " & strField
TextOut(hDC,40,40,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblWidth,"###0.#0")
szBuffer="Width = " & strField
TextOut(hDC,40,60,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblHeight,"###0.#0")
szBuffer="Height = " & strField
TextOut(hDC,40,80,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblVolume,"####0.#0")
szBuffer="Volume = " & strField
TextOut(hDC,40,100,szBuffer,Len(szBuffer))
Call SelectObject(hDC,hTmp)
Call DeleteObject(hFont)
Call EndPaint(Wea.hWnd,ps)
fnOutputProc_OnPaint=0
End Function
Function fnOutputProc_OnClose(Wea As WndEventArgs) As Long
Local pBox As CBox Ptr
pBox=RemoveProp(Wea.hWnd,"Our Box")
If pBox Then
Call GlobalFree(pBox)
End If
Call DestroyWindow(Wea.hWnd)
fnOutputProc_OnClose=0
End Function
Function fnOutputProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
Local Wea As WndEventArgs
Register iReturn As Long
Register i As Long
For i=0 To 2
If wMsg=OutputHdlr(i).wMessage Then
Wea.hWnd=hWnd: Wea.wParam=wParam: Wea.lParam=lParam
Call Dword OutputHdlr(i).dwFnPtr Using FnPtr(Wea) To iReturn
fnOutputProc=iReturn
Exit Function
End If
Next i
fnOutputProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function
Paul Squires's FireFly 3 can be used to create this BoxVolume/Form3 application and the
use of it for that purpose is really interesting. Essentially you have a Visual Basic
like experience where very many things are handled by the FireFly Visual Designer/IDE;
especially those things related to building the interface and making everything work;
what I've been referring to as the internal plumbing or wiring of the application.
Specificaly, and with reference to our BoxVolume program, the types of things that you
don't have to do when making the project in FireFly are as follows;
1) You don't have to do any registering of Window Classes or CreateWindow() calls;
2) You don't have to connect message handlers to Window Procedures using any kind
of function pointer or switch/select case logic. In fact, you don't have to
deal with WinMain() or WndProc() at all if you don't want to;
What is left that you do have to do (after you've used the FireFly Visual Designer to
create your user interface) is write the code for the event procedures that calculate
and display the box's volume. In fact, here is the totality of the code I wrote to
create an exact duplicate of our little project in FireFly. You should be able to
easily figure out how the auto-generated procedure names relate to my C++ and PowerBASIC
project's procedure names...
First BoxVolume.inc
Type CBox
dblLength As Double
dblWidth As Double
dblHeight As Double
dblVolume As Double
szBoxType As Asciiz*16
End Type
Then frmBoxVolume.frm
Function frmBoxVolume_WM_CREATE(hWndForm As Dword, ByVal UserData As Long) As Long
Local szBuffer() As Asciiz*16
Register i As Long
ReDim szBuffer(4) As Asciiz*16
szBuffer(0)="Ordinary Box"
szBuffer(1)="Box Of Junk"
szBuffer(2)="Beer Crate"
szBuffer(3)="Wine Box"
szBuffer(4)="Candy Box"
For i=0 To 4
Call SendMessage(HWND_FRMBOXVOLUME_CBOTYPE,%CB_INSERTSTRING,-1,VarPtr(szBuffer(i)))
Next i
SendMessage(HWND_FRMBOXVOLUME_CBOTYPE,%CB_SETCURSEL,0,0)
Function=0
End Function
Function frmBoxVolume_btnCalculate_BN_CLICKED(ControlIndex As Long, hWndForm As Dword, hWndControl As Dword,idButtonControl As Long) As Long
Local szBuffer As Asciiz*64, szBoxType As Asciiz*16
Local bx As CBox
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_TXTLENGTH),szBuffer,64)
bx.dblLength=Val(szBuffer)
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_TXTWIDTH),szBuffer,64)
bx.dblWidth=Val(szBuffer)
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_TXTHEIGHT),szBuffer,64)
bx.dblHeight=Val(szBuffer)
bx.dblVolume = bx.dblLength * bx.dblWidth * bx.dblHeight
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_CBOTYPE),szBoxType,16)
szBuffer="The Volume Of Your " & szBoxType & " Is " & Str$(bx.dblVolume) & "."
Call SetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_TXTVOLUME),szBuffer)
Function=0
End Function
Function frmBoxVolume_btnDisplay_BN_CLICKED(ControlIndex As Long, hWndForm As Dword, hWndControl As Dword, idButtonControl As Long) As Long
Local szBuffer As Asciiz*64, szBoxType As Asciiz*16
Local bx As CBox
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_TXTLENGTH),szBuffer,64)
bx.dblLength=Val(szBuffer)
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_TXTWIDTH),szBuffer,64)
bx.dblWidth=Val(szBuffer)
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_TXTHEIGHT),szBuffer,64)
bx.dblHeight=Val(szBuffer)
bx.dblVolume = bx.dblLength * bx.dblWidth * bx.dblHeight
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_CBOTYPE),bx.szBoxType,16)
Call frmOutput_Show(0,%FALSE,VarPtr(bx))
Function=0
End Function
Function frmBoxVolume_WM_CLOSE(hWndForm As Dword) As Long
Local hOutput As Dword
Do
hOutput=FindWindowEx(0,0,"FORM_BOXVOLUME_FRMOUTPUT_CLASS","Output Screen")
If hOutput Then
Call SendMessage(hOutput,%WM_CLOSE,0,0)
Else
Exit Do
End If
Loop
Function=0
End Function
Then the code for the Output Screen - frmOutput.frm
Function frmOutput_WM_CREATE (hWndForm As Dword, ByVal UserData As Long) As Long
Local ptrBox As CBox Ptr, ptrOutputBox As CBox Ptr
ptrBox=UserData
ptrOutputBox=GlobalAlloc(%GPTR,SizeOf(CBox))
If ptrOutputBox Then
Type Set @ptrOutputBox = @ptrBox
Call SetProp(hWndForm,"Our Box",ptrOutputBox)
Else
Function=-1
End If
Function=0
End Function
Function frmOutput_WM_PAINT(hWndForm As Dword) As Long
Local szBuffer As Asciiz*64
Local strField As String*8
Local hFont,hTmp As Dword
Local ptrBox As CBox Ptr
Local ps As PAINTSTRUCT
Local hDC As Dword
hDC=BeginPaint(hWndForm,ps)
hFont=CreateFont _
( _
20,0,0,0,%FW_BOLD,0,0,0,%ANSI_CHARSET, _
%OUT_DEFAULT_PRECIS,%CLIP_DEFAULT_PRECIS, _
%PROOF_QUALITY,%DEFAULT_PITCH,"Courier New" _
)
hTmp=SelectObject(hDC,hFont)
ptrBox=GetProp(hWndForm,"Our Box")
szBuffer="Box Type = " & @ptrBox.szBoxType
TextOut(hDC,40,20,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblLength,"###0.#0")
szBuffer="Length = " & strField
TextOut(hDC,40,40,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblWidth,"###0.#0")
szBuffer="Width = " & strField
TextOut(hDC,40,60,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblHeight,"###0.#0")
szBuffer="Height = " & strField
TextOut(hDC,40,80,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblVolume,"####0.#0")
szBuffer="Volume = " & strField
TextOut(hDC,40,100,szBuffer,Len(szBuffer))
Call SelectObject(hDC,hTmp)
Call DeleteObject(hFont)
Call EndPaint(hWndForm,ps)
Function=0
End Function
Function frmOutput_WM_CLOSE(hWndForm As Dword) As Long
Local pBox As CBox Ptr
pBox=RemoveProp(hWndForm,"Our Box")
If pBox Then
Call GlobalFree(pBox)
End If
Function=0
End Function
That's it! Compare Function frmBoxVolume_WM_CREATE() with either the fnWndProc_OnCreate()
of the PowerBASIC or C++ version and you'll see how much shorter it is. All the
CreateWindow() and RegisterClassEx() calls are gone. That is taken care of by designer,
which, by the way, does create/auto-generate the underlying Api code in a form you can
examine if you wish. However, the point is I suppose - you don't have to.
Just a few quick comments on naming objects. When I created the user interface in FireFly
I named the Length text box txtLength - which would have been a typical VB usage. The main
program Window/Dialog/Form I named frmBoxVolume. So if you look in...
Function frmBoxVolume_btnCalculate_BN_CLICKED()
...which is the event procedure FireFly auto-generated for clicking the 'Calculate' button
on the main form, you'll see this code to get the box length out of the txtLength text box...
Call GetWindowText(GetDlgItem(hWndForm,IDC_FRMBOXVOLUME_TXTLENGTH),szBuffer,64)
What you see there is that FireFly created the equate with the IDC_ prefix, then added the
name of the form to that - frmBoxVolume, then finally added the txtLength name of the text
box to produce the whole equate. FireFly has a dialog 'Handles And Control Ids' on its View
Menu where you can easily get your equates/handles for everything in your application and
get them inserted into your code where you need them.
This is a really nice way to program. I'll attach all the FireFly 3 files for this project
that you should hopefully be able to easily recreate the project on your computer if you
have FireFly 3 or the FireFly Demo from www.planetsquires.com
/*
ProgEx40f -- Form3: Volume Of Your Box. Adding Sophisticated Keyboard Navigation
Through Edit And Button Control Subclassing.
I actualy thought I was through with this Box Example Series and I was for a short while
satisfied with it (note that I havn't added anything to it for a few weeks). But then I
got to asking myself, "Would I give a program such as this to my users?", I unfortunately
had to answer no. I felt the program was just too awkward to use. For example, if you
had several boxes for which you needed to do calculations, you had to manually clear out
the text boxes for each new entry. In other words, the program could use a 'Clear'
button.
The next issue concerns data entry validation. As I'm sure you are aware, bad data is the
source of most problems with programs. And this program, while its only useful input are
numbers upon which calculations must be done, will accept any input whatsoever including
your birthday and home address. Not good.
Another issue that may be a problem for some of us and for others perhaps not concerns
navigation between text boxes and buttons. At present the tab key is the only way to move
between controls. For those of us with roots way back in the world of DOS, we kind of feel
more at home using the [ENTER] key after making a data entry. The program as it now stands
doesn't respond in any way to the [ENTER] key (in terms of the text boxes, at least), nor
to cursor or arrow navigation keys.
These are all somewhat complex problems whose solution however is relatively easy. The
reason I say they are complex though is that understanding their solution involves some
fairly 'deep' Windows architecture issues involving Window Classes and Window Procedures.
Lets take a brief look at the way the program as it now stands uses the several distinct
Window Classes it contains.
The "Form3" Window Class was defined & registered in WinMain(), and one instance of it was
created there. It is the main program window containing the text boxes and buttons.
fnWndProc() is its Window Procedure, and our code in that Window Procedure responds to
WM_CREATE, WM_COMMAND, and WM_CLOSE messages.
During the processing of this aforementioned window's WM_CREATE message we register another
class named "Output" and that class becomes our "Output" window. Any number of these are
created by clicking the 'Display' buton, and fnOutputProc() is the Window Procedure for
all windows of this class.
However, during the processing of the main program's WM_CREATE message we instantiated
several windows of 'system global classes' such as edit, button, and combobox controls.
While these windows were created with CreateWindow() function calls and exist as child
windows on our main program's form, we do not have the Window Procedures for these windows
in our application. Think about it this way; in our various Form2 programs (remember the
one which reported mouse movements, window resizing, button clicks, typed text, awhile back
in ProgEx39), we tested in the main window procedure for WM_CHAR messages. When we got
a WM_CHAR message we took the LOWORD(wParam) and concatenated it to our String variable,
then wrote that String to the Window with TextOut(). Well, we certainly didn't have to
do anything like that here to get text into our various length, width & height text boxes!
If a textbox has the focus and we type text it just mysteriously appears there. The reason
it just mysteriously appears there is because in the text box's window procedure (which we
don't have in our application) there are various routines it runs - probably in WM_CHAR and
WM_PAINT handlers - to put it there. Luckily for us this control and its window procedure
produce pretty adequate results. Unluckily for us, if there is some behavior of the control
we don't like - such as the fact that our text boxes are to contain only numbers and the
default text boxes will accept any character entered, well, we're just out of luck. We
don't have the Window Procedure with the 'raw' incoming data in our app to tinker with.
Or are we?
Well, as it turns out, Windows is a pretty remarkable & advanced operating system, and its
Application Programming Interface contains several functions that actually give us the
address of the internal (within Windows itself) Window Procedure for a specific standard
system global widnow class such as edit controls, buttons, etc; however, it will also
give us the Window Procedure address for any registered control for which we have the
class name. Now do you see why I've spent as much time as I have on function pointers?
They are pretty important in Windows!
So, the technique goes by the name 'subclassing', and the function we need to use to get
access to these internal Window Procedures is named SetWindowLong(). Before I get into
the specific semantics, here is some info directly from Microsoft on Subclassing...
--Microsoft--
Subclassing Defined
Subclassing is a technique that allows an application to intercept messages destined for
another window. An application can augment, monitor, or modify the default behavior of a
window by intercepting messages meant for another window. Subclassing is an effective way
to change or extend the behavior of a window without redeveloping the window. Subclassing
the default control window classes (button controls, edit controls, list controls, combo
box controls, static controls, and scroll bar controls) is a convenient way to obtain the
functionality of the control and to modify its behavior. For example, if a multiline edit
control is included in a dialog box and the user presses the ENTER key, the dialog box
closes. By subclassing the edit control, an application can have the edit control insert a
carriage return and line feed into the text without exiting the dialog box. An edit control
does not have to be developed specifically for the needs of the application.
The Basics
The first step in creating a window is registering a window class by filling a WNDCLASS
structure and calling RegisterClass. One element of the WNDCLASS structure is the address of
the window procedure for this window class. When a window is created, the 32-bit versions of
the Microsoft Windows™ operating system take the address of the window procedure in the
WNDCLASS structure and copy it to the new window's information structure. When a message is
sent to the window, Windows calls the window procedure through the address in the window's
information structure. To subclass a window, you substitute a new window procedure that
receives all the messages meant for the original window by substituting the window procedure
address with the new window procedure address.
When an application subclasses a window, it can take three actions with the message: (1) pass
the message to the original window procedure; (2) modify the message and pass it to the
original window procedure; (3) not pass the message.
--End Microsoft--
So there you have it. Lets do some subclassing. The way it works is fairly simple. When
a CreateWindow() call is made to create some child window control, we have returned to us
the window handle. What we do next is call SetWindowLong() passing into it the window handle
just received, the equate/define GWL_WNDPROC, and the address of the new window procedure.
Here is the SetWindowLong() documentation, again from Microsoft...
--Microsoft--
SetWindowLong The SetWindowLong function changes an attribute of the specified window. The
function also sets a 32-bit (long) value at the specified offset into the extra
window memory of a window.
LONG SetWindowLong
(
HWND hWnd, // handle of window
int nIndex, // offset of value to set
LONG dwNewLong // new value
);
Parameters
hWnd Handle to the window and, indirectly, the class to which the window belongs.
nIndex Specifies the zero-based offset to the value to be set. Valid values are in the range
zero through the number of bytes of extra window memory, minus 4; for example, if you
specified 12 or more bytes of extra memory, a value of 8 would be an index to the third
32-bit integer. To set any other value, specify one of the following values:
Value Action
GWL_EXSTYLE Sets a new extended window style.
GWL_STYLE Sets a new window style.
GWL_WNDPROC Sets a new address for the window procedure.
GWL_HINSTANCE Sets a new application instance handle.
GWL_ID Sets a new identifier of the window.
GWL_USERDATA Sets the 32-bit value associated with the window. Each window
has a corresponding 32-bit value intended for use by the
application that created the window.
dwNewLong Specifies the replacement value.
Remarks
If you use SetWindowLong with the GWL_WNDPROC index to replace the window procedure, the window
procedure must conform to the guidelines specified in the description of the WindowProc callback
function...
Calling SetWindowLong with the GWL_WNDPROC index creates a subclass of the window class used to
create the window. An application can subclass a system class, but should not subclass a window
class created by another process. The SetWindowLong function creates the window subclass by
changing the window procedure associated with a particular window class, causing the system to
call the new window procedure instead of the previous one. An application must pass any messages
not processed by the new window procedure to the previous window procedure by calling CallWindowProc.
This allows the application to create a chain of window procedures.
--End Microsoft--
You might be wondering how one might manage to pass into the function the address of the new
window procedure. Its quite easy. Lets take our txtLength edit control, for example. The first
thing you need to do in C/C++ is define a special function pointer variable that will hold the
address returned from the SetWindowLong() function call that subclasses the edit control. The
type is WNDPROC and here is what the global declaration would look like...
WNDPROC fnEditWndProc; //original window procedure of control which we are replacing; but we
//need to save it in this variable/function pointer
We're not adding a global variable to the program through this declaration, but rather a function
address, and all our functions are essentially global. Next, create the global callback type
window rocedure such as this...
long __stdcall fnEditSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
Note that this new procedure has a function signature with which you should be familiar. Its a
Window Procedure. Remember I told you this was important! You'll encounter this type of function
signature time and time again in Windows programming. This procedure will be in our application,
and once it gets 'set' with SetWindowLong(), every message pertaining to the edit control in
question will be 'hooked' into our application through this function. In the above example I
simply have a return statement that calls CallWindowProc(), but in our actual application we'll
be fleshing it out with various tests of the wParam and msg parameters to determine what we want
to do with specific messages.
And here is the remaining step to set it all up for our length edit control...
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","",dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
fnEditWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnEditSubClass);
The above two lines are right from our new modified fnWndProc_OnCreate() function where we create
the length edit control then immediately subclass it through SetWindowLong(). Note that the return
value from SetWindowLong() when the 2nd parameter is GWL_WNDPROC is nothing less than the address
within Windows of the edit control's Window Procedure! Please take a long look at the voluminous
code just below and when your confusion reaches intolerabe proportions, return to here and I'll
try to explain further what I am doing (what's going on).
When you 1st subclass a control, I believe its a good idea to make your subclass window procedure
as simple as possible such as this one for the edit control...
long __stdcall fnEditSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
Then run the program to see if it runs or if it crashes in which case you've screwed something
up. Also make sure your subclassed control is working normally with the subclass procedure in
place. Usually when I'm writing/debugging an application I have a debug output file opened which
I write output to. If the control has the focus and you have an output statement in it going into
a debug file, be prepared to have it execute several hundred times per second though! I just
thought I'd warn you about that because if you get up from your computer and come back to the
running program an hour later your hard drive might be full!
Now, as for what is going on in the program below, I wished to make the following modifications
to the program...
1) Prevent any characters from being entered in the text boxes except numbers and the American
decimal point;
2) Re-create the tab order to just involve the following sequence, leaving out of this sequence
the combo box and the 'Volume' edit control...
1 Length Text Box
2 Width Text Box
3 Height Text Box
4 Calculate Button
5 Display Button
6 Clear Button;
3) Enable the [ENTER] key to move along in the above [TAB] sequence;
4) Enable the cursor up and cursor down keys to move backwards and forwards through the above
sequence.
To accomplish this it was necessary to subclass both the three edit controls and the three button
controls. You can see that down in fnWndProc_OnCreate(). The button is a seperate class from
edit controls, and therefore its internal window procedure address is also different. That is
why these two WNDPROC variables had to be defined...
WNDPROC fnEditWndProc, fnButtonWndProc; //to save internal WndProc addresses for edit & button
Here are the two actual sub class procedures as it was necessary to create them for this program
in terms of my requirements as set out above...
long __stdcall fnEditSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return fnEditSubClass_OnKeyPress(hwnd,msg,wParam,lParam);
case WM_KEYDOWN:
return fnEditSubClass_OnKeyDown(hwnd,msg,wParam,lParam);
}
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
long __stdcall fnButtonSubClass(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return fnButtonSubClass_OnKeyPress(hwnd,msg,wParam,lParam);
case WM_KEYDOWN:
return fnButtonSubClass_OnKeyDown(hwnd,msg,wParam,lParam);
}
return CallWindowProc(fnButtonWndProc,hwnd,msg,wParam,lParam);
}
Note that they first filter the msg parameter for WM_CHAR and WM_KEYDOWN messages. The WM_CHAR
filter is necessary because [TAB], [ENTER], and numeric key presses are things we are interested
in in terms of my requirements as set out above. The WM_KEYDOWN message is received when users
press the arrow keys, function keys, Delete, Insert, etc. These are not picked up in WM_CHAR,
and we want to be able to change focus with the up and down navigation keys. The main subclass
procedures above then re-route program execution to other procedures with more refined filtering
algorithms when the msg is one of these two.
To procede from here lets follow through the algorithms with examples of what happens when
various keys are pressed. Lets assume the cursor is blinking in the Width text box. Lets further
assume the user hits the '3' key.
Whatever code machinery within Windows that monitors external hardware will detect that a keyboard
key was pressed and if this program presently is the active program and one of the text boxes has
the focus Windows will access its internal data structures for our text boxes to find the Window
Procedure to which the WM_CHAR message it will generate should be sent. It will find that
fnEditSubClass() is that procedure and fnEditSubClass will receive the WM_CHAR message. Having
been 'called', fnEditSubClass() will determine through its switch logic structure that the message
is a WM_CHAR message so it will immediately call fnEditSubClass_OnKeyPress() passing the message
and associated parameters on unchanged. fnEditSubClass_OnKeyPress() will use its switch construct
to examine the wParam and we'll immediatly be in the various cases from 46 to 57 as the number '3'
has an asci code of 51. You can see how the switch logic construct works in terms of consecutive
elements. Unless a 'break;' statement is hit, execution will keep falling through the adjacent cases.
However, it will soon hit the return statement which calls CallWindowProc() and what this statement
does is pass all the so far unaltered message parameters to fnEditWndProc, which is the original
Window Procedure for the edit controls which we replaced in the SetWindowLong() calls, but the
address of which we saved in WNDPROC variable fnEditWndProc. Look below to assure yourself that
the 1st parameter of CallWindowProc() is a Window Procedure address and in our case its
fnEditWndProc. At this point, albiet some rather complex indirection was involved, the character
'3' will show up in the Width text box as if nothing unusual had happened! It should be clear from
this that Window Procedures both in our application and within Windows itself had access to this
character message generated by the press of a key. Had we not subclassed these text boxes, our
application would not have had access to the message before the internal edit control Window
Procedure.
Lets now take a look at what happens if, for example, the 'F' key is pressed, which key has no valid
use in this program.
Well, hitting the 'F' key while the cursor is in the Width text box will generate a WM_CHAR message
and it will be received in fnEditSubClass(). Because of the switch there program execution will be
routed to fnEditSubClass_OnKeyPress(). The switch there will then try to match up the wParam which
holds the key code with the various cases in the switch statement. The letter 'F' has an asci code
of 70 and therefore no match is made; execution will fall into the 'default' case and the function
will return with zero. This result of zero will also be returned from fnEditSubClass(). However,
the important point isn't what is returned by these functions in this case of when the 'F' key was
pressed but what isn't returned - the CallWindowProc() function doesn't get called in this case and
the keypress message carrying the letter F never makes it to the edit control Window Procedure! You
can hold down the letter F all day and none will ever show up in the text box. The reason is that
Windows is no longer giving the former edit control window procedure in our program the results of
keyboard entry. Its all being given to us; if we pass the key on to the original window procedure
by calling CallWindowProc() then the letter will show up. In this case though we just exit from the
functions with an arbitrary return value of zero. The point to take home here is that if we want
the text box to perform its normal role its our responsibility to pass messages on to it by calling
CallWindowProc().
There is a rather funny terminology programmers have adopted with regard to all this and the
terminology makes reference to something all of us do and easily understand and that is eat. Imagine
for a moment that several of us have sat down at the dinner table to eat. There is a lone biscuit
on the biscuit plate being passed around. When we are passed the plate with the lone biscuit our
choice is to either remove and eat the biscuit, or pass it on down the line so that someone else
might take it. That's kind of the situation with the 'chains' of Window Procedures of which Microsoft
is referring to above. In the case of the 'F' key above, it was passed to us and we 'ate' it; or
in programmer speak, we 'ate the message'. We didn't pass it on down the line by giving it to
CallWindowProc(), so therefore the message ended with us and the 'F' never showed up in the text box.
Enough of my heavy handed attempt at humor!
In the case of pressing the [ENTER] key while the cursor is blinking in the Width text box, our edit
control subclass KeyPress() procedure will again be called, and we'll soon be in the case IDC_EDIT_WIDTH.
All that needs to be done there is to recognize that the hwnd that came through in the parameter list
is the hwnd of the edit control which received the key press, and that its parent obtainable with the
GetParent() function will give us the main dialog's hWnd. With that we can find the HANDLE of the
Height text box using GetDlgItem() and the control id of the Height text box. Then its a simple matter
of using the SetFocus() function to set the focus there. Note that I didn't bother passing the [ENTER]
keypress on to the original edit control WndProc. I 'ate' it. Passing it on wouldn't have really hurt;
its just that nothing was to be gained through it, and on my computers anyway I ended up with the
'bong' sound; which I don't like.
In the case of WM_KEYDOWN messages with the edit controls, the logic is similiar to the [ENTER] and [TAB]
key cases. Just a matter of setting focus to the next/previous control in the tab sequence.
The button KeyPress() logic is a bit interesting - buttons do receive keypresses - not just mouse clicks.
SendMessage(hwnd,BM_CLICK,0,0);
However, if you are a real glutton for punishment you can send a complete WM_COMMAND message to the
main window complete with notification codes, control ids, the works...
SendMessage(hParent,WM_COMMAND,MAKEWPARAM(IDC_BUTTON_CALCULATE,BM_CLICK),(LPARAM)GetDlgItem(hParent,IDC_BUTTON_CALCULATE));
But I've a feeling you'll like the former better!
Finally, note that down in WinMain() the IsDialogMessage() function call is gone; that's because we've
picked up here with our subclassing our specialized tabbing and control navigation needs. The
IsDialogMessage() function gave us a fairly decent tab order, and it makes sense to use that if
possible. But if your app would strongly benifit from an arrangement like we've done here, subclassing
might be the way to go.
One final issue; some folks like to replace the original Window Procedure back in Windows internal data
structure when the app closes. My personal opinion is that this is more a matter of form rather than
an absolute requirement; but you've got the addresses saved in fnEditWndProc and fnButtonWndProc if you
feel compelled to do so with SetWindowLong(). I'm laboring under no such compulsion so I didn't do it
here, but wanted to make you aware of the issue.
*/
//Main.cpp
#include <windows.h>
#include <stdio.h>
#include "ProgEx40f.h"
#include "Output.h"
EVENTHANDLER MainHandler[3];
EVENTHANDLER OutputHandler[3];
WNDPROC fnEditWndProc, fnButtonWndProc;
long fnEditSubClass_OnKeyPress(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(wParam)
{
case 9: //tab key
case 13: //enter key
int iCtrlId=GetDlgCtrlID(hwnd);
HWND hParent=GetParent(hwnd);
switch(iCtrlId)
{
case IDC_EDIT_LENGTH:
SetFocus(GetDlgItem(hParent,IDC_EDIT_WIDTH));
return 0;
case IDC_EDIT_WIDTH:
SetFocus(GetDlgItem(hParent,IDC_EDIT_HEIGHT));
return 0;
case IDC_EDIT_HEIGHT:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CALCULATE));
return 0;
}
case 46: //Americam Decimal Point Or Period
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
case 48: //0
case 49: //1
case 50: //2 In these cases just pass the key on to
case 51: //3 the edit control's Window Procedure
case 52: //4
case 53: //5
case 54: //6
case 55: //7
case 56: //8
case 57: //9
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
default: //Eat the key!
return 0;
}
}
long fnEditSubClass_OnKeyDown(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
int iCtrlId=GetDlgCtrlID(hwnd);
HWND hParent=GetParent(hwnd);
switch(iCtrlId)
{
case IDC_EDIT_LENGTH:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CLEAR));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_EDIT_WIDTH));
return 0;
}
case IDC_EDIT_WIDTH:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_EDIT_LENGTH));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_EDIT_HEIGHT));
return 0;
}
case IDC_EDIT_HEIGHT:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_EDIT_WIDTH));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CALCULATE));
return 0;
}
}
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
long fnButtonSubClass_OnKeyPress(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
int iCtrlId=GetDlgCtrlID(hwnd);
HWND hParent=GetParent(hwnd);
switch(wParam)
{
case 9:
{
switch(iCtrlId)
{
case IDC_BUTTON_CALCULATE:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_DISPLAY));
return 0;
case IDC_BUTTON_DISPLAY:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CLEAR));
return 0;
case IDC_BUTTON_CLEAR:
SetFocus(GetDlgItem(hParent,IDC_EDIT_LENGTH));
return 0;
}
break;
}
case 13:
{
SendMessage(hwnd,BM_CLICK,0,0);
switch(iCtrlId)
{
case IDC_BUTTON_CALCULATE:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_DISPLAY));
return 0;
case IDC_BUTTON_DISPLAY:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CLEAR));
return 0;
case IDC_BUTTON_CLEAR:
SetFocus(GetDlgItem(hParent,IDC_EDIT_LENGTH));
return 0;
}
break;
}
}
return CallWindowProc(fnButtonWndProc,hwnd,msg,wParam,lParam);
}
long fnButtonSubClass_OnKeyDown(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
int iCtrlId=GetDlgCtrlID(hwnd);
HWND hParent=GetParent(hwnd);
switch(iCtrlId)
{
case IDC_BUTTON_CALCULATE:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_EDIT_HEIGHT));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_DISPLAY));
return 0;
}
case IDC_BUTTON_DISPLAY:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CALCULATE));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_CLEAR));
return 0;
}
case IDC_BUTTON_CLEAR:
switch(wParam)
{
case VK_UP:
SetFocus(GetDlgItem(hParent,IDC_BUTTON_DISPLAY));
return 0;
case VK_DOWN:
SetFocus(GetDlgItem(hParent,IDC_EDIT_LENGTH));
return 0;
}
}
return CallWindowProc(fnButtonWndProc,hwnd,msg,wParam,lParam);
}
long __stdcall fnEditSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return fnEditSubClass_OnKeyPress(hwnd,msg,wParam,lParam);
case WM_KEYDOWN:
return fnEditSubClass_OnKeyDown(hwnd,msg,wParam,lParam);
}
return CallWindowProc(fnEditWndProc,hwnd,msg,wParam,lParam);
}
long __stdcall fnButtonSubClass(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return fnButtonSubClass_OnKeyPress(hwnd,msg,wParam,lParam);
case WM_KEYDOWN:
return fnButtonSubClass_OnKeyDown(hwnd,msg,wParam,lParam);
}
return CallWindowProc(fnButtonWndProc,hwnd,msg,wParam,lParam);
}
long fnWndProc_OnCreate(lpWndEventArgs Wea)
{
char* szBuffer[]={"Ordinary Box","Box Of Junk","Beer Crate","Wine Box","Candy Box"};
DWORD dwStyle=WS_CHILD|WS_VISIBLE;
char szClassName[]="Output";
WNDCLASSEX wc;
HWND hCtl;
//Create Child Window Controls On Main Form
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
hCtl=CreateWindow("static","Choose Kind of Box",WS_CHILD|WS_VISIBLE,10,10,140,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindow("combobox","",dwStyle|CBS_DROPDOWNLIST|WS_VSCROLL,190,10,140,100,Wea->hWnd,(HMENU)IDC_COMBO_TYPE,Wea->hIns,0);
for(unsigned int i=0; i<5; i++)
SendMessage(hCtl,CB_INSERTSTRING,(WPARAM)-1,(LPARAM)szBuffer[i]);
SendMessage(hCtl,CB_SETCURSEL,(WPARAM)0,0); //|WS_TABSTOP
hCtl=CreateWindow("static","Length of Box",WS_CHILD|WS_VISIBLE,10,53,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","",dwStyle,150,50,70,25,Wea->hWnd,(HMENU)IDC_EDIT_LENGTH,Wea->hIns,0);
fnEditWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnEditSubClass);
hCtl=CreateWindow("static","Width of Box",WS_CHILD|WS_VISIBLE,10,93,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","",dwStyle,150,90,70,25,Wea->hWnd,(HMENU)IDC_EDIT_WIDTH,Wea->hIns,0);
fnEditWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnEditSubClass);
hCtl=CreateWindow("static","Height of Box",WS_CHILD|WS_VISIBLE,10,133,125,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","",dwStyle,150,130,70,25,Wea->hWnd,(HMENU)IDC_EDIT_HEIGHT,Wea->hIns,0);
fnEditWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnEditSubClass);
hCtl=CreateWindow("static","Volume",WS_CHILD|WS_VISIBLE,10,183,50,25,Wea->hWnd,(HMENU)-1,Wea->hIns,0);
hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","",dwStyle,62,180,335,25,Wea->hWnd,(HMENU)IDC_EDIT_VOLUME,Wea->hIns,0);
hCtl=CreateWindow("button","Calculate",dwStyle,245,50,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_CALCULATE,Wea->hIns,0);
fnButtonWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnButtonSubClass);
hCtl=CreateWindow("button","Display",dwStyle,245,90,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_DISPLAY,Wea->hIns,0);
fnButtonWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnButtonSubClass);
hCtl=CreateWindow("button","Clear",dwStyle,245,130,120,25,Wea->hWnd,(HMENU)IDC_BUTTON_CLEAR,Wea->hIns,0);
fnButtonWndProc=(WNDPROC)SetWindowLong(hCtl,GWL_WNDPROC,(LONG)fnButtonSubClass);
SetWindowText(Wea->hWnd,"Volume Of Your Box");
SetFocus(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE));
//Register Output Screen Class
wc.lpszClassName=szClassName, wc.lpfnWndProc=fnOutputProc;
wc.cbSize=sizeof (WNDCLASSEX), wc.style=CS_DBLCLKS;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION), wc.hInstance=Wea->hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION), wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH), wc.cbWndExtra=4;
wc.lpszMenuName=NULL, wc.cbClsExtra=0;
RegisterClassEx(&wc);
return 0;
}
void btnCalculate_OnClick(lpWndEventArgs Wea)
{
double dblLength,dblWidth,dblHeight,dblVolume;
char szBuffer[64],szBoxType[16],szText[64];
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16);
dblLength=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16);
dblWidth=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16);
dblHeight=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),szBoxType,16);
dblVolume=dblLength*dblWidth*dblHeight;
sprintf(szBuffer,"%8.2f",dblVolume);
strcpy(szText,"Your ");
strcat(szText,szBoxType);
strcat(szText," Has A Volume Of ");
strcat(szText,szBuffer);
SetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_VOLUME),szText);
}
void btnDisplay_OnClick(lpWndEventArgs Wea)
{
ProgramData ProDta;
char szBuffer[64];
HINSTANCE hIns;
HWND hWnd;
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH),szBuffer,16);
ProDta.dblLength=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH),szBuffer,16);
ProDta.dblWidth=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT),szBuffer,16);
ProDta.dblHeight=atof(szBuffer);
GetWindowText(GetDlgItem(Wea->hWnd,IDC_COMBO_TYPE),ProDta.szBoxType,16);
ProDta.dblVolume = ProDta.dblLength * ProDta.dblWidth * ProDta.dblHeight;
hIns=GetModuleHandle(0);
hWnd=CreateWindow("Output","Output Screen",WS_OVERLAPPEDWINDOW,650,300,350,200,0,0,hIns,&ProDta);
ShowWindow(hWnd,SW_SHOWNORMAL);
}
void btnClear_OnClick(lpWndEventArgs Wea)
{
char szBuffer[4];
HWND hWnd;
szBuffer[0]=0;
hWnd=GetDlgItem(Wea->hWnd,IDC_EDIT_WIDTH);
SetWindowText(hWnd,szBuffer);
hWnd=GetDlgItem(Wea->hWnd,IDC_EDIT_HEIGHT);
SetWindowText(hWnd,szBuffer);
hWnd=GetDlgItem(Wea->hWnd,IDC_EDIT_VOLUME);
SetWindowText(hWnd,szBuffer);
hWnd=GetDlgItem(Wea->hWnd,IDC_EDIT_LENGTH);
SetWindowText(hWnd,szBuffer);
SetFocus(hWnd);
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_BUTTON_CALCULATE:
btnCalculate_OnClick(Wea);
break;
case IDC_BUTTON_DISPLAY:
btnDisplay_OnClick(Wea);
break;
case IDC_BUTTON_CLEAR:
btnClear_OnClick(Wea);
break;
}
return 0;
}
long fnWndProc_OnClose(lpWndEventArgs Wea)
{
HWND hOutput;
do //Search And Destroy Mission For Any Output Windows Hanging Around
{
hOutput=FindWindowEx(0,0,"Output","Output Screen");
if(hOutput)
SendMessage(hOutput,WM_CLOSE,0,0);
else
break;
}while(TRUE);
DestroyWindow(Wea->hWnd);
PostQuitMessage(0);
return 0;
}
void AttachEventHandlers(void) //This procedure maps windows messages to the
{ //procedure which handles them.
MainHandler[0].Code=WM_CREATE, MainHandler[0].fnPtr=fnWndProc_OnCreate;
MainHandler[1].Code=WM_COMMAND, MainHandler[1].fnPtr=fnWndProc_OnCommand;
MainHandler[2].Code=WM_CLOSE, MainHandler[2].fnPtr=fnWndProc_OnClose;
OutputHandler[0].Code=WM_CREATE, OutputHandler[0].fnPtr=fnOutputProc_OnCreate;
OutputHandler[1].Code=WM_PAINT, OutputHandler[1].fnPtr=fnOutputProc_OnPaint;
OutputHandler[2].Code=WM_CLOSE, OutputHandler[2].fnPtr=fnOutputProc_OnClose;
}
long __stdcall fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea; //This procedure loops through the EVENTHANDER array
//of structs to try to make a match with the msg parameter
for(unsigned int i=0; i<3; i++) //of the WndProc. If a match is made the event handling
{ //procedure is called through a function pointer -
if(MainHandler[i].Code==msg) //(EventHandler[i].fnPtr). If no match is found the
{ //msg is passed onto DefWindowProc().
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*MainHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
char szClassName[]="Form3";
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
AttachEventHandlers();
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX); wc.style=CS_DBLCLKS;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=0;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,100,400,415,250,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
//ProgEx40f.h
#ifndef PROGEX40F_H
#define PROGEX40F_H
#define IDC_COMBO_TYPE 1500
#define IDC_EDIT_LENGTH 1505
#define IDC_EDIT_WIDTH 1510
#define IDC_EDIT_HEIGHT 1515
#define IDC_EDIT_VOLUME 1520
#define IDC_BUTTON_CALCULATE 1525
#define IDC_BUTTON_DISPLAY 1530
#define IDC_BUTTON_CLEAR 1535
typedef struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
}WndEventArgs, *lpWndEventArgs;
struct EVENTHANDLER
{
unsigned int Code;
long (*fnPtr)(lpWndEventArgs);
};
struct ProgramData
{
double dblLength;
double dblWidth;
double dblHeight;
double dblVolume;
char szBoxType[16];
};
#endif
//Output.cpp
#include <windows.h>
#include <stdio.h>
#include "ProgEx40f.h"
#include "Output.h"
extern EVENTHANDLER OutputHandler[3];
long fnOutputProc_OnCreate(lpWndEventArgs Wea)
{
CREATESTRUCT *pCreateStruct;
ProgramData* pProgramData;
ProgramData* pMain;
pCreateStruct=(CREATESTRUCT*)Wea->lParam;
pMain=(ProgramData*)pCreateStruct->lpCreateParams;
pProgramData=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
if(pProgramData)
{
SetWindowLong(Wea->hWnd,0,(long)pProgramData);
pProgramData->dblLength=pMain->dblLength;
pProgramData->dblWidth=pMain->dblWidth;
pProgramData->dblHeight=pMain->dblHeight;
pProgramData->dblVolume=pMain->dblVolume;
strcpy(pProgramData->szBoxType,pMain->szBoxType);
}
else //If we can't allocate the memory there isn't much use
return -1; //continuing so -1 causes CreateWindow to fail.
return 0;
}
long fnOutputProc_OnPaint(lpWndEventArgs Wea)
{
char szBuffer[64],szNumber[16];
ProgramData* pProgramData;
HFONT hFont,hTmp;
PAINTSTRUCT ps;
HDC hDC;
hDC=BeginPaint(Wea->hWnd,&ps);
pProgramData=(ProgramData*)GetWindowLong(Wea->hWnd,0);
hFont=CreateFont(20,0,0,0,FW_BOLD,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH,"Courier New");
hTmp=(HFONT)SelectObject(hDC,hFont);
strcpy(szBuffer,"Box Type = "), strcat(szBuffer,pProgramData->szBoxType);
TextOut(hDC,40,20,szBuffer,strlen(szBuffer));
sprintf(szNumber,"%8.2f",pProgramData->dblLength);
strcpy(szBuffer,"Length = "), strcat(szBuffer,szNumber);
TextOut(hDC,40,40,szBuffer,strlen(szBuffer));
sprintf(szNumber,"%8.2f",pProgramData->dblWidth);
strcpy(szBuffer,"Width = "), strcat(szBuffer,szNumber);
TextOut(hDC,40,60,szBuffer,strlen(szBuffer));
sprintf(szNumber,"%8.2f",pProgramData->dblHeight);
strcpy(szBuffer,"Height = "), strcat(szBuffer,szNumber);
TextOut(hDC,40,80,szBuffer,strlen(szBuffer));
sprintf(szNumber,"%8.2f",pProgramData->dblVolume);
strcpy(szBuffer,"Volume = "), strcat(szBuffer,szNumber);
TextOut(hDC,40,100,szBuffer,strlen(szBuffer));
SelectObject(hDC,hTmp);
DeleteObject(hFont);
EndPaint(Wea->hWnd,&ps);
return 0;
}
long fnOutputProc_OnClose(lpWndEventArgs Wea)
{
ProgramData* pProgramData=NULL;
pProgramData=(ProgramData*)GetWindowLong(Wea->hWnd,0);
if(pProgramData)
GlobalFree(pProgramData);
DestroyWindow(Wea->hWnd);
return 0;
}
long __stdcall fnOutputProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea; //This procedure loops through the EVENTHANDER array
//of structs to try to make a match with the msg parameter
for(unsigned int i=0; i<3; i++) //of the WndProc. If a match is made the event handling
{ //procedure is called through a function pointer -
if(OutputHandler[i].Code==msg) //(OutputHandler[i].fnPtr). If no match is found the
{ //msg is passed onto DefWindowProc().
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*OutputHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
//Output.h
#ifndef OUTPUT_H
#define OUTPUT_H
long fnOutputProc_OnCreate(lpWndEventArgs);
long fnOutputProc_OnPaint(lpWndEventArgs);
long fnOutputProc_OnClose(lpWndEventArgs);
long __stdcall fnOutputProc(HWND,unsigned int,WPARAM,LPARAM);
#endif
Attached is zip of ProgEx40f
And couldn't hardly do without a PowerBASIC version of the above...
'ProgEx40g -- PowerBASIC 9.02 Version Of Our BoxVolume/Form3 Program With Extensive Subclassing
'
' In this PowerBASIC version of the program I subclassed the Length, Width, and
' Height text boxes, and the Calculate and Display buttons. Also, I added a
' Clear button and subclassed that too. In addition, I added some simple data
' entry validation to the text boxes in that they will no longer accept non-
' numeric input.
'
' Due to the subclassing, you can now use the [ENTER] key to navigate through the
' controls, as well as the up and down arrow keys.
'
#Compile Exe
#Include "Win32api.inc"
#Include "BoxVolume.inc"
#Include "frmOutput.inc"
Function fnEditSubClass_OnKeyPress(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local iCtrlId,hParent As Dword
Select Case As Long wParam
Case 9, 13 '[TAB], [ENTER]
iCtrlId=GetDlgCtrlID(hWnd)
hParent=GetParent(hWnd)
Select Case As Long iCtrlId
Case %IDC_EDIT_LENGTH
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_WIDTH))
Exit Function
Case %IDC_EDIT_WIDTH
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_HEIGHT))
Exit Function
Case %IDC_EDIT_HEIGHT
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CALCULATE))
Exit Function
End Select
Case 46
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
Exit Function
Case 48 To 57
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
Exit Function
End Select
Function=0
End Function
Function fnEditSubClass_OnKeyDown(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local hParent As Dword
hParent=GetParent(hWnd)
Select Case As Long GetDlgCtrlID(hWnd)
Case %IDC_EDIT_LENGTH
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CLEAR))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_WIDTH))
Function=0 : Exit Function
End Select
Case %IDC_EDIT_WIDTH
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_LENGTH))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_HEIGHT))
Function=0 : Exit Function
End Select
Case %IDC_EDIT_HEIGHT
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_WIDTH))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CALCULATE))
Function=0 : Exit Function
End Select
End Select
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnButtonSubClass_OnKeyPress(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local hParent As Dword
hParent=GetParent(hWnd)
If wParam=9 Or wParam=13 Then
If wParam=13 Then
Call SendMessage(hWnd,%BM_CLICK,0,0)
End If
Select Case As Long GetDlgCtrlID(hWnd)
Case %IDC_BUTTON_CALCULATE
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_DISPLAY))
Function=0 : Exit Function
Case %IDC_BUTTON_DISPLAY
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CLEAR))
Function=0 : Exit Function
Case %IDC_BUTTON_CLEAR
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_LENGTH))
Function=0 : Exit Function
End Select
End If
Function=CallWindowProc(fnButtonWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnButtonSubClass_OnKeyDown(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Local hParent As Dword
hParent=GetParent(hWnd)
Select Case As Long GetDlgCtrlID(hWnd)
Case %IDC_BUTTON_CALCULATE
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_HEIGHT))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_DISPLAY))
Function=0 : Exit Function
End Select
Case %IDC_BUTTON_DISPLAY
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CALCULATE))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_CLEAR))
Function=0 : Exit Function
End Select
Case %IDC_BUTTON_CLEAR
Select Case As Long wParam
Case %VK_UP
Call SetFocus(GetDlgItem(hParent,%IDC_BUTTON_DISPLAY))
Function=0 : Exit Function
Case %VK_DOWN
Call SetFocus(GetDlgItem(hParent,%IDC_EDIT_LENGTH))
Function=0 : Exit Function
End Select
End Select
Function=CallWindowProc(fnButtonWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnEditSubClass(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case As Long wMsg
Case %WM_CHAR
Function=fnEditSubClass_OnKeyPress(hWnd,wMsg,wParam,lParam)
Exit Function
Case %WM_KEYDOWN
Function=fnEditSubClass_OnKeyDown(hWnd,wMsg,wParam,lParam)
Exit Function
End Select
Function=CallWindowProc(fnEditWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnButtonSubClass(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case As Long wMsg
Case %WM_CHAR
Function=fnButtonSubClass_OnKeyPress(hWnd,wMsg,wParam,lParam)
Case %WM_KEYDOWN
Function=fnButtonSubClass_OnKeyDown(hWnd,wMsg,wParam,lParam)
End Select
Function=CallWindowProc(fnButtonWndProc,hWnd,wMsg,wParam,lParam)
End Function
Function fnWndProc_OnCreate(Wea As WndEventArgs) As Long
Local pCreateStruct As CREATESTRUCT Ptr
Local szClassName As Asciiz*16
Local szBuffer() As Asciiz*16
Local hCtl,dwStyle As Dword
Local wc As WNDCLASSEX
Register i As Long
pCreateStruct=Wea.lParam
Wea.hInst=@pCreateStruct.hInstance
dwStyle=%WS_CHILD Or %WS_VISIBLE
Redim szBuffer(4) As Asciiz*16
szBuffer(0)="Ordinary Box" : szBuffer(1)="Box Of Junk" : szBuffer(2)="Beer Crate" : szBuffer(3)="Wine Box" : szBuffer(4)="Candy Box"
hCtl=CreateWindow("static","Choose Kind of Box",%WS_CHILD Or %WS_VISIBLE,10,10,140,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindow("combobox","",dwStyle Or %CBS_DROPDOWNLIST Or %WS_VSCROLL,150,10,140,100,Wea.hWnd,%IDC_COMBO_TYPE,Wea.hInst,Byval 0)
For i=0 To 4
SendMessage(hCtl,%CB_INSERTSTRING,-1,Varptr(szBuffer(i)))
Next i
SendMessage(hCtl,%CB_SETCURSEL,0,0)
Erase szBuffer
hCtl=CreateWindow("static","Length of Box",%WS_CHILD Or %WS_VISIBLE,10,53,125,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",dwStyle,150,50,70,25,Wea.hWnd,%IDC_EDIT_LENGTH,Wea.hInst,Byval 0)
fnEditWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnEditSubClass))
hCtl=CreateWindow("static","Width of Box",%WS_CHILD Or %WS_VISIBLE,10,93,125,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",dwStyle,150,90,70,25,Wea.hWnd,%IDC_EDIT_WIDTH,Wea.hInst,Byval 0)
fnEditWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnEditSubClass))
hCtl=CreateWindow("static","Height of Box",%WS_CHILD Or %WS_VISIBLE,10,133,125,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",dwStyle,150,130,70,25,Wea.hWnd,%IDC_EDIT_HEIGHT,Wea.hInst,Byval 0)
fnEditWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnEditSubClass))
hCtl=CreateWindow("static","Volume",%WS_CHILD Or %WS_VISIBLE,10,183,50,25,Wea.hWnd,-1,Wea.hInst,Byval 0)
hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",dwStyle,60,180,335,25,Wea.hWnd,%IDC_EDIT_VOLUME,Wea.hInst,Byval 0)
hCtl=CreateWindow("button","Calculate",dwStyle,245,48,120,30,Wea.hWnd,%IDC_BUTTON_CALCULATE,Wea.hInst,Byval 0)
fnButtonWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnButtonSubClass))
hCtl=CreateWindow("button","Display",dwStyle,245,88,120,30,Wea.hWnd,%IDC_BUTTON_DISPLAY,Wea.hInst,Byval 0)
fnButtonWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnButtonSubClass))
hCtl=CreateWindow("button","Clear",dwStyle,245,128,120,30,Wea.hWnd,%IDC_BUTTON_CLEAR,Wea.hInst,Byval 0)
fnButtonWndProc=SetWindowLong(hCtl,%GWL_WNDPROC,CodePtr(fnButtonSubClass))
Call SetWindowText(Wea.hWnd,"Box Volume With Child Control Subclassing")
'Register Output Screen Class
szClassName="Output"
wc.lpszClassName=Varptr(szClassName) : wc.lpfnWndProc=CodePtr(fnOutputProc)
wc.cbSize=Sizeof(WNDCLASSEX) : wc.style=%CS_DBLCLKS
wc.hIcon=LoadIcon(%NULL,ByVal %IDI_APPLICATION) : wc.hInstance=Wea.hInst
wc.hIconSm=LoadIcon(%NULL, ByVal %IDI_APPLICATION) : wc.hCursor=LoadCursor(%NULL,ByVal %IDC_ARROW)
wc.hbrBackground=GetStockObject(%WHITE_BRUSH) : wc.cbWndExtra=4
wc.lpszMenuName=%NULL : wc.cbClsExtra=0
Call RegisterClassEx(wc)
fnWndProc_OnCreate=0
End Function
Function btnCalculate_OnClick(Wea As WndEventArgs) As Long
Local szBuffer As Asciiz*64
Local bx As CBox
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_LENGTH),szBuffer,64) : bx.dblLength=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_WIDTH),szBuffer,64) : bx.dblWidth=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_HEIGHT),szBuffer,64) : bx.dblHeight=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_COMBO_TYPE),bx.szBoxType,16)
bx.dblVolume = bx.dblLength * bx.dblWidth * bx.dblHeight
szBuffer="The Volume Of Your " & bx.szBoxType & " Is " & Str$(bx.dblVolume) & "."
Call SetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_VOLUME),szBuffer)
Function=0
End Function
Function btnDisplay_OnClick(Wea As WndEventArgs) As Long
Local szBuffer As Asciiz * 64
Local hIns,hOutput As Dword
Local bx As CBox
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_LENGTH),szBuffer,64) : bx.dblLength=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_WIDTH),szBuffer,64) : bx.dblWidth=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_EDIT_HEIGHT),szBuffer,64) : bx.dblHeight=Val(szBuffer)
Call GetWindowText(GetDlgItem(Wea.hWnd,%IDC_COMBO_TYPE),bx.szBoxType,16)
bx.dblVolume = bx.dblLength * bx.dblWidth * bx.dblHeight
hIns=GetModuleHandle("")
hOutput=CreateWindow("Output","Output Screen",%WS_OVERLAPPEDWINDOW,650,300,350,200,0,0,hIns,Byval Varptr(bx))
Call ShowWindow(hOutput,%SW_SHOWNORMAL)
Function=0
End Function
Function btnClear_OnClick(Wea As WndEventArgs) As Long
Local hEdit As Dword
hEdit=GetDlgItem(Wea.hWnd,%IDC_EDIT_WIDTH)
Call SetWindowText(hEdit,"")
hEdit=GetDlgItem(Wea.hWnd,%IDC_EDIT_HEIGHT)
SetWindowText(hEdit,"")
hEdit=GetDlgItem(Wea.hWnd,%IDC_EDIT_VOLUME)
SetWindowText(hEdit,"")
hEdit=GetDlgItem(Wea.hWnd,%IDC_EDIT_LENGTH)
SetWindowText(hEdit,"")
SetFocus(hEdit)
Function=0
End Function
Function fnWndProc_OnCommand(Wea As WndEventArgs) As Long
Select Case As Long Lowrd(Wea.wParam)
Case %IDC_BUTTON_CALCULATE
Call btnCalculate_OnClick(Wea)
Case %IDC_BUTTON_DISPLAY
Call btnDisplay_OnClick(Wea)
Case %IDC_BUTTON_CLEAR
Call btnClear_OnClick(Wea)
End Select
fnWndProc_OnCommand=0
End Function
Function fnWndProc_OnClose(wea As WndEventArgs) As Long
Local hOutput As Dword
Do
hOutput=FindWindowEx(0,0,"Output","Output Screen")
If hOutput Then
Call SendMessage(hOutput,%WM_CLOSE,0,0)
Else
Exit Do
End If
Loop
Call PostQuitMessage(0)
fnWndProc_OnClose=0
End Function
Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
Static wea As WndEventArgs
Register iReturn As Long
Register i As Long
For i=0 To 2
If wMsg=MsgHdlr(i).wMessage Then
wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
Call Dword MsgHdlr(i).dwFnPtr Using FnPtr(wea) To iReturn
fnWndProc=iReturn
Exit Function
End If
Next i
fnWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function
Sub AttachMessageHandlers() 'Associate Windows Message With Message Handlers
ReDim OutputHdlr(2) As MessageHandler
Redim MsgHdlr(2) As MessageHandler
MsgHdlr(0).wMessage=%WM_CREATE : MsgHdlr(0).dwFnPtr=CodePtr(fnWndProc_OnCreate)
MsgHdlr(1).wMessage=%WM_COMMAND : MsgHdlr(1).dwFnPtr=CodePtr(fnWndProc_OnCommand)
MsgHdlr(2).wMessage=%WM_CLOSE : MsgHdlr(2).dwFnPtr=CodePtr(fnWndProc_OnClose)
OutputHdlr(0).wMessage=%WM_CREATE : OutputHdlr(0).dwFnPtr=CodePtr(fnOutputProc_OnCreate)
OutputHdlr(1).wMessage=%WM_PAINT : OutputHdlr(1).dwFnPtr=CodePtr(fnOutputProc_OnPaint)
OutputHdlr(2).wMessage=%WM_CLOSE : OutputHdlr(2).dwFnPtr=CodePtr(fnOutputProc_OnClose)
End Sub
Function WinMain(ByVal hIns As Long,ByVal hPrev As Long,ByVal lpCL As Asciiz Ptr,ByVal iShow As Long) As Long
Local szAppName As Asciiz*16
Local wc As WndClassEx
Local hWnd As Dword
Local Msg As tagMsg
szAppName="Form3" : Call AttachMessageHandlers()
wc.lpszClassName=VarPtr(szAppName) : wc.lpfnWndProc=CodePtr(fnWndProc)
wc.cbClsExtra=0 : wc.cbWndExtra=0
wc.style=%CS_HREDRAW Or %CS_VREDRAW : wc.hInstance=hIns
wc.cbSize=SizeOf(wc) : wc.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)
wc.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW) : wc.hbrBackground=%COLOR_BTNFACE+1
wc.lpszMenuName=%NULL
Call RegisterClassEx(wc)
hWnd=CreateWindow(szAppName,"Form3",%WS_OVERLAPPEDWINDOW,100,400,410,250,0,0,hIns,ByVal 0)
Call ShowWindow(hWnd,iShow)
While GetMessage(Msg,%NULL,0,0)
Call TranslateMessage(Msg)
Call DispatchMessage(Msg)
Wend
Function=msg.wParam
End Function
'BoxVolume.inc
%IDC_COMBO_TYPE = 1500
%IDC_EDIT_LENGTH = 1505
%IDC_EDIT_WIDTH = 1510
%IDC_EDIT_HEIGHT = 1515
%IDC_EDIT_VOLUME = 1520
%IDC_BUTTON_CALCULATE = 1525
%IDC_BUTTON_DISPLAY = 1530
%IDC_BUTTON_CLEAR = 1535
Type WndEventArgs
wParam As Long
lParam As Long
hWnd As Dword
hInst As Dword
End Type
Type MessageHandler
wMessage As Long
dwFnPtr As Dword
End Type
Type CBox
dblLength As Double
dblWidth As Double
dblHeight As Double
dblVolume As Double
szBoxType As Asciiz*16
End Type
Global MsgHdlr() As MessageHandler
Global fnEditWndProc As Dword
Global fnButtonWndProc As Dword
Declare Function FnPtr(wea As WndEventArgs) As Long
'frmOutput.inc
Global OutputHdlr() As MessageHandler
Function fnOutputProc_OnCreate(Wea As WndEventArgs) As Long
Local ptrBox As CBox Ptr, ptrOutputBox As CBox Ptr
Local pCreateStruct As CREATESTRUCT Ptr
pCreateStruct=Wea.lParam
ptrBox=@pCreateStruct.lpCreateParams
ptrOutputBox=GlobalAlloc(%GPTR,SizeOf(CBox))
If ptrOutputBox Then
Type Set @ptrOutputBox = @ptrBox
Call SetProp(Wea.hWnd,"Our Box",ptrOutputBox)
Else
Function=-1
End If
Function=0
End Function
Function fnOutputProc_OnPaint(Wea As WndEventArgs) As Long
Local szBuffer As Asciiz*64
Local strField As String*8
Local hFont,hTmp As Dword
Local ptrBox As CBox Ptr
Local ps As PAINTSTRUCT
Local hDC As Dword
hDC=BeginPaint(Wea.hWnd,ps)
hFont=CreateFont _
( _
20,0,0,0,%FW_BOLD,0,0,0,%ANSI_CHARSET, _
%OUT_DEFAULT_PRECIS,%CLIP_DEFAULT_PRECIS, _
%PROOF_QUALITY,%DEFAULT_PITCH,"Courier New" _
)
hTmp=SelectObject(hDC,hFont)
ptrBox=GetProp(Wea.hWnd,"Our Box")
szBuffer="Box Type = " & @ptrBox.szBoxType
TextOut(hDC,40,20,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblLength,"###0.#0")
szBuffer="Length = " & strField
TextOut(hDC,40,40,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblWidth,"###0.#0")
szBuffer="Width = " & strField
TextOut(hDC,40,60,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblHeight,"###0.#0")
szBuffer="Height = " & strField
TextOut(hDC,40,80,szBuffer,Len(szBuffer))
RSet strField=Format$(@ptrBox.dblVolume,"####0.#0")
szBuffer="Volume = " & strField
TextOut(hDC,40,100,szBuffer,Len(szBuffer))
Call SelectObject(hDC,hTmp)
Call DeleteObject(hFont)
Call EndPaint(Wea.hWnd,ps)
fnOutputProc_OnPaint=0
End Function
Function fnOutputProc_OnClose(Wea As WndEventArgs) As Long
Local pBox As CBox Ptr
pBox=RemoveProp(Wea.hWnd,"Our Box")
If pBox Then
Call GlobalFree(pBox)
End If
Call DestroyWindow(Wea.hWnd)
fnOutputProc_OnClose=0
End Function
Function fnOutputProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
Static Wea As WndEventArgs
Register iReturn As Long
Register i As Long
For i=0 To 2
If wMsg=OutputHdlr(i).wMessage Then
Wea.hWnd=hWnd: Wea.wParam=wParam: Wea.lParam=lParam
Call Dword OutputHdlr(i).dwFnPtr Using FnPtr(Wea) To iReturn
fnOutputProc=iReturn
Exit Function
End If
Next i
fnOutputProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function
Hi Frederick!
I was completly riveted by your detailed Description!
Thank you, good Work!
Great! I'm always glad to here when someone has found something useful!