/*
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