Creating Forms with Custom Title Bars (Part III)

Copyright © 1995, Mark R. Johnson

RETURN TO... Creating Forms with Custom Title Bars (Part II)

2. Creating a System Menu

As you may have noticed in the DoMenu function, we are creating our own pop up menu in response to mouse clicks on the window's control box. We must do this because the default methods for showing the system menu also draw the control box. Since we are drawing the control box, we must also, therefore, handle showing a system menu. To do this, we must fool the default message handlers into thinking that we do not have a system menu so they will not draw one. Then, as in response to the WM_NCLBUTTONDOWN message, we must handle showing our own menu.

We will need to respond to the following window messages to handle the system menu:

Add the following lines to the protected section of the TForm1 declaration that we created during Step 1:

  ...
    procedure WMSysChar(var Message : TWMSysChar); message WM_SYSCHAR;
    procedure WMCommand(var Message : TMessage); message WM_COMMAND;
    procedure WMKeyDown(var Message : TWMKeyDown); message WM_KEYDOWN;
    procedure WMKeyUp(var Message : TWMKeyUp); message WM_KEYUP;
    procedure WMSysKeyDown(var Message : TWMSysKeyDown); message WM_SYSKEYDOWN;
    procedure WMSysKeyUp(var Message : TWMSysKeyUp); message WM_SYSKEYUP;
  ...

WM_SYSCHAR

The WM_SYSCHAR message is sent to the active window when a key is pressed and released while holding down the ALT key. If the key pressed was the spacebar (ALT+SPACE), then the system menu is shown. Here we will check for the spacebar keypress and show the system menu if needed.

Add the WMSysChar procedure to the implementation section of the unit.

procedure TForm1.WMSysChar(var Message : TWMSysChar);
begin
  if HasCaption and (Message.CharCode = VK_SPACE) then
    DoMenu
  else
    inherited;  {Call default processing.}
end;

WM_COMMAND

The WM_COMMAND message is sent to a window when the user selects an item from a menu, a control sends a notification to its parent, or an accelerator key is translated. The ID of the source of the message is sent in wParam. In response to this message, we will check the ID to see if it comes from a system menu item. If so, we will send a WM_SYSCOMMAND message with the same ID so the default message handlers for the system menu can respond.

Add the WMCommand procedure to the implementation section of the unit.

procedure TForm1.WMCommand(var Message : TMessage);
begin
  if Message.wParam >= $F000 then
    PostMessage(Handle, WM_SYSCOMMAND, Message.wParam, Message.lParam);
  inherited;  {Call default processing.}
end;

WM_KEYDOWN, WM_KEYUP,
WM_SYSKEYDOWN, WM_SYSKEYUP

The WM_KEYDOWN and WM_KEYUP messages are sent to the active window when a key is pressed and released, respectively. The *SYS* counterparts are sent if the key is pressed/released while holding down the ALT key. Since any of these actions could cause the system menu to pop up, we will hide the WS_SYSMENU window style before passing the message on to the default handlers, fooling the default handlers into thinking that our form has no system menu. This leaves it up to us to handle creating and showing the system menu, as we have done in the previous sections.

Add the following message handlers to the implementation section of the unit.

procedure TForm1.WMKeyDown(var Message : TWMKeyDown);
var
  dwStyle : longint;
begin
  dwStyle := GetWindowLong(Handle, GWL_STYLE);
  SetWIndowLong(Handle, GWL_STYLE, dwStyle and not longint(WS_SYSMENU));
  inherited;  {Call default processing.}
  SetWindowLong(Handle, GWL_STYLE, dwStyle);
end;

procedure TForm1.WMKeyUp(var Message : TWMKeyUp);
var
  dwStyle : longint;
begin
  dwStyle := GetWindowLong(Handle, GWL_STYLE);
  SetWIndowLong(Handle, GWL_STYLE, dwStyle and not longint(WS_SYSMENU));
  inherited;  {Call default processing.}
  SetWindowLong(Handle, GWL_STYLE, dwStyle);
end;

procedure TForm1.WMSysKeyDown(var Message : TWMSysKeyDown);
var
  dwStyle : longint;
