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:

procedure TForm1.FormCreate(Sender: TObject);
var
  R: TRect;
begin
  if DwmCompositionEnabled then
  begin
    SetRectEmpty(R);
    AdjustWindowRectEx(R, GetWindowLong(Handle, GWL_STYLE), False,
      GetWindowLong(Handle, GWL_EXSTYLE));
    FWndFrameSize := R.Right;
    GlassFrame.Top := -R.Top;
    GlassFrame.Enabled := True;
    SetWindowPos(Handle, 0, Left, Top, Width, Height, SWP_FRAMECHANGED);
    DoubleBuffered := True;
  end;
end;

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:

  protected
    procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;

And then this code to its implementation:

procedure TForm1.WMNCCalcSize(var Message: TWMNCCalcSize);
begin
  if not GlassFrame.Enabled then
    inherited
  else
    with Message.CalcSize_Params.rgrc[0] do
    begin
      Inc(Left, FWndFrameSize);
      Dec(Right, FWndFrameSize);
      Dec(Bottom, FWndFrameSize);
    end;
end;

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:

  1. Both the form’s icon and its caption have been lost.
  2. The title bar itself does not do anything when you double-click or right-click it, or indeed attempt to drag it.
  3. The form’s top border does not allow us to resize the form any more.
  4. 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).
  5. 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:

procedure TForm1.WndProc(var Message: TMessage);
begin
  if GlassFrame.Enabled and HandleAllocated and DwmDefWindowProc(Handle,
    Message.Msg, Message.WParam, Message.LParam, Message.Result) then Exit;
  inherited;
end;

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:

function TForm1.GetSysIconRect: TRect;
begin
  if not (biSystemMenu in BorderIcons) or not (BorderStyle in [bsSingle, bsSizeable]) then
    SetRectEmpty(Result)
  else
  begin
    Result.Left := 0;
    Result.Right := GetSystemMetrics(SM_CXSMICON);
    Result.Bottom := GetSystemMetrics(SM_CYSMICON);
    if WindowState = wsMaximized then
      Result.Top := GlassFrame.Top - Result.Bottom - 2
    else
      Result.Top := 6; //is the 'right' value for both normal and large fonts on my machine
    Inc(Result.Bottom, Result.Top);
  end;
end;

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).

procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest);
var
  ClientPos: TPoint;
  IconRect: TRect;
begin
  inherited;
  if not GlassFrame.Enabled then Exit;
  case Message.Result of
    HTCLIENT: {to be dealt with below};
    HTMINBUTTON, HTMAXBUTTON, HTCLOSE:
    begin
      Message.Result := HTCAPTION; //slay ghost btns when running on Win64
      Exit;
    end;
  else Exit;
  end;
  ClientPos := ScreenToClient(Point(Message.XPos, Message.YPos));
  if ClientPos.Y > GlassFrame.Top then Exit;
  if ControlAtPos(ClientPos, True) <> nil then Exit;
  IconRect := GetSysIconRect;
  if (ClientPos.X < IconRect.Right) and ((WindowState = wsMaximized) or
     ((ClientPos.Y >= IconRect.Top) and (ClientPos.Y < IconRect.Bottom))) then
    Message.Result := HTSYSMENU
  else if ClientPos.Y < FWndFrameSize then
    Message.Result := HTTOP
  else
    Message.Result := HTCAPTION;
end;

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:

procedure TForm1.PaintWindow(DC: HDC);
begin 
  with GetClientRect do
    ExcludeClipRect(DC, 0, GlassFrame.Top, Right, Bottom);
  inherited;
end;

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:

procedure TForm1.InvalidateTitleBar;
var
  R: TRect;
begin
  if not HandleAllocated then Exit;
  R.Left := 0;
  R.Top := 0;
  R.Right := Width;
  R.Bottom := GlassFrame.Top;
  InvalidateRect(Handle, @R, False);
end;

procedure TForm1.WMWindowPosChanging(var Message: TWMWindowPosChanging);
const
  SWP_STATECHANGED = $8000; //see TCustomForm.WMWindowPosChanging in Forms.pas
begin
  if GlassFrame.Enabled then
    if (Message.WindowPos.flags and SWP_STATECHANGED) = SWP_STATECHANGED then
      Invalidate
    else
      InvalidateTitleBar;
  inherited;
