Setting up a custom title bar – reprise

As a follow up to my last post, I’ve written a small demo that (rather pointlessly of course) enables custom drawing in the main form’s title bar only to reinstate the standard elements. It does this across changes to the form’s BorderIcons, BorderStyle, Caption and Icon properties, and has a check box to allow easily comparing to the ‘real thing’:

The substantive code is essentially the same as what I presented last time, though one small but important thing it adds is a couple of lines to combat the problem of changed client coordinates.

If you recall, this was the issue of how in enabling custom drawing on a form’s title bar, you must extend its client area onto it, messing up the position of child controls in the process. While merely taking away the top part of the non-client area was better than the MSDN-suggested solution of zapping the non-client area entirely, the problem did still remain.

A good way to counteract it, though, is to do two things: (1) move all non-aligned controls over to a frame, which is then top-aligned to the form; and (2) override the form’s AdjustClientArea method as appropriate. This method is a purely VCL-level thing that allows a parent control to force aligned children to be within a bounds less than the actual client area — of the standard controls, it’s used by TGroupBox to make sure a top aligned child (for example) will be below the group box caption rather than over it.

Given this, my demo simply overrides AdjustClientRect as thus:

procedure TfrmMain.AdjustClientRect(var Rect: TRect);
  if FUseCustomFrame then Inc(Rect.Top, GlassFrame.Top);

One thing the demo more unfortunately demonstrates too, though, is the issue I blogged about recently: unless you mark the EXE to require the Vista sub-system or later, the custom frame will not work properly with non-sizeable border styles.

Anyhow, if you’re interested, I’ve put the demo up on CodeCentral here.

[Edit, in response to comments: by design, the pre-compiled EXE won’t work on XP or earlier! Read what I wrote here for why. Not by design, I intially hacked away at the precompiled EXE too much. I’ve taken the EXE out of the ZIP for now.]

[Edit 2: check out the comment by Torbins below for a potentially much simpler alternative to taking out the non-client area. That said, I can’t get this to work properly when compiling with D2007 — it causes ‘ghost’ buttons in use, amongst artifacts.]

[Edit 3 (Jan 2011): I finally got round to fixing the demo. There’s also an additional fix for the ghost buttons that appeared on Windows 7 64 bit (these were the same buttons that plagued the alternative solution on all platforms), plus the source to a small console application I wrote to do the patching mentioned above. Download location is as before.]

19 thoughts on “Setting up a custom title bar – reprise

  1. On windows 7 I just get this error:
    This application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.


    • Ajasja — interesting! What happens if you recompile? (It may just be that a fiddled about with the resources too much – I edited them to get the EXE size down.)

  2. In case you do not need any controls in the non-client frame, just want to paint something there:
    DwmSetWindowAttribute(Handle, DWMWA_ALLOW_NCPAINT, @b, sizeof(b));
    With this code you can avoid client-area resizing, but still need to paint icon and caption.

    • Torbins — that doesn’t work properly for me, which is why I never brought it up. What’s your set up? (Thinking about the Delphi and Windows version in particular).

      • I have Windows Vista and Delphi 2010. The code above I have pleased in OnCreate event. And this is WM_NCPAINT handler:

        procedure TForm1.WMNCPaint(var Message: TWMNCPaint);
          dc: Cardinal;
          dc := GetWindowDc(Handle);
          if Message.RGN = 1 then
            FillRect(dc, Rect(0, 0, Width, Height), GetStockObject(BLACK_BRUSH));
            OffsetRgn(Message.RGN, -Left, -Top);
            FillRgn(dc, Message.RGN, GetStockObject(BLACK_BRUSH));
            OffsetRgn(Message.RGN, Left, Top);
          SetBkMode(dc, TRANSPARENT);
          SetTextColor(dc, $FFFFFF);
          //it is just an example, DrawGlassCaption should be here
          TextOut(dc, 5, 5, PChar('Hello!'), 6);
          ReleaseDC(Handle, dc);

        That’s all. May be demo will be helpful:

        • Interesting, thanks (I’ve wrapped your code in sourcecode tags to the formatting). An important benefit is that you can then use pretty much the same custom drawing code across Win2000, XP, Vista and W7, right? That said, when I recompile with D2007, there’s painting issues that I don’t get with your D2010 version.

          Separately, what’s the rationale of your demo not using the DrawThemeTextEx API?

          • I do not have Delphi 2007, and in Delphi 7 everything works fine.
            DrawAlphaText is much simpler in use then DrawThemeTextEx – ideal solution for a demo.

          • ‘I do not have Delphi 2007, and in Delphi 7 everything works fine.’

            It’s a bit weird – on some compiles it works, on others maximising and restoring the window leads to the border not being painted correctly (patches of white).

            Also, there are ‘ghost’ close/maximise/minimise buttons — click and hold just below the respective ‘real’ button, and the ghost one button will appear.

          • White patches I saw only a few times. And also there were black ones in the client area. May be it will be better to always redraw entire frame, except the client area. I have updated my demo.

      • Torbins –

        The bsSingle issue is the same one I noted was the case for my more long-winded method. You’ll find the same issue with any non-sizeable border style (bsDialog, bsToolWindow), and the ‘fix’ is the same – you have to change the EXE’s minimum sub-system version. Disappointingly though, even that doesn’t fix the ghost buttons, at least for me.

        With respect to the black, if you’re talking about the black flicker, then I think that’s inevitable whatever you do – ‘even’ Microsoft apps like Word 2007 suffer from it (try resizing a Word window – or for that matter, any ribbonised MS app – on a low-powered laptop that’s one or two years old. It’s not a pleasant user experience!). In fact, leaving customising the title bar aside, you can create this flicker by just setting GlassFrame.Bottom to 1 and GlassFrame.Enabled to True.

        I do find it a bit odd though that one of the ‘selling’ points of the DWM is that it kills flicker, yet doing anything ‘custom’ to the glass frame immediately creates one kind of flicker that you just can’t avoid.

        • I mean borders visually become smaller. And once you have commented DwmSetWindowAttribute they restore their normal size.

        • “mean borders visually become smaller”

          That is the issue I was talking about I think. The problem is that the DWM draws the frame as if the window itself were a bit smaller that it actually is.

  3. I get the ghost buttons on both of the demos here (the Custom Title Bar Test and Torbins demo), compiled on Delphi 2010 and Win7 x64.

    I’d love to get rid of them!

    • Definitely, there is potential for improvement in my demo. I also have some graphical artefacts. Looks like some additional messages should be hooked, but which ones? According to documentation WM_NCPAINT and WM_NCCALCSIZE should be enough.

      • Check out the revision to my version (I’ve amended both my original post and the demo – it’s the top of the WM_NCHITTEST that I’ve altered), which fixes my code’s ghost button issue on Win64. Playing around, doing the same thing with yours didn’t work though…

  4. Pingback: Custom title bars – reprise « Delphi Haven

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s