4

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.

overactor
  • 1,759
  • 2
  • 15
  • 23
  • 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 May 28 '14 at 18:20
  • 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 May 28 '14 at 18:21
  • 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 May 28 '14 at 18:37
  • An SSCCE means code that we can paste and run as is. – David Heffernan May 28 '14 at 19:29
  • I don't have access to my source code (or delphi for that matter) right now, though it seems like very minimal work to reproduce the problem given the code I provided, if it's not sufficient, I suppose I'll leave the question for what it is until I have access to my source code again. – overactor May 28 '14 at 19:37
  • 1
    You're walking in the wrong direction. Create a borderless form instead and mimic the behavior of a normal form. – Peter May 28 '14 at 19:50
  • 1
    @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 May 28 '14 at 19:56
  • Let's wait until you have access to your code. The above cannot possibly work. For one thing, Canvas will not retrieve a handle suitable for NC painting. – Sertac Akyuz May 28 '14 at 20:25
  • This link *may* help: it shows how to custom-draw on the glass of a title bar. http://stackoverflow.com/questions/8056846/how-to-draw-a-custom-caption-bar-like-the-yahoo-messenger-11 – David May 28 '14 at 20:30
  • Also this link shows how to hook the VCL styles for a specific class (say, TForm.) It may be possible to do that, then in the code you write in the hook method to do the painting, do different painting based on which form it is that's being painted. http://stackoverflow.com/questions/12079665/how-to-apply-a-vcl-style-hook-to-a-particular-component-of-a-form – David May 28 '14 at 20:33
  • @SertacAkyuz My mistake, Canvas.Handle := GetWindowDC(Handle); is missing, but you're probably right, I'll get back to this at some time in the near future. DavidM: Thanks a lot, unfortunately, several instances of the same form need different colors. I've read the first link earlier and found it very helpful, I'll definitely go through them in detail again. – overactor May 28 '14 at 20:33
  • @over - Ok. In the meantime, if you don't care the looks of an inactive window, just return false from WM_NCACTIVATE. And remove the inherited call there. And move the painting code to the NCPaint handler. And do not send an activation message from the paint handler. – Sertac Akyuz May 28 '14 at 20:58
  • @SertacAkyuz If I don't call inherited in the NCACTIVATE handler, the main menu doesn't get painted, only the individual menu items show up when I move my mouse over them, I looked for a way to force the main menu to repaint itself, but to no avail. Additionally to that, it also seems like a lot of the functionality goes lost when I remove the inherited call. – overactor May 28 '14 at 21:53
  • @over - You're right, strange but it looks like menu bar drawing has got something to the with the default processing of wm_ncactivate. – Sertac Akyuz May 28 '14 at 22:07
  • @overactor, I've been personally working with border-less forms for a while now and I haven't encountered issues that you mention. There is a lot of functionality that you have to re-implement but it's well worth it in the end. – Peter May 29 '14 at 07:09
  • What I can't imagine very well at the moment is how I would go about programmatically making all forms borderless (to be honest, that one's easy), adding my own border and then adjusting everything on there so it stays in place, without knowing what it is that is on there. Amongst almost a million lines of code, I also don't know if the shift in coordinate system might not mess things up. I also don't know how a main menu reacts to being on a borderless form, since it's in the non client area but I can of course test that. What would be the advantages over a stylehook like RRUZ suggests? – overactor May 29 '14 at 08:56
  • @overactor, there are several cons and pros which I won't get into but the main advantage is that you have complete control and freedom to implement the "none-client" area as you please without restrictions. To answer your question, it's a matter of perspective. Border-less form approach will take longer to implement, especially if you're working on existing application but the end result will be more satisfying and future proof. On the other hand, VCL Styles are convenient and fast to implement but ties you down to the framework which is not future proof. – Peter May 29 '14 at 09:39
  • @Peter - What about the menu? – Sertac Akyuz May 29 '14 at 10:20
  • @SertacAkyuz, can you be more specific, are we talking about the TMainMenu component or what exactly is this "menu"? – Peter May 29 '14 at 10:32
  • @Peter - The question is specific, and OP has asked twice about it to you now which somehow you must have overlooked: a TMainMenu. – Sertac Akyuz May 29 '14 at 11:06
  • @SertacAkyuz, I don't correlate the phrases "menu" or "main menu" to the TMainMenu component so I didn't comment on that. I'm not surprised that TMainMenu doesn't play well with a border-less form, I consider it to be a component of the past. I myself have always used [TActionMainMenuBar](http://docwiki.embarcadero.com/Libraries/XE6/en/Vcl.ActnMenus.TActionMainMenuBar) component and I would recommend the same to the OP. It would solve his "main menu" issue afaik. – Peter May 29 '14 at 11:32
  • @Peter - it's the VCL application of the native api's menu. Action menus may become a thing of the past, but not a TMainMenu. – Sertac Akyuz May 29 '14 at 11:50
  • It is indeed a TMainMenu, I'm sorry if that wasn't clear. I'm not the one who implemented it and considering how many forms there are in the project, switching them all around to another component (if necessary) seems like quite a task, but IF it is the way to go... Does anyone have more info on how TMainMenus work with borderless forms adn what to use in stead if it doesn't work well? – overactor May 29 '14 at 12:02
  • @SertacAkyuz, afaik the only place where TMainMenu is a VCL wrapper for native API implementation is on the Win platform. IMO TMainMenu is a thing of the past for Delphi developers, especially on Windows platform where it offers close to 0 customization. There is absolutely no reason not to switch to action menus. – Peter May 29 '14 at 12:27
  • @Peter - 1- 'Windows' is the platform that this question is about, WM_NCPAINT will not be sent in any other. 2- Normally I would argue against your suggestion about using a completely proprietary menu system against the native one. But for this particular case, it is evident that native look and behavior is not something important. – Sertac Akyuz May 29 '14 at 17:50
  • Overactor, have you seen [this](http://delphihaven.wordpress.com/2010/04/19/setting-up-a-custom-titlebar/)? This blog post from @ChrisRolliston seems to handle all the difficulties you are dealing with... – whosrdaddy Jun 03 '14 at 18:34
  • @whosrdaddy, I have seen it and it was a great help, but since I'm doing things slightly differently than he is, not everything can be easily applied as it is. Shifting the client area is difficult in my situation, though i'm starting to think I should have just gone with that anyway. Thanks for the interest in any case. – overactor Jun 03 '14 at 18:39

1 Answers1

5

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);
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • For a sample of customization of the background and title bar ofa form using the VCL Styles you can read my blog post [Vcl Styles – Adding background images and colors to Delphi forms](http://theroadtodelphi.wordpress.com/2012/03/26/vcl-styles-adding-background-images-and-colors-to-delphi-forms/) – RRUZ May 28 '14 at 21:34
  • It seems likely that this will work, one question though, could I use the same stylehook for two different forms and have each form have a different color? If so, how would I go about this and if not, how do I go about reusing code when creating different stylehooks that are essentially the same? Do I make a stylehook and the override it X times? – overactor May 28 '14 at 22:12
  • 1
    You can create only one style hook and read the colors to use from some property in the form to hook or create an internal list with the form classes and the colors to use for each form depending of his type. – RRUZ May 28 '14 at 22:28
  • That should absolutely do it then, if no better answer shows up (which I highly doubt) I'll accept this answer as soon as I've tried it. Thanks a lot. – overactor May 28 '14 at 22:37
  • #RRUZ I'm trying this out but am running into some difficulties. I created my own stylehook that inherits from TFormStyle hook, declared a PaintNC procedure and registered it for TBasicForm. This procedure isn't getting called though, whether I'm use the default windows style or a custom style. ANother problem is: I don't really want to use a custom vcl style, is there a way to use this stylehook without applying a stayle to any other control? Will I have to disable the StyleElements for every control in my application? – overactor May 30 '14 at 07:29
  • one thing I can think of is Creating an empty class inheriting from TStyleHook and registering that for TControl, is that in any way plausible? – overactor May 30 '14 at 07:33
  • If you don't want use the VCL Styles, you can't use the style hooks. So must write your own class to handle the NC draw. Also you can use a detour to disable the styles in the controls, but only will work for TWinControls. – RRUZ May 30 '14 at 15:46
  • It seems a bit backwards to enable VCL Styles only to disable them everywhere possible. What would that detour be? Should I go through all components on each form, use GetPropInfo to see if they have a StyleElements property and set it to an empty set if they do? – overactor May 30 '14 at 16:11
  • I was under the impression, which you want customize the Title bar of the forms with the VCL Styles enabled. If that is not the case then you must create your own class to paint the NC Area, because there is not point in use the VCL Styles just for this task. – RRUZ May 30 '14 at 17:28
  • Fair point, thanks for your answer though, it might not have been a perfect fit for my specific problem, but it's still useful. – overactor May 30 '14 at 17:46