Meta

Setting up a custom title bar on Vista/Windows 7

Back in the day, if you wanted to draw in a form’s title bar for some reason (to, for example, add one or more extra buttons), you would handle the WM_NCPAINT message and draw to a handle got from the GetDCEx function. While you can still do this with recent versions of Windows, it will cause the window frame to lose its ‘glass’ styling and translucent effects, something that is generally not what you want.

How, then, should you go about customising the title bar in the ‘Aero’ world of Vista and Windows 7? In this post and its sequel, I will document my attempts at finding out. One thing I should emphasise at the off is that this has been a matter of trial and error on my part, of playing around really. Moreover, one might well think that custom window frames are a crappy idea in the first place — even I can’t see the point of Google Chrome putting its page tabs inside its title bar rather than below it, for example. Nonetheless, they can have their use, and anyway, solving a Windows API puzzle can be interesting in itself…

The basics

Realistically, the minimum Delphi version you should be using here is 2007, which makes sense given this version was released not long after Vista debuted. While you could struggle along with an older version — hell, Delphi 2 might be considered ‘good enough’ to the extent of having a 32 bit compiler capable of calling the Desktop Window Manager (DWM) API — Delphi 2007 both does some of the work of setting up the glass frame for you, and more importantly, amends aspects of the VCL’s implementation to be more (though by no means totally) glass friendly.

Now if you check out MSDN, you’ll come across an article with the very promising title ‘Custom Window Frame Using DWM’. This is basically a mild edit of an earlier blog post on the Windows shell team’s (now discontinued) blog, so you might think it would be the be all and end all of the matter. Unfortunately it is not though, the elephant in the room being its advice to completely remove your window’s (i.e., your form’s) non-client area (see the section entitled ‘Removing the Standard Frame’). The problem with this is that doing such a thing messes up the position of every child control. While this is OK if you can guarantee your application will only run when the DWM is active, making such a guarantee would be foolish given it only takes a few clicks for the user to disable the thing and return to a non-glassy world.

Nonetheless, the basic principle outlined in the article still holds: namely, that to draw on a glass title bar, you have to (a) extend the client area onto it and (b) extend the glass area by the same amount that you just added to the client area. (In practice, given the way Windows works, you actually do (b) before (a), but I’ll come to that in a moment.) Painting must then be done on a bitmap in a specific format, which then gets blitted to the DWM’s own buffer. Given most GDI functions (and thus, most native Delphi graphics) are not alpha-channel aware, you also have to be a bit careful about what you draw onto the bitmap. Nonetheless, the VCL does a fair bit of the legwork overall, even if there’s still a lot of things to do manually, as we shall now see.

Setting things up

To get going, create a new VCL application, and head for the code editor. There, create a uses clause in the implementation section, and add DwmApi to it. Then, to the form class, add a private integer field called FWndFrameSize, and the following code as a handler for the form’s OnCreate event:

This code first checks to see whether glass is enabled, before extending the glass frame by the size of the title bar. The SetWindowPos call then causes the non-client area to be recalculated, and setting DoubleBuffered to True gets the VCL to do the special buffering I mentioned earlier (in Delphi 2009 or later, it also causes child controls to be double buffered — more on that in a later post however).

Run the application, and you’ll find it produces a form with a fat title bar, since while we’ve increased the glass frame size, we haven’t taken out the non-client area for the standard title bar. To do that, we need to now override the default handling of the WM_NCCALCSIZE message. For this, add the following to the form’s definition:

Basically, the input rectangle here is the new window bounds. By leaving Top alone, we are in effect extending the client area onto where the non-client area would normally be. (An alternative implementation would be to call ‘inherited’ and change Top to be back to what it was before calling the default implementation.)

So, run the application again; this time, the title bar should be restored to its proper size. However, you may now note the following issues:

Both the form’s icon and its caption have been lost.

The title bar itself does not do anything when you double-click or right-click it, or indeed attempt to drag it.

The form’s top border does not allow us to resize the form any more.

While the minimise/maximise/close buttons still show, they don’t do anything (you’ve have to press Alt+F4 to exit the application cleanly).

