Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
Member Area
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Become a Member
-Why sign up!
-Chat Online!
-Indexes NEW!!
-Build your resume
-Find a job
-Post a job
-Resume Search
-Link to us
Visit Embarcadero
Embarcadero Community
How can I put a button on a form's caption bar? Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
Delphi 2.x
User Rating
No Votes
# Votes
DSP, Administrator
Reference URL:
			Author: Jonas Bilinkevicius

I've seen some programs that add text or buttons on the title bar of a form. How 
can I do this in Delphi?



I got my first insight into solving this problem when I wrote a previous tip that 
covered rolling up the client area of forms so that only the caption bar showed. In 
my research for that tip, I came across the WMSetText message that is used for 
drawing on a form's canvas. I wrote a sample application to test drawing in the 
caption area. The only problem with my original code was that the button would 
disappear when I resized or moved the form.

I turned to Delphi/Pascal guru Neil Rubenking for help. He pointed me in the 
direction of his book, Delphi Programming Problem Solver, which contains an example 
for doing this exact thing. The code below is an adaptation of the example in his 
book. The most fundamental difference between our examples is that I wanted to make 
a speedbutton with a bitmap glyph, and Neil actually drew a shape directly on the 
canvas. He also placed the button created in 16-bit Delphi on the left-hand side of 
the frame, and Win32 button placement was on the right. I wanted my buttons to be 
placed on the right for both versions, so I wrote appropriate code to handle that. 
The deficiency in my code was the lack of handlers for activation and painting in 
the non-client area of the form.

One thing I'm continually discovering is that there is a very definitive structure 
in Windows &mdash a definite hierarchy of functions. I've realized that the thing 
that makes Windows programming at the API level difficult is the sheer number of 
functions in the API set. For those who are reluctant to dive into the WinAPI, 
think in terms of categories first, then narrow your search. You'll find that doing 
it this way will make your life much easier.

What makes all of this work is Windows messages. The messages we're interested in 
here are not the usual Windows messages handled by plain-vanilla Windows apps, but 
are specific to an area of a window called the non-client area. The client area of 
a window is the part inside the border where most applications present information. 
The non-client area consists of the window's borders, caption bar, system menu and 
sizing buttons. The Windows messages that pertain to this area have the naming 
convention of WM_NCMessageType. Taking the name apart, 'WM' stands for Windows 
Message, 'NC' stands for Non-client area, and MessageType is the type of message 
being trapped. For example, WM_NCPaint is the paint message for the non-client 
area. Taking into account the hierarchical and categorical nature of the Windows 
API, nomenclature is a very big part of it; especially with Windows messages. If 
you look in the help file under messages, peruse through the list of messages and 
you will see the order that is followed.

Let's look at a list of things that we need to consider to add a button to the 
title bar of a form:

We need to have a function to draw the button.
We'll have to trap drawing and painting events so that our button stays visible 
when the form activates, resizes or moves.
We're dropping a button on the title bar, so we have to have a way of trapping for 
a mouse click on the button.

I'll now discuss these topics, in the above order.

Drawing a TRect as a Button

You can't drop VCL objects onto a non-client area of a window, but you can draw on 
it and simulate the appearance of a button. In order to perform drawing in the 
title bar of a window, you have to do three very important things, in order:

You must get the current measurements of the window and the size of the frame 
bitmaps so you know what area to draw in and how big to draw the rectangle.
Then you have to define a TRect structure with the proper size and position within 
the title bar.
Finally, you have to draw the TRect to appear as a button, then add any glyphs or 
text you might want to draw to the buttonface.

All of this is accomplished in a single call. For this program we make a call to 
the DrawTitleButton procedure, which is listed below:

1   procedure TTitleBtnForm.DrawTitleButton;
2   var
3     bmap: TBitmap; {Bitmap to be drawn - 16 X 16 : 16 Colors}
4     XFrame, {X and Y size of Sizeable area of Frame}
5     YFrame,
6     XTtlBit, {X and Y size of Bitmaps in caption}
7     YTtlBit: Integer;
8   begin
9     {Get size of form frame and bitmaps in title bar}
10    XFrame := GetSystemMetrics(SM_CXFRAME);
11    YFrame := GetSystemMetrics(SM_CYFRAME);
12    XTtlBit := GetSystemMetrics(SM_CXSIZE);
13    YTtlBit := GetSystemMetrics(SM_CYSIZE);
15  {$IFNDEF WIN32}
16    TitleButton := Bounds(Width - (3 * XTtlBit) - ((XTtlBit div 2) - 2),
17      YFrame - 1,
18      XTtlBit + 2,
19      YTtlBit + 2);
21  {$ELSE} {Delphi 2.0 positioning}
22    if (GetVerInfo = VER_PLATFORM_WIN32_NT) then
23      TitleButton := Bounds(Width - (3 * XTtlBit) - ((XTtlBit div 2) - 2),
24        YFrame - 1,
25        XTtlBit + 2,
26        YTtlBit + 2)
27    else
28      TitleButton := Bounds(Width - XFrame - 4 * XTtlBit + 2,
29        XFrame + 2,
30        XTtlBit + 2,
31        YTtlBit + 2);
32  {$ENDIF}
34    Canvas.Handle := GetWindowDC(Self.Handle); {Get Device context for drawing}
35    try
36      {Draw a button face on the TRect}
37      DrawButtonFace(Canvas, TitleButton, 1, bsAutoDetect, False, False, False);
38      bmap := TBitmap.Create;
39      bmap.LoadFromFile('help.bmp');
40      with TitleButton do
41  {$IFNDEF WIN32}
42        Canvas.Draw(Left + 2, Top + 2, bmap);
43  {$ELSE}
44        if (GetVerInfo = VER_PLATFORM_WIN32_NT) then
45          Canvas.Draw(Left + 2, Top + 2, bmap)
46        else
47          Canvas.StretchDraw(TitleButton, bmap);
48  {$ENDIF}
50    finally
51      ReleaseDC(Self.Handle, Canvas.Handle);
52      bmap.Free;
53      Canvas.Handle := 0;
54    end;
55  end;

Step 1 above is accomplished by making four calls to the WinAPI function 
GetSystemMetrics, asking the system for the width and height of the window that can 
be sized (SM_CXFRAME and SM_CYFRAME), and the size of the bitmaps contained on the 
title bar (SM_CXSIZE and SM_CYSIZE).

Step 2 is performed with the Bounds function, which returns a TRect defined by the 
size and position parameters that are supplied to it. Notice that I used some 
conditional compiler directives here. This is because the size of the title bar 
buttons in Windows 95 and Windows 3.1 are different, so they have to be sized 
differently. And since I wanted to be able to compile this in either version of 
Windows, I used a test for the predefined symbol, WIN32, to see which version of 
Windows the program is compiled under. However, since the Windows NT UI is the same 
as Windows 3.1, it's necessary to grab further version information under the Win32 
conditional to see if the Windows version is Windows NT. If so, we define the TRect 
to be just like the Windows 3.1 TRect.

To perform Step 3, we make a call to the Buttons unit's DrawButtonFace to draw 
button features within the TRect that we defined. As added treat, I included code 
to draw a bitmap in the button. You'll see that I used a conditional compiler 
directive to draw the bitmap under different versions of Windows. I did this 
because the bitmap I used was 16x16 pixels, which might be too big for Win95 
buttons. So I used StretchDraw under Win32 to stretch the bitmap to the size of the 

Trapping the Drawing and Painting Events

You must make sure that the button will stay visible every time the form repaints 
itself. Painting occurs in response to activation and resizing, which fire off 
paint and text setting messages that will redraw the form. If you don't have a 
facility to redraw your button, you'll lose it every time a repaint occurs. So what 
we have to do is write event handlers which will perform their default actions and 
redraw our button when they fire off. The following four procedures handle the 
paint triggering and painting events:

56  {Paint triggering events}
58  procedure TForm1.WMNCActivate(var Msg: TWMNCActivate);
59  begin
60    inherited;
61    DrawTitleButton;
62  end;
64  procedure TForm1.FormResize(Sender: TObject);
65  begin
66    Perform(WM_NCACTIVATE, Word(Active), 0);
67  end;
69  {Painting events}
71  procedure TForm1.WMNCPaint(var Msg: TWMNCPaint);
72  begin
73    inherited;
74    DrawTitleButton;
75  end;
77  procedure TForm1.WMSetText(var Msg: TWMSetText);
78  begin
79    inherited;
80    DrawTitleButton;
81  end;

Every time one of these events fires off, it makes a call to the DrawTitleButton 
procedure. This will ensure that our button is always visible on the title bar. 
Notice that we use the default handler OnResize on the form to force it to perform 

Handling Mouse Clicks

Now that we've got code that draws our button and ensures that it's always visible, 
we have to handle mouse clicks on the button. The way we do this is with two 
procedures. The first procedure tests to see if the mouse click was in the area of 
our button, then the second procedure actually performs the code execution 
associated with our button. Let's look at the code:

82  {Mouse-related procedures}
84  procedure TForm1.WMNCHitTest(var Msg: TWMNCHitTest);
85  begin
86    inherited;
87    {Check to see if the mouse was clicked in the area of the button}
88    with Msg do
89      if PtInRect(TitleButton, Point(XPos - Left, YPos - Top)) then
90        Result := htTitleBtn;
91  end;
93  procedure TForm1.WMNCLButtonDown(var Msg: TWMNCLButtonDown);
94  begin
95    inherited;
96    if (Msg.HitTest = htTitleBtn) then
97      ShowMessage('You pressed the new button');
98  end;

The first procedure WMNCHitTest(var Msg : TWMNCHitTest) is a hit tester message to 
determine where the mouse was clicked in the non-client area. In this procedure we 
test if the point defined by the message was within the bounds of our TRect by 
using the PtInRect function. If the mouse click was performed in the TRect, then 
the result of our message is set to htTitleBtn, which is a constant that was 
declared as htSizeLast + 1. htSizeLast is a hit test constant generated by hit test 
events to test where the last hit occurred.

The second procedure is a custom handler for a left mouse click on a button in the 
non-client area. Here we test if the hit test result was equal to htTitleBtn. If it 
is, we show a message. You can make any call you choose to at this point.

Putting it All Together

Let's look at the entire code in the form to see how it all works together:

99  unit Capbtn;
101 interface
103 uses
104   SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
105   Forms, Dialogs, Buttons;
107 type
108   TTitleBtnForm = class(TForm)
109     procedure FormResize(Sender: TObject);
110   private
111     TitleButton: TRect;
112     procedure DrawTitleButton;
113     {Paint-related messages}
114     procedure WMSetText(var Msg: TWMSetText); message WM_SETTEXT;
115     procedure WMNCPaint(var Msg: TWMNCPaint); message WM_NCPAINT;
116     procedure WMNCActivate(var Msg: TWMNCActivate); message WM_NCACTIVATE;
117     {Mouse down-related messages}
118     procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
119     procedure WMNCLButtonDown(var Msg: TWMNCLButtonDown); message WM_NCLBUTTONDOWN;
120     function GetVerInfo: DWORD;
121   end;
123 var
124   TitleBtnForm: TTitleBtnForm;
126 const
127   htTitleBtn = htSizeLast + 1;
129 implementation
130 {$R *.DFM}
132 procedure TTitleBtnForm.DrawTitleButton;
133 var
134   bmap: TBitmap; {Bitmap to be drawn - 16 X 16 : 16 Colors}
135   XFrame, {X and Y size of Sizeable area of Frame}
136   YFrame,
137     XTtlBit, {X and Y size of Bitmaps in caption}
138   YTtlBit: Integer;
139 begin
140   {Get size of form frame and bitmaps in title bar}
141   XFrame := GetSystemMetrics(SM_CXFRAME);
142   YFrame := GetSystemMetrics(SM_CYFRAME);
143   XTtlBit := GetSystemMetrics(SM_CXSIZE);
144   YTtlBit := GetSystemMetrics(SM_CYSIZE);
146 {$IFNDEF WIN32}
147   TitleButton := Bounds(Width - (3 * XTtlBit) - ((XTtlBit div 2) - 2),
148     YFrame - 1,
149     XTtlBit + 2,
150     YTtlBit + 2);
152 {$ELSE} {Delphi 2.0 positioning}
153   if (GetVerInfo = VER_PLATFORM_WIN32_NT) then
154     TitleButton := Bounds(Width - (3 * XTtlBit) - ((XTtlBit div 2) - 2),
155       YFrame - 1,
156       XTtlBit + 2,
157       YTtlBit + 2)
158   else
159     TitleButton := Bounds(Width - XFrame - 4 * XTtlBit + 2,
160       XFrame + 2,
161       XTtlBit + 2,
162       YTtlBit + 2);
163 {$ENDIF}
165   Canvas.Handle := GetWindowDC(Self.Handle); {Get Device context for drawing}
166   try
167     {Draw a button face on the TRect}
168     DrawButtonFace(Canvas, TitleButton, 1, bsAutoDetect, False, False, False);
169     bmap := TBitmap.Create;
170     bmap.LoadFromFile('help.bmp');
171     with TitleButton do
172 {$IFNDEF WIN32}
173       Canvas.Draw(Left + 2, Top + 2, bmap);
174 {$ELSE}
175       if (GetVerInfo = VER_PLATFORM_WIN32_NT) then
176         Canvas.Draw(Left + 2, Top + 2, bmap)
177       else
178         Canvas.StretchDraw(TitleButton, bmap);
179 {$ENDIF}
181   finally
182     ReleaseDC(Self.Handle, Canvas.Handle);
183     bmap.Free;
184     Canvas.Handle := 0;
185   end;
186 end;
188 {Paint triggering events}
190 procedure TTitleBtnForm.WMNCActivate(var Msg: TWMNCActivate);
191 begin
192   inherited;
193   DrawTitleButton;
194 end;
196 procedure TTitleBtnForm.FormResize(Sender: TObject);
197 begin
198   Perform(WM_NCACTIVATE, Word(Active), 0);
199 end;
201 {Painting events}
203 procedure TTitleBtnForm.WMNCPaint(var Msg: TWMNCPaint);
204 begin
205   inherited;
206   DrawTitleButton;
207 end;
209 procedure TTitleBtnForm.WMSetText(var Msg: TWMSetText);
210 begin
211   inherited;
212   DrawTitleButton;
213 end;
215 {Mouse-related procedures}
217 procedure TTitleBtnForm.WMNCHitTest(var Msg: TWMNCHitTest);
218 begin
219   inherited;
220   {Check to see if the mouse was clicked in the area of the button}
221   with Msg do
222     if PtInRect(TitleButton, Point(XPos - Left, YPos - Top)) then
223       Result := htTitleBtn;
224 end;
226 procedure TTitleBtnForm.WMNCLButtonDown(var Msg: TWMNCLButtonDown);
227 begin
228   inherited;
229   if (Msg.HitTest = htTitleBtn) then
230     ShowMessage('You pressed the new button');
231 end;
233 function TTitleBtnForm.GetVerInfo: DWORD;
234 var
235   verInfo: TOSVERSIONINFO;
236 begin
237   verInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
238   if GetVersionEx(verInfo) then
239     Result := verInfo.dwPlatformID;
240   {Returns:
241     VER_PLATFORM_WIN32s             Win32s on Windows 3.1
242     VER_PLATFORM_WIN32_WINDOWS        Win32 on Windows 95
243     VER_PLATFORM_WIN32_NT           Windows NT }
244 end;
246 end.

Suggestions for Exploring

You might want to play around with this code a bit to customize it to your own 
needs. For instance, if you want to add a bigger button, add pixels to the XTtlBit 
var. You can also mess around with creating a floating toolbar that is purely on 
the title bar. Also, now that you have a means of interrogating what's going on in 
the non-client area of the form, you might want to play around with the default 
actions taken with the other buttons like the System Menu button to perhaps display 
your own custom menu.

Take heed, though: Playing around with Windows messages can be dangerous. Save your work constantly, and be prepared for some system crashes while you experiment.

Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10


Share this page
Download from Google

Copyright © Mendozi Enterprises LLC