2

I use Delphi7, PageControl with owner-draw. I can't get so plain and nice look of tabs, as I see on not-owner-drawn PageControls. What's bad: when using owner-draw, I can't draw on "entire" tab header area, small 1-2px frame around tab header is painted by OS.

1) Delphi not owner-draw, look is OK too (XPMan used):

delphi sys

2) Delphi owner-draw, you see not entire tab header can be colored (XPMan used):

delphi owner draw

I draw current tab with blue and others with white, here. Only example. Code:

procedure TForm1.PageControl1DrawTab(Control: TCustomTabControl;
  TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
  c: TCanvas;
begin
  c:= (Control as TPageControl).Canvas;
  if Active then
    c.Brush.Color:= clBlue
  else
    c.Brush.Color:= clWhite;
  c.FillRect(Rect);    
end;

2b) Delphi owner-draw in real app (XPMan used):

delphi real app

Why do i need to use owner-draw? Simple. To draw X button on tab headers, to paint upper-line with custom color, to paint icons from imagelists.

I'm looking for a way to paint ENTIRE rect of tab headers, not decreased rect which is given to PageControl owner-draw events. I tried to increase the rect given by owner-draw events, but this doesn't help, OS repaints this thin 1-2px frame around tab headers anyway.

Prog1020
  • 4,530
  • 8
  • 31
  • 65
  • 6
    You could start by updating your version of Delphi to something that wasn't designed for Windows 95. Also, if you're owner-drawing the tabs yourself, and don't include the code that's drawing them, we can't possibly suggest how to make it draw better. – Ken White Aug 16 '13 at 22:05
  • Ken is right: update to a decent version of Delphi. – Uwe Raabe Aug 16 '13 at 22:17
  • 1
    The tab control in the first picture looks very much like a regular tab control, there's no indication that it is owner drawn. Include "xpman" to uses and set "OwnerDraw" to false and your tab control should look exactly like it (on W7 and possibly Vista). – Sertac Akyuz Aug 16 '13 at 23:41
  • However, the question remains valid for an owner drawn tab control, the system draws 3D borders after the wm_drawitem is handled. The code is not so important, set the control's canvas' brush color to anything other than clbtnface and fill the entire rect passed in to the DrawTab event handler. – Sertac Akyuz Aug 16 '13 at 23:45
  • Cheaper than a new version of Delphi is using an alternative component, such as JVCL's TJvPageControl or DevExpress's TcxPageControl, sold as part of their VCL product line. – Gogowitsch Aug 16 '13 at 23:51
  • 3
    Can someone explain how will a new Delphi version help? Is it "styles" what's being meant? Can a programmer define his/her own style? – Sertac Akyuz Aug 16 '13 at 23:57
  • @gogowitsch - I don't think JVCL's control would help, it is a TPageControl descendant. I don't know the Tcx.. one. – Sertac Akyuz Aug 16 '13 at 23:57
  • 1
    I've used a straight-up `TPageControl` in Delphi 7. I get the lighter, highlighted tab as a standard feature without owner draw, but I think (if I recall - it's been awhile since I did it) I created a `WindowsXP.Manifest` file to use the correct Windows resources. – lurker Aug 17 '13 at 01:32
  • @Sertac: The VCL has had many code changes since D7 that improves support for theming and more recent OS versions (as minimal evidence, I offer the fact that D7 still requires `XPMan` be added to the uses clause (or that component to be added to a form) to enable basic themed drawing, instead of just providing it automatically by default). – Ken White Aug 17 '13 at 01:48
  • @Sertac: The `Tcx` one that gogowitsch is referring to is part of the $$$ Developers Express package (which is as much, if not more, than the comparable Delphi upgrade). The code here is important, BTW, because you can copy the Rect that's passed in the DrawTab event and using `InflateRect` on that copy, and then using the new Rect to draw the tab without calling `inherited`. – Ken White Aug 17 '13 at 02:30
  • 2
    @Ken - I think you mean "vcl styles" with 'support for theming'. I don't know if it allows custom theming, if it doesn't, it won't help to the OP. Also, built-in support for OS theming is irrelevant for this question, that's not what the OP wants. As for the code, there's no way you don't call *inherited* in an event handler, DrawTab (OnDrawTab to be precise) is not a message handler. In any case, unfortunately, even if you completely overtake `WM|CN_DRAWITEM`, and not call the `inherited`, the 3D borders are still drawn by the OS - obviously later in the paint cycle. – Sertac Akyuz Aug 17 '13 at 02:57
  • 1
    @Sertac: No, not VCL styles (which didn't appear until XE) - Windows Themes, which are totally different (and are natively supported starting with D2007). :-) The OS theme support is what the OP is fighting here, because D7 doesn't support it well (although it's still a struggle to override it). As far as inherited, you're right. I was thinking of DefaultDrawColumnCell in the `Grids` code, not OnDrawTab in PageControl. – Ken White Aug 17 '13 at 03:24
  • Vcl styles would do the trick here. – Peter Aug 17 '13 at 06:05
  • @Ken You don't need a new version of Delphi to get **Windows** do draw themed controls. You just need a comctl32 v6 manifest and Mike Lischke's XP theme manager. In D7 there's a simple component to add all of that, whose name I cannot recall but it's mentioned above. I recently switched from D6 to modern Delphi and my app's appearance did not change. Because Windows painted it, not the VCL. – David Heffernan Aug 17 '13 at 06:08
  • 1
    @David: I know that; I referred to D7's XPMan, which *is* Mike's XP theme manager. :-) Your app's appearance didn't change because the IDE doesn't add theme support to pre-existing projects when they're opened in later versions that support it. (You can test that - create a project in D6, save it, and then open it in D2007 or later, and look at the project options, then create a new project in the later version and compare - theme support is enabled in the second, but not in the first.) – Ken White Aug 17 '13 at 06:13
  • @Ken OK, I was confused then by the recommendation to upgrade Delphi. So far as I know, Delphi 7 produces perfectly fine themed applications. – David Heffernan Aug 17 '13 at 06:17
  • 1
    @David: Not "perfectly fine", if you don't include `XPMan` (and there are some improvements in the theme support as well in D2007 and later). D7 shoehorned the theme support (with Mike's code) into the VCL; D2007 (not sure about 2006) actually incorporated it. – Ken White Aug 17 '13 at 06:23
  • @Ken Clearly you have to use the XPMan unit. Nobody is suggesting otherwise. It seems to be that using that unit is simpler than upgrading. Not least because upgrading involves Unicode migration. A decision to upgrade should not be driven by themed painting. When I wanted themed painting, my codebase was D6. But I did not want to upgrade, so I just put in Mike's code and hey presto, all was good. – David Heffernan Aug 17 '13 at 06:37
  • 1
    @David: I missed the part where I said anything about a Unicode version. "More recent" means "later than Delphi 7", which includes D2007. Mike's code was amazing when D6 was current, and it was great it was (sort of) added to D7. The planet changes, and so does software development. :-) Actual VCL support for theming (and improvements related to that support) trumps Mike's tremendous efforts, as do the other changes in the RTL/VCL to support OS versions after Win95. Asking for .Net appearance in D6 apps is a little unfair - was it around then? – Ken White Aug 17 '13 at 06:49
  • 1
    @Ken It's not really a .net appearance. After all, .net is a broad framework with many visual toolsets. The screenshot in the question is just a Vista+ themed Windows page control. They are rendered perfectly well with, for example D6+Mike's theme manager. As I said, when I moved from D6 to XE2, my app's visuals did not change. Not one pixel. So, good modern native visuals in D6 or D7 are easy to achieve. Because the painting is done by Windows and the Delphi version doesn't matter. That's the key point to understand. – David Heffernan Aug 17 '13 at 07:05
  • @Ken And FWIW, D7 was released after both .net and XP. – David Heffernan Aug 17 '13 at 07:08
  • Maybe I got the question wrong, but isn't here asked how to owner draw the whole tab planes ? It sounds to me that OP doesn't want the system drawing but draw tabs in the OS independent style (just in the Windows 8 style as shown on the first screenshot). – TLama Aug 17 '13 at 08:05
  • 2
    @TLama - That's also what I understood. I think the former picture is just some misconception on part of OP. But Alex has got to participate to clear up, either accept the answer or try to explain better. – Sertac Akyuz Aug 17 '13 at 08:54
  • @Ken, I added code part, and screenshots to post. What I want is to disable 3D border to be drawn around my tab headers. – Prog1020 Aug 17 '13 at 12:27
  • @David: Of course it was released after XP (thus the addition of XPMan). ;-) I appreciate the "key point" as well, but I started programming for Windows with 3.1, MS C (pre-Visual C 1.0) and Petzold's book; I've got how it works. :-) Not 100% of that drawing is done by Windows, though; if it were, there wouldn't be near as much code in TCustomTabControl and TPageControl, and there would be a lot less use of TCanvas for painting. – Ken White Aug 17 '13 at 15:12