Under 64 bit versions of Windows, ‘ghost’ buttons can emerge underneath the real ones — and unlike the real ones, the zombies do actually work! To see this, hover the mouse cursor just below the close button, directly underneath or just right of the X; after a short while, the ‘Close’ tooltip will show. If you then press and hold down the left mouse button, the ghostly appearance of an old-style close button will emerge; let go, and the form will close.

All in all, it’s time to do a bit of fixing.

Fix, fix and fix again

With respect to the real buttons not working, the fix is to call a particular DWM API function when handling the WM_NCHITTEST message. Since the name of this function is the rather generic-sounding DwmDefWindowProc, I would do this by overriding the form’s WndProc method as thus:

While that was nice and easy, fixing the other issues is a bit more involved. In the first instance, we need to be able to know where the icon should be, so define a private method called GetSysIconRect as thus:

With this, we can now amend the handling of the WM_NCHITEST message to detect the system icon area as normal. A custom WM_NCHITEST handler is also where we slay the ghost buttons, the method being to correct any claim of the default handler to have found where the minimise/maximise/close buttons should be (this works because the real buttons are all handled by a separate DWM process rather than the form and its underlying window).

This should all be pretty self-explanatory. Perhaps the only line that isn’t is the one with the ControlAtPos call — all that’s for, though, is to make sure we don’t prevent any TGraphicControl descendant placed on the custom title bar from receiving mouse messages.

Anyhow, run the application once more, and you should find the following:

Double clicking the form’s title bar now correctly toggles its maximised state.

The top border of the form once more allows us to resize it.

While it isn’t visible, left-clicking where the icon belongs brings up the system menu. This exhibits the behaviour of the title bar in Windows Explorer under both Vista and Windows 7.

Alas, but two issues remain, along with a third that is now explicit given the maximise button works:

We still need to actually draw the icon. There’s also the caption to be drawn too.

Right-clicking the title bar still doesn’t do what it should, which is to bring up the system menu.

Maximise the form, then minimise it, then un-minimize it. The client area goes all black!

The third issue here is actually the first time the VCL’s default handling of things has got in the way (all the other issues have been standard DWM irritations you would have to face when using any framework). Basically, when trapping the WM_WINDOWPOSCHANGING message, TCustomForm can set a flag that says the glass frame needs to be ‘refreshed’, which in practice means painting the form black in PaintWindow, a virtual protected method inherited from TWinControl (Ord(clBlack) = 0 = completely transparent if an alpha channel is assumed). Presumably because the code doesn’t imagine an extended glass frame will be used only to make up for an extended client area, this flag then ends up getting set when we don’t want it to.

Nonetheless, after a bit of playing around, I found a fix that’s pretty simple — basically, you just need to clip out the client area in an override for PaintWindow:

Note that if you want to draw outside of the custom title bar area in either an override for the Paint method or a handler for the OnPaint event, you’ll have to now reset the clipping region first.

While we’re about it, we should also ensure the form gets invalidated appropriately when being resized. For this, define both a private method called InvalidateTitleBar and a custom handler for the WM_WINDOWPOSCHANGING message, implementing them like this:

We obviously now need a ShowSystemMenu routine. This turns out to be a bit long-winded given the need to initialise the menu ourselves. Moreover, the actual showing and making effective of the user’s selection requires a bit of hocus pocus, though the following implementation works well for me:

Restoring the icon and caption

In a standard VCL application, a form’s icon as displayed will come either from its Icon property, or if that is empty (as it will be by default), Application.Icon. Given this and the fact we have already calculated where it should be drawn, one might think of handling the form’s OnPaint event as thus:

Unless you’ve explicitly set a 32 bit icon, however, this won’t work properly due to the alpha channel issue I mentioned in passing near the start. One easy-ish way to get around this, though, is to use a 32 bit image list as an intermediary.

So, drop a TImageList component onto the form; if you are using Delphi 2009 or later, go and set its ColorDepth property to cd32bit. Then, add CommCtrl to a uses clause, and handle the form’s OnPaint event like this:

If you’re wondering, the $IF is to support Delphi 2007, and the call to ImageList_AddIcon directly is to avoid an entirely unnecessary exception liable to be raised by TImageList.AddIcon.

With the icon done, the last piece in the jigsaw is to reinstate the form’s caption. Now the advice on MSDN is to use the DrawThemeTextEx API function with a ‘glow size’ of 15, so we’ll follow that. Nonetheless, you need to watch out for the fact that if the form is maximised and running on Vista, it (a) shouldn’t have any glow effect and (b) should be drawn white.