end;

Black painting solved, the next thing to be fixed is the missing system menu. For this, first add some custom handling for the WM_NCRBUTTONUP message:

procedure TForm1.WMNCRButtonUp(var Message: TWMNCRButtonUp);
begin
  if not GlassFrame.Enabled or not (biSystemMenu in BorderIcons) then
    inherited
  else
    case Message.HitTest of
      HTCAPTION, HTSYSMENU: ShowSystemMenu(Self, Message);
    else inherited;
    end;
end;

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:

procedure ShowSystemMenu(Form: TForm; const Message: TWMNCRButtonUp);
var
  Cmd: WPARAM;
  Menu: HMENU;

  procedure UpdateItem(ID: UINT; Enable: Boolean; MakeDefaultIfEnabled: Boolean = False);
  const
    Flags: array[Boolean] of UINT = (MF_GRAYED, MF_ENABLED);
  begin
    EnableMenuItem(Menu, ID, MF_BYCOMMAND or Flags[Enable]);
    if MakeDefaultIfEnabled and Enable then
      SetMenuDefaultItem(Menu, ID, MF_BYCOMMAND);
  end;
begin
  Menu := GetSystemMenu(Form.Handle, False);
  if Form.BorderStyle in [bsSingle, bsSizeable, bsToolWindow, bsSizeToolWin] then
  begin
    SetMenuDefaultItem(Menu, UINT(-1), 0);
    UpdateItem(SC_RESTORE, Form.WindowState <> wsNormal, True);
    UpdateItem(SC_MOVE, Form.WindowState <> wsMaximized);
    UpdateItem(SC_SIZE, (Form.WindowState <> wsMaximized) and
      (Form.BorderStyle in [bsSizeable, bsSizeToolWin]));
    UpdateItem(SC_MINIMIZE, (biMinimize in Form.BorderIcons) and
      (Form.BorderStyle in [bsSingle, bsSizeable]));
    UpdateItem(SC_MAXIMIZE, (biMaximize in Form.BorderIcons) and
      (Form.BorderStyle in [bsSingle, bsSizeable]) and
      (Form.WindowState <> wsMaximized), True);
  end;
  if Message.HitTest = HTSYSMENU then
    SetMenuDefaultItem(Menu, SC_CLOSE, MF_BYCOMMAND);
  Cmd := WPARAM(TrackPopupMenu(Menu, TPM_RETURNCMD or
    GetSystemMetrics(SM_MENUDROPALIGNMENT), Message.XCursor,
    Message.YCursor, 0, Form.Handle, nil));
  PostMessage(Form.Handle, WM_SYSCOMMAND, Cmd, 0)
end;

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:

procedure TForm1.FormPaint(Sender: TObject);
var
  IconHandle: HICON;
begin
  IconHandle := Icon.Handle;
  if IconHandle = 0 then IconHandle := Application.Icon.Handle;
  with GetSysIconRect do
    DrawIconEx(Canvas.Handle, Left, Top, IconHandle,
      Right - Left, Bottom - Top, 0, 0, DI_NORMAL);
end;

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:

procedure TForm1.FormPaint(Sender: TObject);
var
  IconHandle: HICON;
  R: TRect;
begin
  if ImageList1.Count = 0 then
  begin
    ImageList1.Width := GetSystemMetrics(SM_CXSMICON);
    ImageList1.Height := GetSystemMetrics(SM_CYSMICON);
    {$IF NOT DECLARED(TColorDepth)}
    ImageList1.Handle := ImageList_Create(ImageList1.Width,
      ImageList1.Height, ILC_COLOR32 or ILC_MASK, 1, 1);
    {$IFEND}
    IconHandle := Icon.Handle;
    if IconHandle = 0 then IconHandle := Application.Icon.Handle;
    ImageList_AddIcon(ImageList1.Handle, IconHandle);
  end;
  R := GetSysIconRect;
  ImageList1.Draw(Canvas, R.Left, R.Top, 0);
end;

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:

{$IF not Declared(UnicodeString)}
type
  UnicodeString = WideString;
{$IFEND}