2 Answers2

8

The tabs of an owner drawn native "tab control" (TPageControl in VCL, although its ascendant is appropriately named TCustomTabControl - it is anyone's guess why the creative naming..), is expected to be painted by its parent control while processing WM_DRAWITEM messages, as documented here.

The VCL takes the burden from the parent by mutating the message to a CN_DRAWITEM message and sending it to the control itself. In this process the VCL has no further intervention. It just calls the OnDrawTab message handler if it is assigned by user code, passing appropriate parameters.

So, it's not the VCL that draws the borders around tabs, but the OS itself. Also, evidently, it doesn't do this during processing of WM_DRAWITEM messages but later in the painting process. You can verify this by putting an empty WM_DRAWITEM handler on the parent of a page control. Result is, whatever we paint in the event handler, it will later get borders by the OS.

What we might try is to try to prevent what the OS draws take effect, we have the device context (as Canvas.Handle) after all. Unfortunately this route also is a dead end because the VCL, after the event handler returns, restores the device context's state.

The only way, then, we have is to completely abandon handling an OnDrawTab event, and acting upon CN_DRAWITEM message. Below sample code use an interposer class, but you can subclass the control any way you like. Make sure that OwnerDrawn is set.

type
  TPageControl = class(comctrls.TPageControl)
  protected
    procedure CNDrawitem(var Message: TWMDrawItem); message CN_DRAWITEM;
  end;

  TForm1 = class(TForm)
    ..

..

procedure TPageControl.CNDrawitem(var Message: TWMDrawItem);
var
  Color: TColor;
  Rect: TRect;
  Rgn: HRGN;
begin
  Color := 0;  
  // draw in different colors so we see where we've drawn
  case Message.DrawItemStruct.itemID of
    0: Color := $D0C0BF;
    1: Color := $D0C0DF;
    2: Color := $D0C0FF;
  end;
  SetDCBrushColor(Message.DrawItemStruct.hDC, Color);

  // we don't want to get clipped in the passed rectangle
  SelectClipRgn(Message.DrawItemStruct.hDC, 0);

  // magic numbers corresponding to where the OS draw the borders
  Rect := Message.DrawItemStruct.rcItem;
  if Bool(Message.DrawItemStruct.itemState and ODS_SELECTED) then begin
    Inc(Rect.Left, 2);
//    Inc(Rect.Top, 1);
    Dec(Rect.Right, 2);
    Dec(Rect.Bottom, 3);
  end else begin
    Dec(Rect.Left, 2);
    Dec(Rect.Top, 2);
    Inc(Rect.Right, 2);
    Inc(Rect.Bottom);
  end;
  FillRect(Message.DrawItemStruct.hDC, Rect,
      GetStockObject(DC_BRUSH));

  // just some indication for the active tab
  SetROP2(Message.DrawItemStruct.hDC, R2_NOTXORPEN);
  if Bool(Message.DrawItemStruct.itemState and ODS_SELECTED) then
    Ellipse(Message.DrawItemStruct.hDC, Rect.Left + 4, Rect.Top + 4,
      Rect.Left + 12, Rect.Top + 12);

  // we want to clip the DC so that the borders to be drawn are out of region
  Rgn := CreateRectRgn(0, 0, 0, 0);
  SelectClipRgn(Message.DrawItemStruct.hDC, Rgn);
  DeleteObject(Rgn);

  Message.Result := 1;
  inherited;
end;


Here is how the above looks here:
enter image description here

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • 2
    Nice! Although, the question is then, if such hack-drawing makes even sense. You can just hide the tabs and for page switching use e.g. images placed on top of the page control. – TLama Aug 17 '13 at 14:54
  • @TLama - The clipping code could be put around the inherited handler, maybe it would look less hack-ish. In any case, indeed! – Sertac Akyuz Aug 17 '13 at 15:06
1

From what I can tell, you are simply looking to have themed painting of your application. In Delphi 7, all you need to do to achieve that is to add an application manifest that specifies the use of comctl32 version 6. The simple way to do so is to add a TXPManifest component to one of your forms or data modules, or just to reference the XPMan unit in your project.

Since you want the system to paint your page control, you must not do any owner drawing.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490