4
votes

I'm working on a rather large project that uses custom forms and would like to draw the non-client area of these forms myself. I can't use vcl-styles because many of the forms need to have a caption bar and border in a color picked at run time and as far as I know, styles are application wide by design.

What I have accomplished so far is drawing the caption bar and border, drawing on the caption, disabling the minimize, maximize and close buttons and replacing them with my own. I achieved this by intercepting the WM_NCPaint and WM_NCACTIVATE messages and calling my own procedure after the inherited keyword when handling WM_NCACTIVATE and sending a WM_ACTIVATE message without handling the WM_NCPAINT message as such:

SendMessage(Handle, WM_NCACTIVATE, ORD(active), -1);

The reason I did this is because I couldn't get the TMainMenu to paint itself consistently, after stepping through the code it seemed that the WM_NCACTIVATE message being handled correctly paints the main menu. Another approach I tried there was calling UpdateItems on the main menu, but this didn't give any result.

I deactivated the top right buttons by handling the WM_NCHITTEST message as such:

procedure TBasicForm.WMNCHITTEST(var message : TMessage);
begin
  inherited;
  case message.Result of
    HTMINBUTTONE, HTMAXBUTTON, HTCLOSE: message.Result := HTCAPTION;
  end;
end;

I got my own buttons (which I draw in the procedure I call when Handling WM_NCACTIVATE) by handling WM_NCLBUTTONDOWN, this is not a perfect solution but, can easily be improved on. (I trust I do not need to elaborate on this.

So far this sounds pretty good, however.

  • There is quite a lot of flickering when switching focus between forms certain ways.
  • Sometimes the original top right buttons show up, though they don't react to the mouse anymore.
  • When closing a form, The caption bar (and only the caption) bar reverts to its old appearance.

The direct question is, how do I solve these three issues? It might be that I'm going about this entirely the wrong way though, in which case the question is, how can I achieve a custom drawn caption bar and border, preferably without meddling with the functionality of the borders and caption bar too much and having the a main menu be drawn properly?

As I said, it's a rather large project with many forms, so changing things around in the form designer is one of the last things I'd consider doing.

To Reproduce the problems I'm experiencing, create a new form and handle WM_NCHITTEST, WM_NCACTIVATE and WM_NCPAINT as I described earlier.

...
procedure WMNCHITTEST(var message : TMessage); message WM_NCHITTEST;
procedure WMNCACTIVATE(var message : TMessage); message WM_NCACTIVATE;
procedure WMNCPAINT(var message : TMessage); message WM_NCPAINT;
...
implementation
...
procedure TBasicForm.WMNCHITTEST(var message : TMessage);
begin
  inherited;
  case message.Result of
    HTMINBUTTONE, HTMAXBUTTON, HTCLOSE: message.Result := HTCAPTION;
  end;
end;

procedure TBasicForm.WMNCACTIVATE(var message : TMessage);
begin
  inherited;
  Canvas.Brush.Style := bsSolid;
  Canvas.Brush.Color := clRed;

  Canvas.Rectangle(0, 0, Width, GetSystemMetric(SM_CYCAPTION) + GetSystemMetric(SM_CYBORDER));
  Canvas.Rectangle(0, 0, GetSystemMetric(SM_CXBORDER), Height);
  Canvas.Rectangle(Width - GetSystemMetric(SM_CXBORDER), 0, Width, Height);
  Canvas.Rectangle(Width - GetSystemMetric(SM_CXBORDER), Heigth - GetSystemMetric(SM_CYBORDER), Width, Height);
end;

procedure TBasicForm.WMNCPAINT(var message : TMessage);
begin
  SendMessage(Handle, WM_NCACTIVATE, ORD(active), -1);
end;
...

Now, add a second form to the project, make sure both forms are created and switch focus between the two forms repeatedly (also try clicking the second form, then click the custom drawn caption bar of first form), this should result in some flickering and the close, min and max button showing up. closing the form (by pressing alt + f4) should briefly show the original caption bar.

1
In case this question is improperly formatted or lacks information, I apologize in advance and ask you to inform me; I'd really like to do anything to get the best possible answer.overactor
Please show an an SSCCE. Also, it is not helpful to ask for help but also say that you are not prepared to change your design. If the design is broken, so be it. Rejecting VCL styles also seems dubious. It sounds like exactly what you need.David Heffernan
By changing things around in the design, I meant the form designer, since there are so many forms. As for the VCL styles, I am under the assumption that you can not apply different styles to different forms, which I need. I'll Adjust my question and try to include an SSCCE. Thanks for you interest in any case.overactor
You're walking in the wrong direction. Create a borderless form instead and mimic the behavior of a normal form.Peter
@PeterVonča Does this work properly with the main menu, and doesn't that mess up the position of all components on the form? As I said, the project already has tons of forms created, some being multiple steps of inheritance away from this form. Additionally, this has to work parallel to normal forms depending on a boolean value saved in the registry (at least for now).overactor

1 Answers

5
votes

Write a proper class to paint the non client area of a form require a lot of work, you are already handling some of the basic windows messages involved but there a lot more. Based on my experience these are my recommendations.

A. There is quite a lot of flickering when switching focus between forms certain ways.

Q. This issue can have many causes, but the main is use several calls to the draw method on the canvas, you can overcome this using a bitmap buffer (TBitmap) to draw all the title bar to the canvas of the bitmap and finally call the Canvas.Draw just once passing the bitmap.

A. Sometimes the original top right buttons show up, though they don't react to the mouse anymore.

See the answer to the next question.

A. When closing a form, The caption bar (and only the caption) bar reverts to its old appearance.

Q This is because you need to invalidate the NC Area of the form when the form is restored or resized , so you must add support for some additional messages like WM_WINDOWPOSCHANGING, WM_SIZE, WM_MOVE, WM_NCMOUSEMOVE, WM_NCLBUTTONDOWN, WM_NCRBUTTONDOWN and so on

To avoid all this work you can use the VCL Styles, for this you must override the TFormStyleHook class and implement a set of custom style hooks and apply in the forms which you want to custom the title bar using the RegisterStyleHook method like so

TStyleManager.Engine.RegisterStyleHook(TMyForm1, TMyCustomformStyleHook1);
TStyleManager.Engine.RegisterStyleHook(TMyForm2, TMyCustomformStyleHook2);