home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / mnth0101.zip / Sharf / subclass.doc < prev    next >
Text File  |  1992-01-18  |  24KB  |  573 lines

  1. Column Name: Advanced PM Programming
  2. Column Title: Writing Your Own Controls
  3.  
  4. Published in the January 1992 issue of "OS/2 Monthly," JDS
  5. Publications.
  6.  
  7. by:  Guy Scharf
  8. (c) Copyright 1991 Software Architects, Inc.
  9.  
  10.  
  11. Introduction
  12.  
  13. Welcome to the first "Programming the PM" column.  I
  14. plan to cover different aspects of OS/2 Presentation
  15. Manager programming in these columns.  Some of the
  16. columns will focus on specific programming techniques. 
  17. Others will cover broader topics.
  18.  
  19. This is the first issue of OS/2 Monthly and this column, so
  20. let me introduce myself.  I am a consultant specializing in
  21. developing OS/2 Presentation Manager products for our
  22. clients.  I help teach IBM Developer Assistance Program
  23. workshops for developers converting their applications to
  24. PM and am a sysop on the IBM OS/2 and Computer
  25. Consultant's Forums on CompuServe.  These columns will
  26. be based on work we are doing currently and questions you
  27. ask.
  28.  
  29. In this first column, we will look at writing custom
  30. controls.
  31.  
  32. In a PM application, one window may "own" another. 
  33. When one window is owned by another, the owned window
  34. is often referred to as a "control window" of the owner. 
  35. Control windows usually have a relatively simple structure,
  36. and are often information input mechanisms for the owner. 
  37. A control window often receives user input in the form of
  38. keystrokes and mouse operations, collects that user input,
  39. and notifies the owner of selected events.  Control windows
  40. may also be used for output.  The title bar, system menu,
  41. and minimize and maximize buttons are all control windows
  42. of the frame in a standard window.  All of the windows
  43. you see inside a dialog are control windows.  Programming
  44. a Presentation Manager application often requires extensive
  45. work with dialogs and control windows to achieve the
  46. desired function.
  47.  
  48.  
  49. Why Write Your Own Control?
  50.  
  51. OS/2 Presentation Manager comes with a variety of built-in
  52. controls -- entry fields, push buttons, static text, list boxes,
  53. combo boxes, and more.  The PM developer quickly finds
  54. that these controls, while helpful, are not sufficient for the
  55. application.  Entry fields are useful, but how about an entry
  56. field that accepts only digits, or one that accepts only
  57. uppercase letters?  List boxes are great, but how do you put
  58. multiple columns in a list box, or how do you drag a list
  59. box item from one location to another?
  60.  
  61. The first step an OS/2 programmer takes is usually to write
  62. the processing code in the dialog procedure.  You examine
  63. the WM_CONTROL messages from the control and
  64. perform any required processing.  The result works fine. 
  65. Next, you want a control of the same type in another
  66. dialog.  So, you modularize the code you wrote before and
  67. build a subroutine library or DLL.  Now, your dialog
  68. procedures need only make selected calls to perform the
  69. required processing.  But, with this approach, you still have
  70. special handling in the dialog procedure for each different
  71. type of control.
  72.  
  73. A custom control is even easier to use.  With a custom
  74. control, you effectively create a new control that operates
  75. exactly as you want.  To use it, you need merely name the
  76. control in the resource script, just as you do for any system
  77. supplied control.  No special processing is required in the
  78. dialog.  You set and retrieve information from the control
  79. in the same way as you do with system-supplied controls.
  80.  
  81. We'll look here at how to write such a custom control. 
  82. The approach we use is "subclassing," where we refine the
  83. operation of an existing control.  You can also write your
  84. own control from scratch, without basing it on any system
  85. control classes, but it is often easier to derive your control
  86. from an existing, similar control than to write it all
  87. yourself.
  88.  
  89.  
  90. What is Subclassing?
  91.  
  92. "Subclassing" is a feature of PM that allows one window
  93. to be based on another.  Every window in PM has a
  94. procedure associated with it.  The procedure implements
  95. the behavior of the window.  Just as your window
  96. procedure implements the behavior of your main window,
  97. system-supplied procedures implement the behavior of entry
  98. fields, list boxes, and other control windows.
  99.  
  100. PM passes messages to windows by calling the window
  101. procedure associated with each window.  When you
  102. subclass a window, you interpose your procedure between
  103. PM and the procedure originally associated with the
  104. window.  You can process as many or as few of the
  105. messages as you like; you can process them, alter them,
  106. discard them, or pass them to the original procedure for
  107. processing.  When you pass them to the original window
  108. procedure, that procedure performs its normal processing. 
  109. By processing selected messages, we alter the behavior of
  110. the control we are subclassing, and create a new type of
  111. control.
  112.  
  113. A control to convert input to uppercase and reject non-
  114. printable characters is useful for some input.  We can
  115. subclass the entry field control and modify it to operate this
  116. way.  All we have to do is to examine each WM_CHAR
  117. message that comes to the window, and either convert the
  118. character to uppercase before passing the WM_CHAR
  119. message on to the standard entry field procedure, or reject
  120. the character with a warning beep.  We need do nothing
  121. else.  A more complex custom entry field control could
  122. check that the characters typed matched a "picture mask"
  123. or that only valid complex numbers were entered.  The
  124. processing would be similar, except that conditions for
  125. determining what input was acceptable would require more
  126. complex code.  
  127.  
  128.  
  129. Making Your Own Control
  130.  
  131. Here we'll explore each step of creating and using a custom
  132. control to convert input to uppercase.  One API that you
  133. will not see here is WinSubclassWindow().  We handle the
  134. subclassing without using that call.
  135.  
  136. Every control has an identifier.  System-supplied controls
  137. are predefined as WC_BUTTON, WC_ENTRYFIELD, etc. 
  138. We define our own WC_UDUPPER class as a string
  139. "UpperCase."  In the resource script (subclass.rc, listing
  140. 4), we simply include a CONTROL statement and specify
  141. the control type as WC_UDUPPER.  Since we are
  142. subclassing the entry field control, we may also want to set
  143. specific entry field styles.  We use EF_LEFT and
  144. EF_MARGIN in this example.
  145.  
  146. Before opening the dialog box, we must register our new
  147. control classes.  This requires adding one call to the main
  148. program (subclass.c, listing 1) -- a call to our
  149. RegisterEntryFieldClasses() function.  In a full application,
  150. this might be done at the same time as other window
  151. classes for the application are registered.  While the system
  152. supplied window classes are pre-registered, our custom
  153. class is not, so we have to add this one call.
  154.  
  155. The registration procedure is contained in the entry field
  156. module (entryfld.c, listing 2).  Before registering our
  157. custom control class name, we call WinQueryClassInfo() to
  158. ask the system for information on the WC_ENTRYFIELD
  159. class.  Two of the elements returned are the address of the
  160. standard window procedure for that class, and the number
  161. of bytes of window words for this class.  We save the
  162. function address in a static variable for later use when our
  163. procedure is entered.  We will need to call this routine to
  164. pass messages on to it for default entry field processing. 
  165. When we register the class, we have to specify the same
  166. number of bytes for window words as the original entry
  167. field class.  These window words are used internally by the
  168. entry field class.  We have to ensure that the correct
  169. number of bytes is created for a window of our new class
  170. too, or the entry field procedure will not function properly
  171. when we pass messages to it.
  172.  
  173. The entry field procedure address is used only in this
  174. module.  We therefore define the variable as static in this
  175. module, keeping the scope as narrow as possible.  We
  176. could avoid the need for even this static variable by calling
  177. WinQueryClassInfo() in our UDUpperWndProc()
  178. procedure.  However, since the function address is never
  179. going to change, we can improve performance by saving it
  180. in a static variable.
  181.  
  182. With this information, we have everything we need to
  183. perform subclasssing: our own window procedure (which
  184. will receive all messages first) and the address of the
  185. system-supplied procedure to implement the standard entry
  186. field class.
  187.  
  188. This approach offers several advantages over using
  189. WinSubclassWindow().  We have to make a single
  190. registration call, and then simply use our new class name
  191. wherever required in resource scripts.  With
  192. WinSubclassWindow(), in place of a single registration call,
  193. we must program a specific WinSubclassWindow() call
  194. every time we want one of our new controls.  In effect, we
  195. are trading declarative code for procedural code. 
  196. Additionally, creating our own control allows our control
  197. to process the WM_CREATE message, if desired.  If you
  198. issue a WinSubclassWindow() call from within
  199. WM_INITDLG processing, the subclassing will not occur
  200. until after the control window has been created.
  201.  
  202.  
  203. How the Control Works
  204.  
  205. Now that the class is registered, the dialog box can be
  206. processed with WinDlgBox().  WinDlgBox() will create all
  207. of the windows within it automatically.  Since our window
  208. class has been registered, the window will be created and
  209. appropriate messages sent to our UDUpperWndProc()
  210. procedure.  (If we had failed to register our custom control
  211. class first, WinDlgBox() would fail with a "resource not
  212. found" error.)
  213.  
  214. The UDUpperWndProc() procedure receives all messages
  215. for the control.  We check to see if the message is a
  216. WM_CHAR message.  If it is, and if it is a key
  217. downstroke and the key is an alphabetic key, and it is lower
  218. case, we convert it to uppercase.  If the character is not a
  219. printable character, we call WinAlarm() to notify the user
  220. of the error and discard the message by simply returning to
  221. PM.  For good WM_CHAR messages and all other
  222. messages, we pass the message to the standard entry field
  223. procedure whose address we saved when we registered the
  224. class.  
  225.  
  226. The system entry field procedure is not aware that we have
  227. made these changes.  From its perspective, all the
  228. keystrokes are uppercase and the illegal characters were
  229. never typed.  Because all messages are passed to the entry
  230. field procedure, the entry field performs normally in all
  231. other respects.
  232.  
  233.  
  234. Extending the Concept
  235.  
  236. The example shown here is simple, but useful.  You can
  237. write your own control classes of any desired complexity. 
  238. We have developed entry field classes that validate that the
  239. input is alphanumeric, that it is a real number, or that the
  240. input follows other rules about what characters are allowed.
  241.  
  242. You can create your own control as a subclass of any
  243. standard window class using these same techniques.  We
  244. have controls that operate as directory list boxes, as multi-
  245. column list boxes, and as list boxes that allow you to drag
  246. items to different positions in the list.
  247.  
  248. Your controls can be as complex as desired.  Our directory
  249. list box, for example, is multi-threaded and prompts the
  250. user to insert a diskette in a drive if the user selects a drive
  251. without a diskette in it.  All of these actions are taken care
  252. of by the control.  The dialog definition in the resource file
  253. and the dialog procedure need not process any messages to
  254. support these functions; everything is in the control.
  255.  
  256. Frequently you will want to add your own message types
  257. to a custom control, so you can send the control needed
  258. information.  In our directory list box, a user message tells
  259. the list box what the initial directory is.  Upon receipt of
  260. that message, the list box clears its contents and refills
  261. itself with all of the subdirectories of the starting directory.
  262.  
  263. More complex controls often require their own data
  264. structures to hold information about the state of the control. 
  265. Most system-supplied controls have reserved four bytes of
  266. window words at QWL_USER.  You may use
  267. WinSetWindowPtr(hwnd,QWL_USER,ptr) to store a
  268. pointer to your private data area, and
  269. WinQueryWindowPtr() to retrieve it.  If you wish to
  270. preserve these bytes for the user of your control, you may
  271. add sizeof(PVOID) to the count of bytes of window words
  272. in the WinRegisterClass() call.  With this approach, your
  273. private four byte pointer will be located at
  274. QWL_USER+n-4, where 'n' is the number of bytes of
  275. window words.  You will probably also need to process
  276. WM_CREATE and WM_DESTROY messages to initialize
  277. the control and its data areas properly and to clean up when
  278. the control is being destroyed.
  279.  
  280.  
  281. Summary
  282.  
  283. When you need a specialized control that resembles an
  284. existing control, consider modifying the existing control by
  285. subclassing it.  Subclassing a control may require less code
  286. than adding custom processing to messages in a dialog
  287. procedure.  It certainly makes the dialog procedure itself
  288. simpler.  And the result may be reused with little effort.
  289.  
  290. The programs in this article are available on the IBMOS2
  291. forum on Compuserve.  GO IBMOS2 and download file
  292. SUBCLS.ZIP from library 3, Ver 1.x Tools.
  293.  
  294. ------------------------
  295.  
  296. Guy Scharf is president of Software Architects, Inc., 2163
  297. Jardin Drive, Mountain View, CA 94040.  Software
  298. Architects, Inc. specializes in OS/2 Presentation Manager
  299. software development and consulting.  Guy can be reached
  300. on the CompuServe IBMOS2 forum or on CompuServe
  301. Mail at 76702,557 or through Internet at
  302. 76702.557@compuserve.com.
  303.  
  304. =======================================================================
  305. LISTING 1: SUBCLASS.C
  306.  
  307. /* subclass.c -- Sample program to demonstrate a private window class */
  308.  
  309. #define INCL_PM
  310. #define INCL_BASE
  311. #include <OS2.H>
  312. #include <process.h>
  313.  
  314. #include "entryfld.H"
  315.  
  316. static MRESULT EXPENTRY TestDlgProc (HWND, USHORT, MPARAM, MPARAM);
  317.  
  318. typedef struct                          /* Data area for dialog */
  319. {
  320.     CHAR    szTypedData[8];             /* Typed data area */
  321. } WWTEST, *PWWTEST;
  322.  
  323. void main (void)
  324. {
  325.     HAB         hab;                        /* Handle to anchor block */
  326.     HMQ         hmqMsgQueue;                /* Handle to message queue */
  327.     WWTEST  wwtest;                     /* Dialog communication area */
  328.  
  329.     hab = WinInitialize (0);            /* Initialize PM */
  330.  
  331.     if (!RegisterEntryFieldClasses (hab))  /* Register our classes */
  332.         exit (1);
  333.  
  334.     hmqMsgQueue = WinCreateMsgQueue (hab, 0);  /* Create message queue */
  335.  
  336.     WinDlgBox (HWND_DESKTOP, HWND_DESKTOP, TestDlgProc, 0,
  337.                IDLG_TEST, &wwtest);
  338.  
  339.     WinDestroyMsgQueue (hmqMsgQueue);   /* Shutdown */
  340.     WinTerminate       (hab);
  341. }
  342.  
  343. /***************************************************************************
  344.  *                                                                         *
  345.  *  TestDlgProc() -- A procedure to test our new control                   *
  346.  *                                                                         *
  347.  ***************************************************************************/
  348.  
  349. static MRESULT EXPENTRY TestDlgProc (
  350. HWND    hwndDlg,
  351. USHORT  msg,
  352. MPARAM  mp1,
  353. MPARAM  mp2)
  354. {
  355.     PWWTEST     pwwtest;                /* --> dialog data area */
  356.  
  357.     switch(msg)
  358.     {
  359.         case WM_INITDLG:                /* Save address of passed buffer */
  360.                                         /* So we can use it later */
  361.                                         /* (Dialog windows reserve 4 bytes */
  362.                                         /* at QWL_USER that we can use as */
  363.                                         /* we wish.  We usually use this as */
  364.                                         /* a pointer to data for the dialog */
  365.             WinSetWindowPtr (hwndDlg, QWL_USER, PVOIDFROMMP(mp2));
  366.                                         /* Don't allow more input than */
  367.                                         /* we can store */
  368.             WinSendDlgItemMsg (hwndDlg, IDEF_SUBCLASS_ENTRYFIELD,
  369.                                EM_SETTEXTLIMIT,
  370.                                MPFROMSHORT (sizeof (pwwtest->szTypedData)-1),
  371.                                0);
  372.             return 0;
  373.  
  374.         case WM_COMMAND:
  375.             switch(SHORT1FROMMP(mp1))
  376.             {
  377.                                         /* Cancel (ESC) key pressed */
  378.                                         /* Tell caller dialog was cancelled */
  379.                 case DID_CANCEL:
  380.                     WinDismissDlg(hwndDlg, FALSE);
  381.                     return 0;
  382.  
  383.                                         /* OK button (ENTER key) was pressed */
  384.                 case DID_OK:            /* Get address of caller's data area */
  385.                     pwwtest = WinQueryWindowPtr (hwndDlg, QWL_USER);
  386.                                         /* Read current contents of entry */
  387.                                         /* field into data area */
  388.                     WinQueryDlgItemText (hwndDlg, IDEF_SUBCLASS_ENTRYFIELD,
  389.                                          sizeof (pwwtest->szTypedData),
  390.                                          pwwtest->szTypedData);
  391.                                         /* Tell user all data is input */
  392.                     WinDismissDlg(hwndDlg, TRUE);
  393.                     return 0;
  394.             }
  395.             return 0;
  396.                                
  397.         default:
  398.             return(WinDefDlgProc(hwndDlg, msg, mp1, mp2));
  399.     }
  400.     return FALSE;
  401. }
  402.  
  403.  
  404.  
  405. =======================================================================
  406. LISTING 2: ENTRYFLD.C
  407.  
  408. /* entryfld.c -- Upper case conversion subclass procedure */
  409.  
  410. #define INCL_PM
  411. #define INCL_BASE
  412. #include <OS2.H>
  413.  
  414. #include <ctype.h>
  415.  
  416. #include "entryfld.h"
  417.  
  418. /***************************************************************************
  419.  *                                                                         *
  420.  *  Pointer to window procedure for WC_ENTRYFIELD window class.  We        *
  421.  *  use this to subclass the entry field.  This field is initialized       *
  422.  *  in RegisterEntryFieldClasses().                                        *
  423.  *                                                                         *
  424.  ***************************************************************************/
  425.  
  426. static PFNWP pfnEditWndProc;
  427.  
  428. /***************************************************************************
  429.  *                                                                         *
  430.  *  UDUpperWndProc                                                         *
  431.  *                                                                         *
  432.  *  Subclass edit fields for all uppercase, printable input                *
  433.  *  NOTE: this example control is not double-byte character set (DBCS)     *
  434.  *        aware.  If DBCS and National Language Support is required,       *
  435.  *        changes will be required to the processing of the WM_CHAR        *
  436.  *        message.  The _toupper() function is not ANSI and may not be     *
  437.  *        present in all C compiler libraries; use toupper() instead.      *
  438.  *                                                                         *
  439.  ***************************************************************************/
  440.  
  441. static MRESULT EXPENTRY UDUpperWndProc (
  442. HWND    hwnd,
  443. USHORT  msg,
  444. MPARAM  mp1,
  445. MPARAM  mp2)
  446. {
  447.     CHRMSG    *p;                      /* --> entire character msg */
  448.  
  449.     if (msg == WM_CHAR)
  450.     {                  /* Upper case conversion code taken from */
  451.                        /* Cheatham, Reich, Robinson -- */
  452.                        /* "OS/2 Presentation Manager Programming" */
  453.         p = CHARMSG (&msg);     /* Get char msg */
  454.         if (((p->fs & (KC_KEYUP|KC_CHAR)) == KC_CHAR)  /* Only want */
  455.             && (! (p->fs & KC_VIRTUALKEY) ) ) /* key downstroke message */
  456.         {
  457.             if (isalpha (p->chr) && islower (p->chr))  /* Lower case? */
  458.                 p->chr = (USHORT) _toupper ((UCHAR)p->chr);  /* Up it */
  459.                        /* end of extract from "OS/2 PM Programming" */
  460.  
  461.             if (!isprint (p->chr))   /* Eliminate non-printables */
  462.             {
  463.                 WinAlarm (HWND_DESKTOP, WA_ERROR);  /* Warn the user */
  464.                 return ((MRESULT) TRUE);  /* Say we processed the msg */
  465.             }
  466.  
  467.         }
  468.     } 
  469.  
  470.     return (pfnEditWndProc(hwnd, msg, mp1, mp2));
  471. }
  472.  
  473.  
  474.  
  475. /***************************************************************************
  476.  *                                                                         *
  477.  *  Register the entry field classes.  Return TRUE if successfull, FALSE   *
  478.  *  if not successful.  We register the class and save the entry point     *
  479.  *  to the original entry field procedure for later use.                   *
  480.  *                                                                         *
  481.  ***************************************************************************/
  482.  
  483. USHORT  RegisterEntryFieldClasses (     /* Register entry field classes */
  484. HAB     hab)                            /* I - Handle to anchor block */
  485. {
  486.     CLASSINFO   ci;                     /* Window class information */
  487.  
  488.                                         /* Get window class data */
  489.     WinQueryClassInfo (hab, WC_ENTRYFIELD, &ci);
  490.  
  491.                                         /* Save WC_ENTRYFIELD procedure */
  492.     pfnEditWndProc = ci.pfnWindowProc;
  493.  
  494.                                         /* Register our new class */
  495.     return (WinRegisterClass(hab, WC_UDUPPER, UDUpperWndProc, CS_SIZEREDRAW,
  496.                             ci.cbWindowData));
  497.  
  498. }
  499.  
  500. /* entryfld.c */
  501.  
  502.  
  503. =======================================================================
  504. LISTING 3: ENTRYFLD.H
  505.  
  506. #define WC_UDUPPER  "UpperCase"
  507.  
  508. #define IDLG_TEST                                100
  509. #define IDEF_SUBCLASS_ENTRYFIELD        1000
  510.  
  511. USHORT  RegisterEntryFieldClasses (     /* Register entry field classes */
  512. HAB     hab);                           /* I - Handle to anchor block */
  513.  
  514.  
  515.  
  516. =======================================================================
  517. LISTING 4: SUBCLASS.RC
  518.  
  519. #define INCL_WIN
  520. #include "os2.h"
  521. #include "entryfld.h"
  522.  
  523. DLGTEMPLATE IDLG_TEST LOADONCALL MOVEABLE DISCARDABLE
  524. BEGIN
  525.     DIALOG  "Test Custom Entry Field", IDLG_TEST, 12, 25, 140, 48, WS_VISIBLE,
  526.             FCF_SYSMENU | FCF_TITLEBAR
  527.     BEGIN
  528.         CONTROL         "",     IDEF_SUBCLASS_ENTRYFIELD,
  529.                                         13,  25, 113,  10, WC_UDUPPER,
  530.                         ES_LEFT | ES_MARGIN | WS_TABSTOP | WS_VISIBLE
  531.      DEFPUSHBUTTON      "OK",   DID_OK, 50,   5,  40,  13, WS_TABSTOP
  532.     END
  533. END
  534.  
  535.  
  536. =======================================================================
  537. LISTING 5: SUBCLASS.MAK
  538.  
  539. all: subclass.exe
  540.  
  541. SUBCLASS.obj : SUBCLASS.C
  542.     cl /c /W4 /AL /Zpi /G2s /Od subclass.c
  543.  
  544. ENTRYFLD.obj : ENTRYFLD.C
  545.     cl /c /W4 /AL /Zpi /G2s /Od ENTRYFLD.c
  546.  
  547. SUBCLASS.res : SUBCLASS.RC
  548.     rc -r SUBCLASS.rc SUBCLASS.res
  549.  
  550. subclass.exe : subclass.res entryfld.obj subclass.obj
  551.     link /co subclass.obj+entryfld.obj,subclass.exe,NUL,LLIBCEP+OS2,subclass.def
  552.     rc SUBCLASS.res subclass.exe
  553.  
  554.  
  555. =======================================================================
  556. LISTING 6: SUBCLASS.DEF
  557.  
  558. NAME Subclass WINDOWAPI
  559.  
  560. DESCRIPTION 'Example of Private Window Class in Dialog'
  561.  
  562. STUB    'OS2STUB.EXE'
  563.  
  564. CODE    MOVEABLE
  565. DATA    MOVEABLE MULTIPLE
  566.  
  567. HEAPSIZE  1024
  568. STACKSIZE 4096
  569.  
  570. EXPORTS
  571.  
  572. ### END ###
  573.