
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;
...
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;
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;
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;
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.
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;
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.
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:
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.