procedure DrawGlassCaption(Form: TForm; const Text: UnicodeString;
  Color: TColor; var R: TRect; HorzAlignment: TAlignment = taLeftJustify;
  VertAlignment: TTextLayout = tlCenter; ShowAccel: Boolean = False); overload;
const
  BasicFormat = DT_SINGLELINE or DT_END_ELLIPSIS;
  HorzFormat: array[TAlignment] of UINT = (DT_LEFT, DT_RIGHT, DT_CENTER);
  VertFormat: array[TTextLayout] of UINT = (DT_TOP, DT_VCENTER, DT_BOTTOM);
  AccelFormat: array[Boolean] of UINT = (DT_NOPREFIX, 0);
var
  DTTOpts: TDTTOpts;            { This routine doesn't use GetThemeSysFont and          }
  Element: TThemedWindow;       { GetThemeSysColor because they just return theme       }
  IsVistaAndMaximized: Boolean; { defaults that will be overridden by the 'traditional' }
  NCM: TNonClientMetrics;       { settings as and when the latter are set by the user.  }
  ThemeData: HTHEME;

  procedure DoTextOut;
  begin
    with ThemeServices.GetElementDetails(Element) do
      DrawThemeTextEx(ThemeData, Form.Canvas.Handle, Part, State, PWideChar(Text),
        Length(Text), BasicFormat or AccelFormat[ShowAccel] or
        HorzFormat[HorzAlignment] or VertFormat[VertAlignment], @R, DTTOpts);
  end;
begin
  if Color = clNone then Exit;
  IsVistaAndMaximized := (Form.WindowState = wsMaximized) and
    (Win32MajorVersion = 6) and (Win32MinorVersion = 0);
  ThemeData := OpenThemeData(0, 'CompositedWindow::Window');
  Assert(ThemeData <> 0, SysErrorMessage(GetLastError));
  try
    NCM.cbSize := SizeOf(NCM);
    if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NCM, 0) then
      if Form.BorderStyle in [bsToolWindow, bsSizeToolWin] then
        Form.Canvas.Font.Handle := CreateFontIndirect(NCM.lfSmCaptionFont)
      else
        Form.Canvas.Font.Handle := CreateFontIndirect(NCM.lfCaptionFont);
    ZeroMemory(@DTTOpts, SizeOf(DTTOpts));
    DTTOpts.dwSize := SizeOf(DTTOpts);
    DTTOpts.dwFlags := DTT_COMPOSITED or DTT_TEXTCOLOR;
    if Color <> clDefault then
      DTTOpts.crText := ColorToRGB(Color)
    else if IsVistaAndMaximized then
      DTTOpts.dwFlags := DTTOpts.dwFlags and not DTT_TEXTCOLOR
    else if Form.Active then
      DTTOpts.crText := GetSysColor(COLOR_CAPTIONTEXT)
    else
      DTTOpts.crText := GetSysColor(COLOR_INACTIVECAPTIONTEXT); 
    if not IsVistaAndMaximized then
    begin
      DTTOpts.dwFlags := DTTOpts.dwFlags or DTT_GLOWSIZE;
      DTTOpts.iGlowSize := 15;
    end;
    if Form.WindowState = wsMaximized then
      if Form.Active then
        Element := twMaxCaptionActive
      else
        Element := twMaxCaptionInactive
    else if Form.BorderStyle in [bsToolWindow, bsSizeToolWin] then
      if Form.Active then
        Element := twSmallCaptionActive
      else
        Element := twSmallCaptionInactive
    else
      if Form.Active then
        Element := twCaptionActive
      else
        Element := twCaptionInactive;
    DoTextOut;
    if IsVistaAndMaximized then DoTextOut;
  finally
    CloseThemeData(ThemeData);
  end;
end;

procedure DrawGlassCaption(Form: TForm; var R: TRect;
  HorzAlignment: TAlignment = taLeftJustify; VertAlignment: TTextLayout = tlCenter;
  ShowAccel: Boolean = False); overload;
begin
  DrawGlassCaption(Form, Form.Caption, clDefault, R,
    HorzAlignment, VertAlignment, ShowAccel);
end;

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:

function GetDwmBorderIconsRect(Form: TForm): TRect;
begin
  if DwmGetWindowAttribute(Form.Handle, DWMWA_CAPTION_BUTTON_BOUNDS, @Result,
    SizeOf(Result)) <> S_OK then SetRectEmpty(Result);
