Dynamic Control Formatting

Making your Frame Controls Dockable
Written by Alger Pike

Introduction
[NOTE: Here is a link to a zip of the source code for this article. Ed]

This article will assume the user has a working knowledge of frame controls and how to format them in a frame window. As such, I will not review how to format a frame window here. To see an example of the required concepts read "EDM/2 April 1995 - Building Custom Controls", or "EDM/2 December 1995 - Bubble Help". The code presented in this article will take the code in the April article and expand upon it. Eric Slaats, the author, has granted me permission to do this.

One of the many concepts behind programming a GUI interface is "choice". The more pathways a user has to do useful work the better. Sometimes however, we can present the user too much information on the screen. By giving the user too many buttons and other gadgets on the screen, less prime real estate is available, for the data the user is working on. One solution to this problem has been the advent of the dockable control. Such a control, like a button bar or menu, can be detached from the frame window or even hidden out of view. By doing so, the user makes more screen space for their data. Of course when the control is needed again it can easily be made part of the main frame window again, simply by clicking.

Let's us first start implementing our dockable frame control by outlining the required components: 1) a main frame window, 2) a floating control window, and 3) dynamic formatting support to handle frame formatting for each window type. The main frame window handles formatting for all the application controls. The floating window will format only the control that it docks. And somehow, when the control is in the floating window, we must make the main frame window aware of this. Otherwise our SWP structure ends up having a dual entry, one for the main frame and one for the floating frame (PM does not like this).

The main frame window can be thought of as the base of operations. It needs to know about all the controls in the application and what state they are in. There are four states a dockable control can take. They are 1) docked, 2) floating, 3) docked minimized and 4) floating minimized. The frame window takes a specific action for each state the control might be in. If the control is in the docked state formatting for the control proceeds normally. If however, the control is in the floating state, the main window now has one less item to format. We must make it aware of this, so that if the control is floating, the main window will not attempt to format it again. In this way, the formatting becomes dynamic. The number of formatted controls is variable, depending upon which if any, of the frame controls are docked.

The floating control window is easier to implement, but no less important. It is the job of this floating window, to display the control, when it is not docked. Each floating window handles only one frame control. This makes the actual job of formatting, for this window much easier; it is static. The floating control can also be told to minimize itself. This way the control is not visible at all. Of course it can be made visible again with a single click of the mouse.

Let's now proceed to make some controls dockable. We will take both the toolbar and the statusbar of the MLE editor and make them dockable. The first thing to do is to setup a structure with all the variables that we need for each window: ''Figure 1. Parameters required by the floating frame window,''

The first variable is a pointer to the old window procedure of our floating frame window. The second is a pointer to the main frame window. The third is the dockState. The main frame window will use this variable to decide if the control is docked or not. The next three items have to do with menus. The bool helps us determine if the formatted control is a menu or not. (Special care must be taken when docking menus.) The next two are an HWND and itemID to a menu and menu item on the main frame window. We will use these to check the menuitems that tell us if our controls are in view or not. The last item control is an HWND to the actual control that we are docking.

At this point now we can make all the necessary modifications to the main function of the application. (Now would be a good time to dig up the MLE code. This way you can see exactly where the changes I made are placed.) Since the initial state of our controls is known we can tell the application what each state is. Since we also know if each is a menu we can also initialize these fields as well: ''Figure 2. Initializing parameters for the dockable controls,''

Since the controls are visible when docked we check the corresponding menu items: ''Figure 3. Checking menu items for visible controls,''

We will also need to register a new class: ''Figure 4. Registering the floating window class,''

This class will handle all requests that are made to the floating control windows. This step completes all the required changes needed for the main function. (Since the formatting for the floating control is static I will not discuss it, but you can check out the NewDocClientProc and NewDocProc for details.)

The next step is to make the changes required for the main frame window procedure. I added four new menu items to the main window. These items, two for each control, control the behaviour of our dockable control, i.e. they dock and view each control. Add the message handlers as follows (The only dockable support will be through the menu. I leave it up to each user to define double clicking and other standard functionality for their dockable controls): ''Figure 5. Adding new menu items,''

