Creating user controls- not for the faint of heart There are two basic types of user controls, simple and complex. The simple type does not require one to create any screens, as Gpf supplies a default. The tougher one has the "extended" feature of the default screen turned on, and one must create the entire infrastructure to display and make use of such a dialogue. In both cases the developer is responsible for the entire behavior of the user control, which can be very simple for static-like entities, or a real pain for interactive objects (for example, building a genuine multi-column list box, or formatted data entry fields, or SQL-wise controls, etc.) This package contains: a. a minimal user control, similar to STATIC TEXT controls, but auto sizing and with fancy shadowing. No extended dialog is required. b. the same control with an extended dialog, permitting the Gpf developer to specify some extra properties. A. The Minimal Control We will call this control "SHADOW". You will need the following files: GPFUC.H do not write, included with the Gpf product SHADOW.H you write this, though not strictly required SHADOW.C includes GPFUC.H, SHADOW.H, and is the driver code. It must contain at least (2) entry points expected by Gpf, and an entire window procedure for the control. This window procedure will be fairly simple. SHADOW.DEF linker information, and exports the required entry points SHADOW.MAK a makefile, necessary if you hate to type a lot When all is said and done you will have created: SHADOW.DLL this will need to be moved to one of your LIBPATH locations SHADOW.LIB IMPLIBed, nice to make, but again not strictly necessary SHADOW.OBJ which can be discarded after the DLL is generated For this simple example we will not use a SHADOW.H, all source will be in SHADOW.C. What is required in this file? For the purposes of Gpf, two and only two exported functions PUCC APIENTRY RegisterXXXXXX (VOID) FNWP fnwpXXXXXX(HWND,ULONG,MPARAM,MPARAM) where XXXXXX is replaced with your control name. In this example we would therefore create these two functions: RegisterShadow fnwpShadow and can now write the entire SHADOW.DEF file as follows LIBRARY SHADOW INITINSTANCE TERMINSTANCE DESCRIPTION 'any text you want' PROTMODE DATA PRELOAD MULTIPLE NONSHARED CODE PRELOAD EXPORTS RegisterShadow @1 fnwpShadow @2 SHADOW.DEF is now complete, so now let's write and file away SHADOW.MAK. [In this example we are using IBM C\SET 2 in "c" mode]. The entire text follows --------------------------------------------- Lib = Os2386 Objects = shadow.obj LinkOpt = /A:4 /BASE:0x12000000 Options = /W3 /c /Gd- /Ge- /Gm+ /Re Shadow.dll: $(Objects) Shadow.def Link386 $(Objects), Shadow.dll $(LinkOpt), ,$(Lib), Shadow.def Shadow.obj: Shadow.c ICC $(Options) Shadow.C ---------------------------------------------- Nothing left to do except the "C" file. Notes about the two exported functions: 1. RegisterShadow() Three major things are done here: - get a HAB - register this class, including its name and window procedure (fnwpShadow). The last parameter reserves some extra storage which is to be tacked on to each instance. - fill in the fields in struct "Ucc", typedef'd in GPFUC.H. In this file there are several 'defines' of the form GPF_CAPS_wxzy. These are used to control the capabilities which are enabled during the presentation of the default User Control dialog. Since we want the developer to define text, we enable the text entry field this way: Ucc.Capability= GPF_CAPS_TEXT; /* more stuff could be ORed in */ 2. fnwpShadow() Each instance of this control will need some storage in which we can store, as a minimum, the text defined by the developer during control creation. In this case it will be of type PSZ, for which memory will be allocated during the WM_CREATE message. [Note that the call to WM_CREATE will be made from Gpf, after the CREATESTRUCT fields have been filled in. The text specified by the developer will be pointed to by the "pszText" field of this structure.] Once allocated we will place a pointer to this area into the first slot of the EXTRAWORDS asked for during class registration. This storage will be free(d) during the WM_DESTROY message. Now jump back to the start of fnwpShadow and notice that we initially try to retrieve the pointer stored with the instance. If it is NULL we know the WM_CREATE function has not been processed (or invoked) as of yet, hence we immediately return unless the message is WM_CREATE! It is usually safe to ignore any messages flying around before WM_CREATE. The other message of real interest is WM_PAINT. The pointer to the instance text has already been retrieved, so an HPS is opened, and along with the PSZ and HWND, is passed to a helper function called Paint(). The details of what Paint() does will not be explained here, kindly look at the source code. In fact, modify it to your heart's content.... but don't call me if it blows up after your 'improvements' are installed. Many optimizations are possible, such as re-sizing the control to exactly fit the passed-in text. But that would obscure the point of this AppNote. The next control, having an extended dialog, will indeed have some obfuscating code. How does one use this control? 1. From Gpf click on menu "Objects- User Controls" 2. In new dialog, press "Add" 3. In new dialog enter "shadow" in name field enter "shadow" in DLL name field the link via should be left at "import" 4. press Ok. If Gpf is unable to find it, move SHADOW.DLL to a directory seen by the LIBPATH variable in CONFIG.SYS, then repeat the above 5. exit to top level 6. open or create a window 7. click on the "Create- User Control" menu 8. drop the tracking rectangle anywhere on the client area. 9. the User Control default dialog should come up (if you have this turned off, simply double click anywhere within the tracking rectangle for this control) 10. enter the desired text and return with OK 11. drag/drop or re-size as desired 12. if you want to modify the SHADOW.DLL sources, close Gpf so that it releases the DLL! Al Charov, November 1994 ***************************************************************** B. A more complex example Examining file "Gpfuc.h" reveals a definition of GPF_CAPS_EXTENDED. Including this bit during the registration phase enables the "Extended..." pushbutton in the User Control default dialog. Simple enough, but a bunch of extra baggage must be generated to support this capability: - one or more dialog templates must be made, using the toolkit dialog editor, and (resource) compiled. - an extra registration procedure must be written, with a specific format, for the extended dialog - a complete window procedure must be written for each and every new dialog - a parameter/data collection and passing stategy must be implemented within the new procedures, and made known to the two original procedures (made in the simple case). - the .DEF and MAK files need to be expanded a bit. - for general ease, an .RC file is made; its only function is to pull in the .DLG file created for the extended dialogs. Sounds a bit messy, but it all becomes perfectly clear after making a few dozen of these beasts. In this example we will create a User Control with the name "SHADOWX", since it is derived from the previous control but with a few bells and whistles thrown in. As before, let's talk about the bare minimums: - you must create source code for these functions /* the next two are just as in the simple example */ /* except for a slight name modification */ PUCC RegisterShadowx(VOID); MRESULT EXPENTRY fnwpShawdowx(HWND,ULONG,MPARAM,MPARAM) /* the next two are new, when using the EXTENDED feature */ LONG APIENTRY ExtendedShadowx(LONG, PVOID); MRESULT EXPENTRY fnwpExtShadowx(HWND,ULONG,MPARAM,MPARAM); /* the new prefixes, 'Extended' and 'fnwpExt' must be exact, even their case !! */ /* and the suffix for all (4) functions must be the name of this control: Shadowx */ - invoke the dialog editor (not Gpf!!) and create a .DLG and .H file. In this example I created SHADEXT.DLG and SHADEXT.H. No instructions for using this particular editor will be provided here, except an admonition to specify useful ID names. As before we can immediately write the complete .DEF file, and we'll call it SHADOWX.DEF. The entire source follows: LIBRARY SHADOWX INITINSTANCE TERMINSTANCE DESCRIPTION 'any text you want' PROTMODE DATA PRELOAD MULTIPLE NONSHARED CODE PRELOAD EXPORTS RegisterShadowx @1 ExtendedShadowx @2 fnwpShadowx fnwpExtShadowx and now the entire MAK file, which I've named SHADOWX.MAK ------------------------------------------- Lib = Os2386 Objects = shadowx.obj shadext.obj LinkOpt = /A:4 /BASE:0x12000000 Options = /W3 /c /Gd- /Ge- /Gm+ /Re Shadowx.Dll: $(Objects) shadext.res Shadowx.Def Link386 $(Objects) , Shadowx.Dll $(LinkOpt),,$(Lib) ,Shadowx.Def; rc shadext.res shadowx.dll copy shadowx.dll c:\os2\dll Shadowx.Obj: shadowx.c shadowx.h shadext.h ICC $(Options) shadowx.C Shadext.obj: shadext.c shadowx.h shadext.h ICC $(Options) shadext.c shadext.res: shadext.rc shadext.dlg shadext.h rc -r shadext.rc For no particular reason, except perhaps to keep all source files on the small side, I chose to split my code into two "C" files. SHADOWX.C contains the same functions as in the simple example, and SHADEXT.C contains the required new functions which support the extended dialog. Aside from ensuring the compile/link of two source files, the only real addition to the .MAK file is some extra processing for the resource compiler. SHADEXT.DLG and SHADEXT.H files were generated by the Dialog Editor, and then a simple .RC file was created which simply does an 'RCINCLUDE' of these two. With the production details out of the way it is time to discuss the source changes and additions. What are the new features of this control? - Presentation Parameters, familiar to most Gpf users, can now be specified for this control in the same fashion. Note: only foreground/background colors have any effect. Font specifications are ignored, as the source code is hard-wired to 'Times New Roman". - the "Extended..." pushbutton is enabled, leading to a dialog in which we can a. specify a left or right tilt to our text, 0-30 degrees from vertical. b. specify the left shadow color c. specify the right shadow color d. override the background color e. override the foreground color The only change to the registration procedure was the addition of two bits to 'Ucc.capability', turning ON the PresParams and Extended pushbutton in the default User Control dialog. The changes to the window procedure are a bit more involved, and depend on what happens in the extended dialog (if anything; the user should not be required to invoke it). The key to understanding this code is knowing what, and when, gets passed between our four required functions. All Gpf User Controls make use of a structure of type UCC, contained in GPFUC.H: #pragma pack(1) /* force structure alignment packing */ typedef struct { ULONG Capability; /* Caps Style */ ULONG WsStyle; /* Initial Ws Style */ USHORT Cx; /* Initial Cx */ USHORT Cy; /* Initial Cy */ LONG Reserved[60]; /* Reserved */ } UCC; typedef UCC FAR *PUCC; /* Far Pointer */ Previously we used the first four members of this structure and now we will use the fifth in addition. We can define any structure we wish, provided it is 'packed' and does not exceed 60 bytes in length. Gpf will be providing us with a pointer to the 'Reserved' member at appropriate times, and we can type cast it to suit our needs. So I created a structure to hold the information this control will be interested in retaining. In file SHADOWX.H: #define MAX_SHADOW_TEXT 26 /* due to Gpf definitions this structure must be <= 60 bytes */ #pragma pack(1) typedef struct{ USHORT cBytes; CHAR chText[MAX_SHADOW_TEXT]; ULONG BgColor; SHORT BgIndex; ULONG FgColor; SHORT FgIndex; ULONG LeftColor; SHORT LeftIndex; ULONG RightColor; SHORT RightIndex; SHORT sAngle; BOOL fOverride; } UCPARAMS; typedef UCPARAMS * PUCPARAMS; #pragma pack() Member 'cBytes' will be used as a test of whether or not the structure has been initialized. After initialization the sizeof(UCPARAMS) number will be placed here. All members suffixed with 'Color' will contain the OS/2 PM definition for a color, such as CLR_BLACK. All colors known to this control are in structure 'Colors', found at the top of file SHADEXT.C. All members suffixed with 'Index' are used to hold the offset into the 'Colors' structure for the related 'Color' member. This is for convenience only, and comes in handy when communicating with the combo boxes which display the current and the available choices. Member 'sAngle' will contain the tilt angle, read from a spin button having a range of +/- 30. Member 'fOverride' will follow a check button in the extended dialog, and when true will cause the WM_PAINT procedure to ignore Presentation Parameters and use the colors specified by the user from the extended dialog. It is important to understand that Gpf itself will know nothing about this structure, and will care even less. But it will maintain a pointer, set to NULL or to the enclosing area (UCC.Reserved). A NULL value is the initial value and will stay that way unless and until the extended dialog is invoked at least once. Then it will be fixed to point to the reserved block. This pointer is available at these times: - for Gpf's default User Control dialog functions In RegisterShadowx one can, if one really desires, derive &Ucc.Reserved In fnwpShadowx, message WM_CREATE, it is passed in as 'mp1'. It may or may not be NULL, subject to the explanation above. - for the extended dialog functions In fnwpExtShadowx, message WM_INITDLG, it is passed in as 'mp2'. Here it is never NULL and we will immediately capture it into a static variable, available then to all other messages including any further WM_INITDLG. Keep in mind that the entire UCC structure is primarily for the use of Gpf, and we will keep a local copy of the reserved area in the 'window words' of each control instance. How? Let's examine the modifications to WM_CREATE in fnwpShadowx(). As in the simple case, 'mp2' is a pointer to a CREATESTRUCT defined by OS/2 PM header files. The member of interest to us is 'pszText', into which Gpf will stuff any text entered by the Gpf user during the default dialog. The pointer to the reserved block will be in 'mp1', providing that the extended dialog has been invoked at least once. The first time that WM_CREATE is called there is no possibility of having gone through the extended dialog, hence Gpf will have set this to NULL. Meanwhile, we allocate storage for a UCPARAMS structure and initialize a pointer to this block. Next we check 'mp1', here type cast to a PUCPARAMS. If it is NULL we init two of the color fields in our local structure; if it is non-NULL, we copy the entire contents into our local instance. The text is then copied, in both cases, to the newly allocated structure. It is then tucked away for future use with the WinSetWindowPtr() function. WM_PAINT will have access to this pointer, and will modify itself according to the information. Now to the extended dialog... The window procedure, fnwpExtShadowx, will always be given a true and valid pointer to the reserved, 60-byte area (via 'mp2'). As this window is constantly being created and destroyed, we must implement a mechanism to determine the validity of the structure contents. It would be nice, for example, to set all the controls to their last-invoked state. But testing for a NULL pointer will not work, as it is never NULL. Hence we do something else, we set member 'cBytes' to a certain value at the first opportunity. Now we can test the contents of 'cBytes' for this value. If it is incorrect we know this is the first time it is seen by our window procedure, and we initialize ALL relevant members to desired values. The remaining controls can then be initialized with respect to the structure contents, new or existing, and this completes our WM_INITDLG phase. The only remaining job is to catch user interactions with the extended dialog controls. Since they will all generate a WM_COMMAND message, that is where we lump them. Parameter 'mp1' tells us the control ID and the nature of the message. A big switch statement is built to take care of all possibilities of interest to us. Those of no interest should not be ignored, but rather passed to WinDefDlgProc(). Bizarre things can happen if you forget to do this. Using this control is similar to the simple example, except that you must change the name to 'shadowx'. Obviously nothing prevents you from using both types of control, in the same application. Hope this helps. Al Charov, December 1994