end;

This all done, go back to the OnPaint handler we added earlier, and append to it the following code:

  R.Left := R.Right + FWndFrameSize - 3;
  if WindowState = wsMaximized then
    R.Top := FWndFrameSize
  else
    R.Top := 0;
  R.Right := GetDwmBorderIconsRect(Self).Left - FWndFrameSize - 1;
  R.Bottom := GlassFrame.Top;
  DrawGlassCaption(Self, R);
end;

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:

procedure TForm1.WMActivate(var Message: TWMActivate);
begin
  inherited;
  InvalidateTitleBar;
end;

If you want to be a perfectionist, you can also handle CM_TEXTCHANGED to make sure the custom title bar gets updated when the form’s Caption property is changed:

procedure TForm1.CMTextChanged(var Message: TMessage);
begin
  inherited;
  InvalidateTitleBar;
end;

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.]

About these ads

41 thoughts on “Setting up a custom title bar on Vista/Windows 7

  1. Wow! And I got annoyed when Windows introduced that silly non-informative animated paper that files from folder to folder in the Copy dialog.

  2. Pingback: Setting up a custom title bar – reprise « Delphi Haven

  3. 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?

    Awesome article. And thanks for sharing :)

      • 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.)

  4. Great article!
    I have a question. This solution dont work correctly, if I use Vista with Classic Windows theme.
    How can I get runtime the used theme on Windows Vista?

    Thank you!

  5. Hi –

    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.

  6. 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.

        • I get the ghost buttons also, any ideas why this happens, I have implemented my form exactly as you have shown.

          Delphi 2010 Windows 7

          • Put up the resulting EXE somewhere and I’ll have a look at it. I use D2007 myself, so it may be an issue of certain window styles or extended styles, I don’t know.

  7. Pingback: Delphi Client Area von Formular disablen - Delphi-PRAXiS

    • Thanks. On Vista 32 bit, all I can say at the moment is that I only get the ghost buttons when using Torbin’s method. Can I assume the email address you’ve entered is a working one?

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

  9. Pingback: Anonymous

  10. @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:

    procedure TForm1.CreateWindowHandle(const Params: TCreateParams);
    begin
      inherited;
      if WindowHandle <> 0 then
        SetWindowLong(WindowHandle, GWL_STYLE, $16870000);  
    end;
    

    3. If you don’t want the user to be able to resizeble the form, handle the WM_NCHITTEST message like this:

    procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest);
    begin
      inherited;
      case Message.Result of
        HTLEFT, HTTOP, HTTOPLEFT, HTRIGHT, HTBOTTOM, HTBOTTOMRIGHT:
          Message.Result := HTBORDER;
      end;
    end;
    

    Step 2 would ideally be replaced by a CreateParams override that selectively adds or replaces specific bits, but I’m finding Windows always adds extra bits regardless. The magic number used is what I found a WinForms form using (via WinSpy++) when following the advice of this StackOverflow post:
    http://stackoverflow.com/questions/3594086/how-to-create-a-form-with-a-border-but-no-title-bar-like-volume-control-on-win/3594119#3594119

  11. Pingback: TAppButton - Application Button like Firefox (experimentell) - Delphi-PRAXiS

  12. 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?

  13. 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

    Hola podía decirme si el código del articulo podría enviármelo? He intentado hacerlo en mi Delphi pero no funciona. Si es tan amable de enviármelo para poder comprobar que funciona en mi Delphi se lo agradezco, Gracias. salsavalencia@hotmail.com

    • The code is what it is – I would only be emailing you what I put in CodeCentral. Tell me what you mean by ‘not working’ however, and I might be able to help you.

  14. Ok, you can send mail with the code you have in CodeCentral.
    When you send me if I have problems I will comment.
    Thank you.

    Ok, puedes enviarme el correo con el código que tienes en CodeCentral.
    Cuando me lo envíes si tengo problemas se lo comentare.
    Gracias.

    • Sorry, I don’t think you understand what I said: download the CodeCentral version yourself, and if you want any further help, provide more details than simply ‘it doesn’t work’.

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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 )

Google+ photo

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

Connecting to %s