![]() |
Development of a New Window Class - Part 1/2Written by Larry Salomon, Jr. |
IntroductionThis article begins a multi-part series which will ultimately result in the development of a new window class. Everything from the functional requirements to the design to the implementation will be described, giving you a good look at how it is developed from the ground up; in the end, we'll have a completed window class with a variety of uses. InspirationEveryone can remember back to their days in middle school when three-ring binders were all-the-rage and your parents purchased many packs of paper to fill them. The holes in the paper and the rings in the binders combined to allow for easy transfer to other notebooks or for inscription on the paper prior to storage in a binder. Now we're in the computer age, and paper is quickly becoming a thing of the past. However, direct translations of computer concepts to the "real world" is now the thing to do; so, wouldn't it be nice to have a piece of loose-leaf on your monitor to scratch notes on? Functional RequirementsBefore we can do anything, we need to first define what our goals are going to be. What will our control do from the user's perspective? What should it look like? What capabilities will it have from a programming standpoint? These are not easy questions to answer, nor do they have answers that are correct. Because the nature of these questions is subjective, the best answers are going to be the ones that address the issues of those people that are most affected.
Now we have a basis on which to design the control. Whether or not this list of requirements is complete or even doable remains to be seen and the list could be revised one or more times before it arrives at its final state. DesignIt is often easiest to develop new code by repeatedly breaking the problem to be solved into smaller pieces until you arrive at a level where each piece can be coded and tested independently - or with little dependence - on the other pieces of the code. (C++ programmers will recognize this as being one of the inherent advantages of the language, since in the purist form of the language you are forced to do this as a preprocessing step.) In a related manner, writing a new window class is often easiest if you can break down the abstract view of the control into distinct components that are independent of each other. The paper control can be viewed as a collection of lines that can be written on, as well as a side margin and a top margin. Additionally, the holes (if any) are placed in the side margin. Since the user can type in only the top margin and/or the lines, the remainder of the control is reduced to a little more than proper painting of the components. Even so, the handling of the typing can be a major chore if we choose to process the keystrokes ourselves.
Figure 1: Different parts of the paper control. Fortunately, we don't have to. Borrowing the idea from the container control (my apologies to everyone if this was done previously in another control), we can create a single-line entryfield and by giving it in the proper position and size, it will behave exactly as though we processed each and every keystroke. But where will we store the handle to the entryfield? The answer lies in something that has existed since version 1.1 called... Window WordsIn C, there are provisions for declaring variables with 3 scopes of visibility: local, static, and global. Given the high possibility of having more than one window of a particular class existing simultaneously, the latter two are unsatisfactory for storing data since the data is not unique to a specific window; while local scope does provide for data unique to a window (since it is provided for on the stack of the thread running), it is also insufficient, since the data is not retained once the procedure exits. The PM developers saw these limitations and provided an answer in the form of window words. Window words are 1 or more bytes of storage that is reserved for all windows belonging to a specific class (called instances of that class). The actual number of bytes to be reserved is specified in the WinRegisterClass call and the window words can be set/queried using the functions WinSetWindowPtr and WinQueryWindowPtr. WinSetWindowULong and WinQueryWindowULong can also be used with the QWL_USER parameter. All of the standard PM controls reserve 8 bytes of window words; bytes 4-7 are used to store a pointer to a private data structure and bytes 0-3 are available for application use. We, too, will adopt this strategy; the memory will be allocated in the WM_CREATE message and will be released to the system in the WM_DESTROY message. The structure that this memory will be used for can be gleaned with a little thought. typedef struct _PAPERINSTDATA { USHORT usSzStruct; HWND hwndOwner; HAB habAnchor; HWND hwndText; PCHAR pchTitle; SHORT sMaxLines; SHORT sLine; CHAR aachLines[256][256]; FONTMETRICS fmFont; LONG lForeClr; LONG lBackClr; } PAPERINSTDATA, *PPAPERINSTDATA; The fields in the structure are explained below:
Breaking Down the WallsAs stated earlier, the implementation would be easier if we could divide the control's functionality into distinct parts and tackle each individually. Doing so yields the following:
Additional FunctionsTwo important additional functions are the initialization and termination of the paper window class. The initialization function allows us to register the window class; the termination function will not be used in our implementation, although it should still exist to create a "balanced" function set. These two functions are the only two that should be exposed to the programmer. BOOL PprInitialize(HAB habAnchor) { WinRegisterClass(habAnchor, WC_PAPERPAGE, pprWndProc, CS_SIZEREDRAW|CS_CLIPSIBLINGS|CS_PARENTCLIP, sizeof(PVOID)*2); return TRUE; } BOOL PprTerminate(HAB habAnchor) { return TRUE; } SummaryWe have seen how a little forethought can lead to a better design; by attempting to anticipate the needs of those who will use our control, we can shape the design of the control better and fill in more of the blanks that will become apparent once the control is actually used. Additionally, we have learned about window words and how they can be used to store data that is specific to a particular instance of a window class. Coming up in next month's article, we will wrap up the design of the control and begin the implementation of the window procedure. |