Development of a New Window Class - Part 1

From EDM2
Jump to: navigation, search

Written by Larry Salomon Jr.

Part 1 Part 2

Introduction

This 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.

Inspiration

Everyone 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 Requirements

Before 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.

What will our control do from the user's perspective?
Since the term "loose leaf" conjures up specific expectations about its capabilities, we should try to adhere to those expectations: it should allow the user to type text anywhere in the paper body and it should allow for a title to be placed in the margin at the top.

Additionally, good integration with the Workplace Shell is a necessity, so it should respond appropriately if the user drops a new font or colour from the corresponding palette on the control.

What should it look like?
Similarly, it should look like a piece of loose leaf paper to minimize the "state shock" that a user might encounter when using the control.
What capabilities will it have from a programming standpoint?
This is often the most difficult to answer, although placing yourself in the role of a programmer using the control often helps. What capabilities would I want, were I to use this control? Personally, I would like to see the following "programmabilities":
  • The ability to query the contents of any line, as well as set the contents of any line.
  • The ability to query the text in the control, with each line ending with a "newline" character.
  • The ability to query what line the cursor is currently on as well as set the current line.
  • The ability to have the holes on the left or right side of the paper as well as have no holes at all. The ability to query where the holes are on the paper would be nice also, should I choose to implement a binder to contain the pages within.

Notifications are also quite important, so it is a must to be notified whenever:

  • The cursor moves up or down
  • Any mouse button is clicked or double-clicked
  • The system-defined sequence for displaying the context menu is pressed
  • Help is requested

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.

Design

It 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.

Newwin1.gif

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 Words

In 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:

usSzStruct
Contains the size of the structure. This is needed because PM performs a lot of thunking when calling its 16-bit code from a 32-bit application and it needs to know how large of an area to convert to a 16-bit segment.
hwndOwner
Contains the handle of the owner window for notification purposes.
habAnchor
Contains the anchor block of the window.
hwndText
Contains the handle of the entryfield window that will be used for input.
pchTitle
Points to the title text of the window, as set by the WinSetWindowText function (and on the call to WinCreateWindow).
sMaxLines
Contains the maximum number of lines that are displayed. The top line below the margin is the 0th line.
sLine
Contains the current line number.
aachLines
Contains the text of the control. By defining it in this fashion, we are defining a limitation of 256 lines and 255 characters per line (plus 1 for the terminating `\0'.
fmFont
Contains information about the font currently being used.
lForeClr
Specifies the "foreground" colour, which is used to draw the title text and that of the entryfield.
lBackClr
Specifies the "background" colour, which is used to draw the text that has already been entered.

Breaking Down the Walls

As 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:

Painting
This part draws the control on the screen, and can be considered the most important part from a user's standpoint, since first impressions are everything.
User input
This part handles the typing (including arrow keys, should we choose to process them) and mouse actions.
Owner notifications
This part handles the notification of the control's owner of significant events.
Presentation parameters
This part handles the presentation parameters, i.e. dynamically alterable characters of the control; foreground and background colour and the desired font are the most widely used of these and can be modified using the "Colour Palette" and "Font Palette" that are provided with the system.

Additional Functions

Two 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;
}

Summary

We 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.