• Welcome to Jose's Read Only Forum 2023.
 

ProgEx40 -- Window Classes, Child Controls, Multiple Forms - Form3 Project

Started by Frederick J. Harris, December 03, 2009, 04:12:00 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris


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

Frederick J. Harris

#1

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

Frederick J. Harris

#2

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

Frederick J. Harris

#3

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

Frederick J. Harris

#4

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

Frederick J. Harris

#5
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

Frederick J. Harris

#6

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


Frederick J. Harris

#7

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

Frederick J. Harris

#8

'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

Frederick J. Harris

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

Frederick J. Harris

#10

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

Frederick J. Harris


Frederick J. Harris

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

Jürgen Huhn

Hi Frederick!

I was completly riveted by your detailed Description!

Thank you, good Work!
.¸.•'´¯)¸.•'´¯)¸.•'´¯)¸.•'´¯)
¤ª"˜¨¨¯¯¨¨˜"ª¤....¤ ª"˜¨

Frederick J. Harris

Great!  I'm always glad to here when someone has found something useful!