Up to this point, the changes required have been fairly straightforward. All that has been needed has been initializing a few variables, and making a few function calls. At this point, we are now ready to put these variables to use by adding dynamic formatting support. Only those controls that are docked get counted in the PSWP structure and go on to get formatted. To take care of this provision, we need to make some changes to the WM_QUERYFRAMECTLCOUNT message in our main frame control. Remember back to out dockState variable in WinParms. We will check this variable for each control that is dockable. If the control is docked, it needs formatting (dockState = TRUE). We increment the count of items that need to be formatted for controls that are docked. If the control is not docked the number of formatted items remains where it is. ''Figure 6. Making item count control dynamic,''

In this way then, if one of the controls is docked we add a one to our frame items, if they are both docked we add a two, and so on.

The next step is to add the support for dynamic formatting to the WM_FORMATFRAME message. Several new variables are required to help us keep everything straight: USHORT items[ITEMS], maparray[ITEMS], i, itemindex; The first array, items[ITEMS], contains the SWP array numbers for all the controls. In our case, there are two controls; so items[0] = itemcount and items[1] = itemcount + 1, etc. The items[index] is static; In a pure static setting this number is always equal to the control number, i.e. formatted control x has an item count of y and a PSWP index of y. In a dynamic setting these two numbers are not always equal. Formatted control x still has an item count of y but its PSWP index may or may not be y depending upon whether the control is docked or not.

The function of the next array, maparray[], becomes very important especially when some of your controls depend on other controls for formatting. It is the maparray[]'s job to keep track of the dynamic PSWP array elements. Each control is assigned a position in the maparray[], equal to its item count. So for example maparray[0] will always refer to the toolbar and maparray[1] will always refer to the statusbar. The value of the map array then takes the index to the items PSWP array. For example let's say that both of the controls are docked. In this case for the toolbar, maparray[0] = 0 and items[maparray[0]] = items[0] = itemcount. For the status bar, maparray[1] = 1 and items[maparray[1]] = items[1] = itemcount + 1. If the toolbar is not docked maparray[0] = -1, and now the control is not formatted. For the status bar (which is still docked) maparray[1] = 0 and items[maparray[1]] = items[0] = itemcount. So maparray[1], always refers to the status bar. Its value is then used to obtain the correct PSWP index. Now maparray[1] always refers correctly back to control one (the statusbar. This comes in very handy if for instance you have yet another control which depends on the location of maparray[1]. The entire WM_FORMATFRAME message follows: Figure 7. The new WM_FORMATFRAME message handler with added dynamic formatting support,

The final step in making the controls dockable is to write the toggleDockState function. This function will do the actual docking and hiding of the controls. By passing the function the current state of the control and the new control state it will be able to take the appropriate action. The function is prototyped as follows: INT ToggleDockState(HWND mainFrame, HWND *floatFrame,                    HWND control, WinParms *controlParms,                     BOOL dockView); At first this may seem a little scary, but by having so many variables passed to this function, it becomes very generic and can be used to dock any control to and from any frame window. The first parameter is in fact a handle to the frame window of your application. The second parameter is a pointer to the floating frame window. The function will later use this pointer to determine if the window is currently docked. The third parameter is a handle to the control that is to be docked. As far as I know any control you can put into a frame window can be docked in this way including menus. The fourth parameter is a pointer to the window parameters that the floatFrame will need. The structure contains all the important information that the floatFrame needs to know. The final parameter is a BOOL that has to do with viewing. If view is TRUE you are hiding/restoring the control from its current dockState. If the BOOL is FALSE you are changing the dockState of the control.

I think the body of the function is relatively simple to go through. Notice how the if statements take care of all four of the states our dockable control can take. Most of the code is just API calls which take care of some of the details with a dockable control (menu checking etc). The most important thing to notice is that when formatting passes to the floating window, that the control must also be made a child of that window. The control is always formatted using its parents coordinates. If you do not reset the parent, the control is still formatted but it is formatted in the main frame window. Also when formatting of the control passes back to the main frame window, make sure to set the parent back to the main frame. Do this before you destroy the floating window. If you do not, you will destroy the control window since PM destroys all children when destroying a frame window.

Hopefully this article has made you aware of how to format a control to and from its parent window. Making the formatting of controls dynamic adds several complexities to the code. However, the added work is well worth the effort. I have just presented the tip of the iceberg when it comes to this topic. Now that you have the basic understanding it should be quite easy to extend the code even further. Two enhancements I can think of include double click support, and automatic docking based on the movement of the dockable control. The user will appreciate the addition of dockable controls to your application. By using them wisely, the user will be able to hide and show the control at will making more for their valuable data.