begin
  dwStyle := GetWindowLong(Handle, GWL_STYLE);
  SetWIndowLong(Handle, GWL_STYLE, dwStyle and not longint(WS_SYSMENU));
  inherited;  {Call default processing.}
  SetWindowLong(Handle, GWL_STYLE, dwStyle);
end;

procedure TForm1.WMSysKeyUp(var Message : TWMSysKeyUp);
var
  dwStyle : longint;
begin
  dwStyle := GetWindowLong(Handle, GWL_STYLE);
  SetWIndowLong(Handle, GWL_STYLE, dwStyle and not longint(WS_SYSMENU));
  inherited;  {Call default processing.}
  SetWindowLong(Handle, GWL_STYLE, dwStyle);
end;

3. Setting the Minimum Window Size

The final step before we can compile and use our customized form is to provide accurate window size information. This has already been partly accomplished with our WM_NCCALCSIZE message handler. However, we also need to respond to the WM_GETMINMAXINFO message to specify the minimum and maximum acceptable sizes for the window.

WM_GETMINMAXINFO

The WM_GETMINMAXINFO message is sent to a window when the smallest and largest acceptable sizes need to be determined for the window, such as when the window is being resized. Since we have customized the title bar size, we must respond to this event and return appropriate values. In this case, the maximum allowable size is unaffected, but the minimum size, based on the width and height of the title bar, is affected. Typically, the smallest acceptable height is equivalent to the title bar height plus twice the window frame thickness. The smallest acceptable width is the combined widths of the control box, min. button, and max. button (those which the form contains) plus twice the title bar height.

Add the following WMGetMinMaxInfo message handler declaration to the protected section of TForm1, and the definition to the implementation section of the unit:

  ...
    procedure WMGetMinMaxInfo(var Message : TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
  ...
procedure TForm1.WMGetMinMaxInfo(var Message : TWMGetMinMaxInfo);
var
  nX     : integer;
  cy     : integer;
  rcBox  : TRect;
  rcMin  : TRect;
  rcMax  : TRect;
begin
  if HasCaption and TestWinStyle(WS_THICKFRAME) then begin
    cy := GetSystemMetrics(SM_CYFRAME);
    {The following functions return empty rects. if box/button doesn't exist}
    GetControlBoxRect(rcBox);
    GetMinButtonRect(rcMin);
    GetMaxButtonRect(rcMax);
    nX := (rcBox.right - rcBox.left) +
          (rcMin.right - rcMin.left) +
          (rcMax.right - rcMax.left);
    with Message.MinMaxInfo^.ptMinTrackSize do begin
      x := nX + 2 * TitleBarSize;
      y := TitleBarSize + 2 * cy - 1;
    end;
  end;
end;

4. Compiling and Testing the Form

At this point, we're ready to compile and test our customized form. If you receive any errors during the compilation, or if the form does not appear to behave propery when run, refer back to the source code in this article and compare with your own.

You should be able to add controls to the form and change its style attributes in the Delphi Object Browser, just as you can a normal form. For example, try changing the BorderIcons or BorderStyle properties of the customized form. Again, if you experience any strange behavior, refer back to the code in this document to locate possible errors.


5. Installing the Form as a Template

Once the form has been compiled and tested, we are ready to install the form as a template for future use. Delphi's form templates allow you to easily reuse forms from previous work without having to recode all of your customizations.

To install your LILTITLE.PAS form as a template, follow these steps:

  1. Make sure you have the project with LILTITLE.PAS open.
  2. With the mouse cursor positioned over the form, select Save As Template from the SpeedMenu (right mouse click).
  3. A Save Form Template dialog box should appear with Form1 in the Forms list and "LILTITLE" in the File Name field.
  4. Enter "Little Titles" into the Title field, and "Forms with small title bars." in the Description field.
  5. Use the Browse button to select a bitmap to represent the form, if you wish.
  6. Press the OK button to accept the new form template.

From now on, when you request a new form, you will see the Little Titles form in the Browse Gallery. Select this image and press the OK button, if you want a Little Titles form in your project. All of the code we have entered here will automatically accompany the form.


[ Home Page | What's New | About CITY ZOO | Borland Delphi | About the Authors | INDEX ]
keeper@mindspring.com
Copyright © 1995 Mark R. Johnson. This is a CITY ZOO production.
Last revised June 14, 1995.
Enhanced version