Moreover, I’ve found that the correct font to use is not that found from any theming API, but the old-style GetSysColor and SystemParametersInfo functions — basically, even if you manage to figure out valid parameter values for the theming API equivalents, you’ll only be getting back default values that the user may have overridden in the Control Panel.

Putting this all together, I have come up with the following utility function. It’s a bit lengthy partly because it is intended to be properly generic, though much of it is needed even for the simplest case:

To get this to compile, you’ll need to add both UxTheme and Themes to a uses clause, together with StdCtrls (the latter for the TTextLayout enumerated type). Just to make things a touch easier, define the following utility function too:

Lastly, invalidate the title bar when the form is either activated or deactivated — we need to do this since the font colour used in non-active title bars is typically different (if only slightly) from that used in active ones:

Run the application again, and you should find this has all produced a reasonable enough result. Admittedly, and despite the advice on MSDN to use it, DrawThemeTextEx doesn’t paint the caption exactly like the standard frame does — if you look closely, you’ll see the latter uses a solid rectangular background ‘glow’ rather than a character-outlining one. Nonetheless, the DrawThemeTextEx approach seems to be what the custom frames of Microsoft applications use — check out (say) Word 2007 or Windows Live Movie Maker to see what I mean.

That said, the whole point of this exercise was not simply to replicate the standard frame as best we could; rather, it was to add things to it. So, back in the form designer, drop a TSpeedButton onto the form, and place it near the top of the client area:

Add a handler for the button’s OnClick handler if you wish, then run the application once more; if things are all well, it should look like this:

Result!

[Note: I amended this article on 30/1/11 to fix a ghost buttons issue when running on Windows 7 64 bit.]

Can somebody please explain to me what VCL is? It’s mentioned throughout the article (great article by the way), but I have no idea what it is. And I understand the code, but what language is it in? Is VCL the language being used?

Heh, clue’s in the site’s name! ;-) As you say though, most of my article isn’t Delphi-specific, so long as you can translate the Pascal into your language of choice. (FWIW, the VCL is Dephi’s native framework, equivalent to WinForms in .NET.)

This solution dont work correctly, if I use Vista with Classic Windows theme.

My code concerns when the Desktop Window Manager (DWM) is turned on. When the Windows Classic theme is selected, the DWM is turned off. So, to tell whether you can do the things I do here, you need to call the DwmCompositionEnabled function (it’s defined in DwmApi), which is what I in fact do in the example FormCreate code above.

Hi,
great article, I found an artifact that you never mentioned: if I minimize the form in a multiform application it goes just above the taskbar showing restore/maximize/close buttons and a semi circular shape instead of the application’s icon and no caption at all.
I’m on W7 with glass effects enabled but no transparency.

Just some observations – you’ll have to debug it properly yourself I’m afraid:

1. The ‘semi circular shape instead of the application’s icon’ actually *is* the application’s icon, only drawn at 32×32 and without taking account of a 32 bit colour destination.

2. A secondary form won’t minimise correctly depending on the way you make it independent of the main form. I’m not sure your problem here is definitely connected to the custom frame, though it’s difficult to say without being able to see and debug the actual code.

3. I get the ghost buttons myself if I tried the shorter way of creating a custom frame suggested by another commenter here (link). All I can say is that I haven’t come across it otherwise.

To get the ghost files click around below the minimize, maximize and close buttons. You have to click to the left and below these buttons, what is weird is that where you click isn’t where the actual buttons are, but yet they get activated, very very weird,

@vitimtk – That’s nothing to do with a custom frame, but hey, I’ve Googled for you and come up with this solution:
1. At design time, clear the BorderIcons property, and set BorderSize to bsSizeToolWin.
2. Override the protected CreateWindowHandle method like this:

Nice Post, but i seems to have made a mistake somewhere, my Icon is not showing and I cannot move the Form by clicking on the title bar, any chance you can upload a copy of this project source so i can compare?

Hello could you tell me if the code of the article could send it? I tried it in my Delphi but not working. If it’s good enough to send it to check it out in my Delphi appreciate it, thanks. salsavalencia@hotmail.com