Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Layout Management


Jun00: Algorithm Alley

John is a senior applications developer with Lithic Software. He can be contacted at [email protected].


Creating a program that uses a dialog box for the main window (that is, a dialog client) offers advantages both to developers (faster development) and end users (an easier-to-use interface). One of the problems you encounter, however, is how to make the dialog box resize -- and how to scale the controls on the dialog box when it is resized. In this article, I describe an algorithm for scaling child windows when the parent is resized, and I'll present an example program (available electronically; see "Resource Center," page 5) that creates a dialog client for the Windows API using the algorithm. Although the example runs under the Windows API, the algorithm and data structure are also useful for scaling child windows on other platforms. The examples that are based on Java assume that you are using a null layout manager and doing the layout control from within a class.

Resizing Algorithm

To resize the controls, you need:

  • The original size of the control.
  • The original size of the parent.

  • The current size of the parent.

Trying to resize the control based on the previous size of the control, current size of the parent, and current size of the control won't work because sizes are integral, and resizes can be fractional. Thus, you need the original information as a reference point.

The resizing algorithm uses a data structure and a function. Listing One, the data structure resizeinfo, contains a RECT and a HWND, both of which are Windows-specific data structures. To implement the resizing, you need to track the original width and height of the control, so you could use two ints instead. The HWND is used as an identifier. If you were to do this in Java, you could cast the Component to an Object, and just use an Object in place of the HWND. The key information to store is the size information and an identifier for what control the size information refers to.

In the example program, the necessary memory is allocated for the structs, and the structs are stored in sequence. Listing Two shows how the memory for the structs is iterated through to get the correct struct and return it. This code is specific to C, but the memory structure used could be an array (or Vector, if you were using Java).

Listing Three shows how the control is actually resized. First, the relevant resizeinfo struct is retrieved, using the window handle of the control as the identifier. The new size is computed, based on the amount that the dialog was resized. The RECT that stored the size information is in the client coordinates. It doesn't matter where you move the parent window, the children will draw themselves with reference to the upper-left corner of the parent.

The Example Program

The example program is an application that uses this resizing algorithm to resize its child controls. In the code, I've included notes specific to making a dialog client so that you can take this code and use it as a basis for your dialog client programs and dialog boxes (if you are programming for the Windows API). Also, in relevant spots, I've included notes about porting.

The file resize.c (available electronically) is the code for a resizable Windows dialog client. Listings Four and Five are the resource and resource header, respectively. One of the benefits to creating a Windows dialog client is that you can layout the controls in a resource editor. The example dialog is simple, including only two text fields and a button; see Figure 1. These controls permit you to have a look at using different types of controls in the dialog interface. You will resize the single-line text control differently than the multiline edit control when we look at how to scale the client. For Java, you could see if the object is an instanceOf a given class if you wanted to resize based on class.

The resource code for the dialog box used as the main window is not like the code for a conventional dialog box (to see conventional dialog box resource code, see the code for AboutBox, located toward the bottom of resize.rc). Also, note that the dialog box used for the interface has a class, which needs to match the lpszClassName of the WNDCLASS registered with RegisterClass.

Another difference is that the dialog box uses WS_OVERLAPPEDWINDOW as a style. This style is a shorthand way to specify all of the following styles: WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, and WS_MAXIMIZEBOX. The styles to note here are WS_THICKFRAME, which permits the frame to be resized by dragging on the borders, and the WS_MINIMIZEBOX and WS_MAXIMIZEBOX styles, which add the maximize and minimize boxes to the dialog box. If you do not want the dialog box to be resizable, do not use the WS_THICKFRAME and WS_MAXIMIZEBOX styles. If you want the dialog to only exist maximized, minimized, or in its original size, substitute WS_DLGFRAME for WS_OVERLAPPED, and remove WS_THICKFRAME.

If you are going to use the resource file as it is, please note that resize.ico is an external file. To compile the code, create a 32×32 icon named resize.ico and save it in your development directory.

Referring to resize.c, notice that the code looks much like boilerplate code for creating a single document interface program. There are a number of differences of which you need to be aware. The first difference you will notice is that when initializing the WNDCLASS, you set the cbWndExtra member to DLGWINDOWEXTRA. Loading icons and menus is done as for a conventional window. One note about using a menu with a dialog interface program: The controls will be shifted down to make room for the menu, so you need to include some extra room at the bottom of the dialog box when you are designing it.

Another significant difference between conventional Windows code and dialog client code is that you use a call to DialogBox to create the main window, rather than calling CreateWindow as you would with a conventional single document interface program. You also don't need a message-handling loop in WinMain.

Looking at the WinProc code, you will note that it is not a DialogProc. However, it does get the WM_INITDIALOG message (if you process this message, you should return True if you want windows to set the focus; if you set the focus while processing WM_INITDIALOG, return False).

There are several things that differentiate a WndProc from a DialogProc. The two most important issues are that you must use DefWindowProc to handle an unprocessed message, and that you must return a PostQuitMessage when the main window is destroyed. Also, when you quit the application, you will send it a WM_DESTROY message, not just call EndDialog with the dialog handle as you would for a conventional dialog box. If you do not send a PostQuitMessage, you will not free up the resources associated with the window, and the system won't be aware that the process is dead. If you don't use DefWindowProc, the program won't run at all.

Making the Interface Scalable

With the Windows API, there are three steps that need to be implemented to resize the controls.

  • First, the number of controls needs to be determined, then memory for the correct number of resize structs needs to be allocated.
  • Next, the resize structs need to be created and stored.

  • The last step, resizing the controls in response to a WM_ SIZE event, occurs each time the parent is resized. For Java, you could create and store the data structures in a Vector as you create the components.

Whatever type of platform you are working on, you will need to process some sort of resize message or method for the algorithm to work. For Windows, there are several, including WM_MOVING, WM_MOVE, WM_SIZING, and WM_SIZE. The one I use here is WM_ SIZE, which is sent after a window has changed its size. You first check if the window has been minimized. If it has been minimized, you don't need to do anything. If it has not been minimized, the message needs to be processed. You get the window RECT so that you are working with the entire window, not just the client area, which is what is passed in the LPARAM of WM_SIZE.

Using the EnumChildWindows API function lets you iterate through all the child windows. This Windows API function takes the handle of the parent window, a pointer to a callback function (in this example, ResizeEnumProc), and an application-defined value. The function then enumerates the child windows that belong to the specified parent by passing the handle of each child window to the callback function specified in the second parameter. This continues until the last child window handle is reached or the callback function returns False. The application-defined value is used to tell the callback function what it should do. The application-defined value, in this case, indicates whether you just need an enumeration (0L), want to store the initial size information (1L), or are resizing (2L).

The basis of this implementation of the resizing algorithm is getting the current position, then moving (and concurrently resizing) a control based on how the parent window (the dialog client) was resized. To get the control window size, the GetWindowRect function is used. You need the RECT that is returned in the client coordinates, so you take the points that represent the top-left and bottom-right corners of the control and convert them to client coordinates using ScreenToClient. You convert the points back into a RECT, then figure out whether you are going to resize the control, and what the new width and height will be if you do.

Child windows can be scaled according to their window class (for Java, you could see if the object is an instanceOf a given class). So, if you maximized a window, which resulted in the width doubling and the height increasing by 1.5 times, and you wanted a multiline text control to scale similarly, you would want the height of the multiline text control to increase by 1.5 times and the width to increase by 2 times. A complication is that you may not want the size of some classes of controls (buttons, for example) to increase at all. Using GetClassName, you can get the name of the window class. If it is the class you don't want to resize, you set the height and width to rect.bottom-rect.top and rect.right-rect.left, which is the control size that you determined earlier with GetWindowRect. It would still be repositioned when MoveWindow is called, but it would not be changed in terms of size. If the controls are to be resized, you can multiply their width and height by dVResize and dHResize, two globals that indicate how much the parent was resized. dVResize and dHResize are set when the WM_SIZE message is processed.

A further complication would be that you might want single-line text fields to increase in width but not in height (as shown in the listings). To do this, you get the WindowLong associated with the window. This value contains information about the window, including the window style. Doing a bitwise comparison yields whether it is ES_MULTILINE or not (this is a useful way to get information at run-time about windows).

In the WM_SIZE, you can also check to make sure the window is not resized to smaller than the initial size. The initial size was stored during the initial time through WM_SIZE, so checking it is just a matter of checking the current size versus the initial size. If the current size is less, we can resize to the original size. Another way to approach this would be to handle the WM_SIZING message, which is sent to a window that is being resized. You can remove the code that prohibits the user from shrinking the window below the original size, the results are kind of funny.

Uses and Design For a Windows Dialog Client

Programs that use a dialog box for the main window are useful for certain types of applications. Calculators of many types are conveniently made with a dialog interface. If you only need a single window, and if your end users would find it convenient to have all or many of the controls available on the first window that is displayed, your program is a good candidate for using a dialog interface. Using this code as a skeleton, you are ready to start creating programs using a dialog box for your main window. If your application is well suited to this approach, you may want to follow the following design path:

1. Create the dialog box to be used as the main window in a resource editor.

2. Add a class name to the dialog box, and make sure the style is WS_OVERLAPPEDWINDOW.

3. Use boilerplate code for the source file, adjusting the names of the class, menu, dialog template, and icon.

4. Compile and run.

After this sequence is completed successfully, adding functionality and creating more child dialogs is done as usual.

Conclusion

The techniques and algorithm presented here can be expanded or modified to deal with other layout and sizing issues. To express the issues in a more general way, layout managers deal with the following three questions: What is it, where is it, and how has it changed? Addressing these items, you can expand or modify the aforementioned code to address most layout management issues.

DDJ

Listing One

/* holds an identifier and the initial RECT of the control */
typedef struct
    {
    HWND hControl;
    RECT rect;
    } resizeinfo;

Back to Article

Listing Two

/* iterate through the resize array and get the correct information*/
resizeinfo GetResizeInfo(HWND hwnd)
    {
    resizeinfo ri;
    int i;
    for(i=0; i<iChildren; i++)
        {
        ri=pri[i];
        if(ri.hControl==hwnd)
            return ri;
        }
    /*should never get here*/
    return ri;
    }

Back to Article

Listing Three

/*this is where the children are resized*/
BOOL CALLBACK ResizeEnumProc (HWND hwnd, LPARAM lParam)
    {
    char szClassName [31];
    RECT   rect;
    int iLeft, iTop, iWidth, iHeight;
    POINT pTop, pBottom;
    resizeinfo ri;

    /*counting children*/
    if(lParam==0L)
        {iChildren++; return TRUE;}
    if(!GetWindowRect(hwnd, &rect))
        {;/*can't do anything if this fails*/ }
    /*getting the initial child size*/
    else if(lParam==1L)
        {
        ri.hControl=hwnd;
        pTop.x=rect.left;
        pTop.y=rect.top;
        pBottom.x=rect.right;
        pBottom.y=rect.bottom;
        ScreenToClient(GetParent(hwnd), &pTop);
        ScreenToClient(GetParent(hwnd), &pBottom);

        rect.left=pTop.x;
        rect.top=pTop.y;
        rect.right=pBottom.x;
        rect.bottom=pBottom.y;

        ri.rect=rect;
        pri[iCurrentChild++]=ri;
        }
    else/*resizing the controls*/
        {
        /*get the correct control*/
        ri=GetResizeInfo(hwnd);
        /*fix the horizontal size and position*/
        iLeft=(int)(dHResize*(double)ri.rect.left);
        iWidth=(int)(dHResize*(double)(ri.rect.right-ri.rect.left));

        /*fix the vertical size and position*/
        iTop=(int)(dVResize*(double)ri.rect.top);

        /*if this is anything other that a single line
          text edit, resize height (for demonstration)*/
        GetClassName(hwnd, szClassName, 30);
        AnsiUpper(szClassName);
        /*show that we can resize based on class*/
        if(strcmp(szClassName, "EDIT")==0)
            {
            LONG l=GetWindowLong(hwnd, GWL_STYLE);
            /*only ES_MULTILINE EDIT controls get resized*/
            if(l&ES_MULTILINE)
                iHeight=(int)(dVResize*
                              (double)(ri.rect.bottom-ri.rect.top));
            else
                iHeight=ri.rect.bottom-ri.rect.top;
            }
        else
            iHeight=(int)(dVResize*
                          (double)(ri.rect.bottom-ri.rect.top));
        /*move the window*/
        MoveWindow(hwnd, iLeft, iTop, iWidth, iHeight, TRUE);
        }
    return TRUE;
    }

Back to Article

Listing Four

/*resize.rc */

#include <windows.h>
#include "resize.rh"

/* this icon is external, you need an icon named resize.ico
  in the directory for the resource to compile correctly
*/
resizeicon ICON "resize.ico"

/* menu */
resizemenu MENU
{
    POPUP "&File"
    {
      MENUITEM "&Quit...", IDC_QUIT
    }
    POPUP "&Help"
    {
      MENUITEM "&About...", IDC_ABOUT
    }
}
/* Client Dialog */
resizedialog DIALOG 3, 14, 305, 75
STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | 
                                           WS_MINIMIZEBOX | WS_MAXIMIZEBOX
CLASS "resize"
CAPTION "Resizable Dialog Interface"
FONT 8, "MS Sans Serif"
{
 CONTROL "About", IDC_ABOUT, "BUTTON", BS_PUSHBUTTON | WS_CHILD |
                                  WS_VISIBLE | WS_TABSTOP, 228, 28, 65, 12
 CONTROL "Quit", IDC_QUIT, "BUTTON", BS_PUSHBUTTON | WS_CHILD |
                                  WS_VISIBLE | WS_TABSTOP, 228, 41, 65, 12
 CONTROL "", IDC_TEXTAREA, "EDIT", ES_LEFT | ES_MULTILINE |
                                   WS_CHILD | WS_VISIBLE |
                                  WS_BORDER | WS_TABSTOP, 10, 10, 198, 46
 CONTROL "", IDC_TEXTFIELD, "edit", ES_LEFT | WS_CHILD |
                                    WS_VISIBLE | WS_BORDER |
                                    WS_TABSTOP, 228, 10, 66, 14
}
/* conventional about box */
AboutBox DIALOG 31, 24, 160, 52
STYLE WS_POPUP | WS_DLGFRAME
{
   CONTROL "Resizable Dialog Client", -1, "STATIC", SS_CENTER |
                                                    WS_CHILD |
                                                    WS_VISIBLE |
                                                    WS_GROUP, 16, 10, 128, 12
   CONTROL "OK", IDOK, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD |
                                 WS_VISIBLE | WS_GROUP |
                                 WS_TABSTOP, 64, 32, 32, 14
}

Back to Article

Listing Five

/*resize.rh*/

#define IDC_STATICTEXT  101
#define IDC_TEXTAREA    102
#define IDC_TEXTFIELD   103
#define IDC_ABOUT       104
#define IDC_QUIT        105

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.