Is there a way to make TRadioButton be Transparent? - delphi

I'm using delphi 2010

I agree with Andreas and Serg in that the control is transparent when themes are enabled.
I, once, had tried to make the CheckBox transparent for when runtime themes are not enabled in project options, or a classic theme is selected with the OS; the result was not perfect. The below is the same code applied to the RadioButton.
Problems easily noticable are, as you would guess from the code, it's a bit flickery and it is not transparent when DoubleBuffered. A problem not easily noticable can (sometimes) be duplicated by bringing a different window in front of the form containing the controls, and then slowly moving it aside, sometimes this leaves some artifacts.
Well, anyway, here it is;
type
TMyRadioButton = class(TRadioButton)
private
procedure CnCtlColorStatic(var Msg: TWMCtlColorStatic); message CN_CTLCOLORSTATIC;
procedure WmEraseBkgnd(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WmPaint(var Msg: TWMNCPaint); message WM_PAINT;
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
uses
themes;
procedure TMyRadioButton.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
end;
procedure TMyRadioButton.WmPaint(var Msg: TWMNCPaint);
begin
if not (ThemeServices.ThemesEnabled or DoubleBuffered) then
InvalidateRect(Handle, nil, True);
inherited;
end;
procedure TMyRadioButton.WmEraseBkgnd(var Msg: TWMEraseBkgnd);
var
R: TRect;
begin
if not (ThemeServices.ThemesEnabled or DoubleBuffered)
and (Parent <> nil) then begin
R := Rect(Left, Top, Left + Width, Height + Top);
InvalidateRect(Parent.Handle, #R, True);
UpdateWindow(Parent.Handle);
Msg.Result := 1;
end else
inherited;
end;
procedure TMyRadioButton.CnCtlColorStatic(var Msg: TWMCtlColorStatic);
begin
if not (ThemeServices.ThemesEnabled or DoubleBuffered) then begin
SetBKMode(Msg.ChildDC, TRANSPARENT);
Msg.Result := GetStockObject(NULL_BRUSH);
end else
inherited;
end;

Quote Remy Lebeau (TeamB):
TLabel is a TGraphicControl
descendant, and thus has to do all of
its own drawing manually, so it can
implement transparency as needed.
TCheckBox and TRadioButton, on the
other hand, are TWinControl
descendants that wrap standard Win32
API controls, and thus are subject to
whatever capabilities the OS supports
for them (transparency is not one of
them).
https://forums.codegear.com/thread.jspa?threadID=24027&tstart=375
You either need to do some heavy overriding, or else you will need to use a third party component...

A simple trick: make the button color white, shrink it to the minimum size, only the button; and put a transparent label behind it.
Otherwise, to make a button really transparent you need to owner draw it. You may find some examples in the web.
I found some information on responding to the WM_CTLCOLOR message. But I gave a quick try but couldn't quite get it to work.

I experimented with the standard VCL TRadioButton control in Delphi 2009 (I suppose Delphi 2010 is the same).
If you compile the project with runtime themes enabled (Project->Options->Application->Enable Runtime Themes), the TRadioButton control is transparent and its 'Color' property ignored. If the runtime themes disabled, the TRadioButton control is not transparent and its background is defined by its 'Color' property.
So I assume that the standard VCL TRadioButton (and the underlying windows control) is made transparent by the Windows theme, not by the control itself. You can switch off the theme support on application level, and in that case you get a non-transparent radio button. If you need a transparent radiobutton with runtime themes disabled, use 3rd party custom radiobutton (TCustomControl descendant, not a standard Windows radiobutton wrapper)

The easiest way is to buy a component set like Raize Components which will do this for you and lots more besides. Raize in particular allows you to customize lots of aspects of the UI.

http://www.torry.net/quicksearchd.php?String=transparent+radiobutton&Title=No might help. None of those are D2010 or D2009, but I believe porting would be possible.

Related

MDI-Form ignores StyleElements seClient

i have a problem with the VCL-Styles and MDI-Form. I want to use the VCL Styles, but i also want to draw the background (image) of my MainForm (MDI) by myself. This worked fine without VCL Styles, but when a Style is active the background image of the MainForm isn't shown.
I checked out the StyleElements for the MainForm, but exclude the seClient is ignoerd and the background image isn't shown.
When i exclude the seClient and seBoarder the image is shown again. Obviously the Form Boarder lost the Style, which is also not that what i want.
The image is drawn at the Canvas in the ClientWndProc by the messages WM_ERASEBKGND, WM_VSCROLL and WM_HSCROLL. With the Styles, it looks like this events didn't raise. Is there any way the get the image at the form background with VCL Styles active?
The main point to realize here is that form styled fsMDIForm is a very special TWinControl that manages two window handles instead of one - TWinControl.Handle and TForm.ClientHandle. While the first handle is the form window itself the second is MDI client window (container-like for MDI child windows inside MDI parent).
TFormStyleHook hooks both window procedures and introduces new method TFormStyleHook.MDIClientWndProc, which processes messages sent to MDI client. This method luckily virtual. It does some pre-processing of messages and then calls the original hooked procedure. The sad part is that it prevents calling the old procedure for WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCPAINT and WM_ERASEBKGND. Even worse is that on WM_ERASEBKGND it paints the client area background directly using StyleServices.
Thanks to the above the subclassing of TFormStyleHook for MDI forms a PITA. I see multiple design flaws here:
Missing virtual TFormStyleHook.PaintMDIClientBackground similar to TFormStyleHook.PaintBackground.
No way to control/access over the original MDI client proc without hacking (hidden in private field FMDIPrevClientProc).
Disability to control styling of MDI client window via TForm.StyleElements (as noted by OP).
So what is the workaround? The easiest I can see is creating a custom style hook:
type
TMainFormStyleHook = class(TFormStyleHook)
public
procedure MDIClientWndProc(var Message: TMessage); override;
end;
{ TMainFormStyleHook }
procedure TMainFormStyleHook.MDIClientWndProc(var Message: TMessage);
begin
if Message.Msg = WM_ERASEBKGND then
begin
{ TODO: Paint background to TWMEraseBkgnd(Message).DC }
Message.Result := 1;
end
else
inherited;
end;
and applying it to your MDI parent:
type
TMainForm = class(TForm)
private
class constructor Create;
class destructor Destroy;
{ ... }
end;
{ TMainForm }
class constructor TMainForm.Create;
begin
TCustomStyleEngine.RegisterStyleHook(TMainForm, TMainFormStyleHook);
end;
class destructor TMainForm.Destroy;
begin
TCustomStyleEngine.UnRegisterStyleHook(TMainForm, TMainFormStyleHook);
end;
Note that you still need to keep painting background in MDI parent form in case the VCL styles are disabled, so it's worth creating method TMainForm.PaintMDICLientBackground(DC: HDC) and call it from both places.
I would argue that this is a bug in VCL. How about you guys?

How to correctly respond to focus messages in a custom control?

I need to create my own panel which will be derived from TCustomPanel - Here I will be doing my own custom painting on the panel such as drawing a header at the top.
One of the requirements I need is that I need to be able to draw two different colors depending on whether my panel has focus or not, I also need a way of knowing whether or not a child control inside my panel has focus too - of course though there is no knowing of what the child contents could be.
So for example, if the panel (or any child controls inside the panel) has focus, then the color could be clSkyBlue. If the panel (or none of the child controls inside the panel) does not have focus, then the color could be clWhite.
Here is a quick layout of the custom panel: (to keep it simple for the example there is no header drawing, just basic color changing)
type
TMyPanel = class(TCustomPanel)
private
//
protected
// procedure Paint; override;
procedure WMMouseDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.ParentBackground := False;
Self.ParentColor := False;
end;
destructor TMyPanel.Destroy;
begin
inherited Destroy;
end;
procedure TMyPanel.WMMouseDown(var Message: TWMLButtonDown);
begin
Self.SetFocus;
end;
procedure TMyPanel.WMKillFocus(var Message: TWMKillFocus);
begin
Self.Color := clWhite;
Invalidate;
end;
procedure TMyPanel.WMSetFocus(var Message: TWMSetFocus);
begin
Self.Color := clSkyBlue;
Invalidate;
end;
The first problem I ran into is making the panel focusable, I'm sure there will be a proper solution that I have overlooked but in this case I used a dirty trick by intercepting the WM_LBUTTONDOWN message and making the panel focused. This of course also denies the other requirement I mentioned before where child controls also decide what color the panel should be depending on whether or not the panel or it's child controls are focused.
This is what happens when I click on my panel:
This is what happens when clicking on the child control:
As you can see the panel turned white, but I need it to turn to clSkyBlue to indicate that the panel or it's child contents has focus, which should look like:
So, what is the solution to correctly identify whether or not my custom control or it's child controls has the focus or not?
I had thought about iterating through each child control inside the panel to determine if it had focus or not but I am not sure how I would fire such an event in the first place as clicking directly on a child control would surely ignore any messages that the custom panel is waiting to intercept.
All TWinControl descendants are already focusable and so is your TCustomPanel descendant, too. There's no need to do any additional work in this regard.
What is not done automatically for you (and I think you want to do) is to show the focus state of your component visually. One possible way to do this is by handling CM_ENTER and CM_EXIT messages:
TMyPanel = class(TCustomPanel)
private
procedure FocusChanged(Value: Boolean);
protected
procedure CMEnter(var Message: TCMEnter); message CM_ENTER;
procedure CMExit(var Message: TCMExit); message CM_EXIT;
end;
procedure TMyPanel.CMEnter(var Message: TCMEnter);
begin
FocusChanged(True);
end;
procedure TMyPanel.CMExit(var Message: TCMExit);
begin
FocusChanged(False);
end;
procedure TMyPanel.FocusChanged(Value: Boolean);
const
Colors: array[Boolean] of TColor = (clWhite, clSkyBlue);
begin
Color := Colors[Value];
end;
An easy way is to create your own Edit instead of you TPanel.
Your derived Edit may control the OnEnter(); and OnExit() events and chance its Parent colour.
As a can see you know how to intercept events so it will be an easier approach.

How to put controls into a Design State Mode just like the Form Designer does?

This one has been puzzling me for some time now and maybe the answer is an easy one, or perhaps it involves much more VCL hacking or magic to accomplish what I am looking for but either way I am at a loss as to how to solve my problem.
If you look at the Delphi Form Designer you will see that none of the controls animate when the mouse moves over them, they also cannot receive focus or input (eg you cannot type into a TEdit, click a TCheckBox or move a TScrollBar etc), only at runtime do the controls behave normally and respond to user interaction.
I want to know how can I implement this type of behavior to any control at runtime, eg set controls into like a Designer State Mode? However, controls should also still respond to Mouse Events such as OnMouseDown, OnMouseMove, OnMouseUp etc so they can be moved and sized if needed for example.
This is the closest that I managed:
procedure SetControlState(Control: TWinControl; Active: Boolean);
begin
SendMessage(Control.Handle, WM_SETREDRAW, Ord(Active), 0);
InvalidateRect(Control.Handle, nil, True);
end;
Which could be called simply like so:
procedure TForm1.chkActiveClick(Sender: TObject);
begin
SetControlState(Button1, chkActive.Checked);
SetControlState(Button2, chkActive.Checked);
SetControlState(Edit1, chkActive.Checked);
end;
Or for example, all controls on the form:
procedure TForm1.chkActiveClick(Sender: TObject);
var
I: Integer;
Ctrl: TWinControl;
begin
for I := 0 to Form1.ControlCount -1 do
begin
if Form1.Controls[I] is TWinControl then
begin
Ctrl := TWinControl(Form1.Controls[I]);
if (Ctrl <> nil) and not (Ctrl = chkActive) then
begin
SetControlState(Ctrl, chkActive.Checked);
end;
end;
end;
end;
Two problems I have noticed with the above is that whilst the controls do appear to become Design State like, some controls such as TButton still have the animation effect painted on them. The other issue is when pressing the left Alt key when the controls are Design State like causes them to disappear.
So my question is, how do I put controls into a Design State mode at runtime just like the Delphi Form Designer does, where those controls do not animate (based on Windows Theme) and cannot receive focus or input?
To make that bit clearer, look at this sample image based off the above code sample where the controls are no longer active, but the TButton's animation paint is still active:
But should actually be:
From the two images above, only the TCheckBox control can be interacted with.
Is there a procedure hidden away somewhere that can change the state of a control? Or perhaps a more suitable approach to achieving this? The code I managed to get so far just presents more problems.
Setting the controls to Enabled := False is not an answer I am looking for either, yes the behavior is kind of the same but of course the controls paint differently to show they are disabled which is not what I am looking for.
What you are looking for is not a feature of the controls themselves, but rather is an implementation of the Form Designer itself. At design-time, user input is intercepted before it can be processed by any given control. The VCL defines a CM_DESIGNHITTEST message to allow each control to specify whether it wants to receive user input at design-time (for example to allow visual resizing of list/grid column headers). It is an opt-in feature.
What you can do, though, is put the desired controls onto a borderless TPanel, and then simply enable/disable the TPanel itself as needed. That will effectively enable/disable all user input and animations for its child controls. Also, when the TPanel is disabled, the child controls will not render themselves as looking disabled.
Remy Lebeau's answer on putting controls into a container such as a TPanel, and then setting the panel to Enabled := False does put the controls into the state I was looking for. I also discovered that overriding the controls WM_HITTEST put the controls into the same state, eg they don't receive focus and cannot be interacted with. The problem with those two is that the controls still need to be able to respond to MouseDown, MouseMove and MouseUp events etc but they no longer cannot.
Remy also suggested writing a class and implement Vcl.Forms.IDesignerHook, something I have not attempted yet as maybe it requires too much work for what I need.
Anyway, after lots of playing around I found another alternative way, it involves the use of PaintTo to draw the control onto a canvas. The steps I did are as follows:
Create a custom TPanel with an exposed Canvas
At FormCreate create and align the custom panel to client
Add controls to the form at runtime (bringing the custom panel to the front)
Call the controls PaintTo method onto the custom panels Canvas
What this is essentially doing is creating the components and using the Form as the parent with our custom panel sitting on top. The controls are then painted onto the panels canvas which makes it appear as if the control is on the panel, when actually it sits underneath on the form undisturbed.
Because the controls are underneath the panel, in order for them to respond to events such as MouseDown, MouseMove and MouseUp etc I overrided the WM_NCHitTest in the panel and set the result to HTTRANSPARENT.
In code it would look something like this:
Custom panel:
type
TMyPanel = class(TPanel)
protected
procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHitTest;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Canvas;
end;
{ TMyPanel }
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Align := alClient;
BorderStyle := bsNone;
Caption := '';
end;
destructor TMyPanel.Destroy;
begin
inherited Destroy;
end;
procedure TMyPanel.WMNCHitTest(var Message: TWMNCHitTest);
begin
Message.Result := HTTRANSPARENT;
end;
Form:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FMyPanel: TMyPanel;
procedure ControlMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
public
{ Public declarations }
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
FMyPanel := TMyPanel.Create(nil);
FMyPanel.Parent := Form1;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FMyPanel.Free;
end;
procedure TForm1.ControlMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Sender is TWinControl then
begin
ShowMessage('You clicked: ' + TWinControl(Sender).Name);
end;
end;
Example of adding a TButton to the form:
procedure TForm1.Button1Click(Sender: TObject);
var
Button: TButton;
begin
Button := TButton.Create(Form1);
Button.Parent := Form1;
FMyPanel.BringToFront;
with Button do
begin
Caption := 'Button';
Left := 25;
Name := 'Button';
Top := 15;
OnMouseDown := ControlMouseDown;
PaintTo(FMyPanel.Canvas, Left, Top);
Invalidate;
end;
end;
If you try running the above, you will see that the TButton we created does not animate or receive focus, but it can respond to MouseDown events we attached in the code above, that is because we are not actually looking at the control, instead we are viewing a graphical copy of the control.
I'm not sure if this is what you're after or not, but Greatis has a Form Designer component. See: http://www.greatis.com/delphicb/formdes/

TLinkLabel background on a TPageControl

I am trying to use a TLinkLabel on a TPageControl, and I can't find a way to make it use it's parent's background.
// Image removed because the website doesn't exist any more
// and I can't find it anywhere... Sorry.
As you can see, the parent tab sheet's lovely gradient is not preserved behind the link text.
I would like the functionality of having multiple links in a flowing block of text (the functionality that TLinkLabel provides) and have the background of the parent showing behind the text.
TLinkLabel does not have a ParentBackground property. I have tried creating a derived class that adds csParentBackground to the control style, to no avail:
TMyLinkLabel = class (TLinkLabel)
public
constructor Create(AOwner: TComponent); override;
end;
...
constructor TMyLinkLabel.Create(AOwner: TComponent);
begin
inherited;
ControlStyle := ControlStyle - [csOpaque] + [csParentBackground]
end;
Anyone have any ideas?
Nat, you are nearly there with your changes to the ControlStyle of the TLinkLabel. What you have to do in addition is to make sure that the parent of the standard Windows static control (that's what the TLinkLabel is) handles the WM_CTLCOLORSTATIC message correctly.
The VCL has a nice redirection mechanism to let controls handle messages that are sent as notifications to their parent windows for themselves. Making use of this a completely self-contained transparent link label can be created:
type
TTransparentLinkLabel = class(TLinkLabel)
private
procedure CNCtlColorStatic(var AMsg: TWMCtlColorStatic);
message CN_CTLCOLORSTATIC;
public
constructor Create(AOwner: TComponent); override;
end;
constructor TTransparentLinkLabel.Create(AOwner: TComponent);
begin
inherited;
ControlStyle := ControlStyle - [csOpaque] + [csParentBackground];
end;
procedure TTransparentLinkLabel.CNCtlColorStatic(var AMsg: TWMCtlColorStatic);
begin
SetBkMode(AMsg.ChildDC, TRANSPARENT);
AMsg.Result := GetStockObject(NULL_BRUSH);
end;
Normally I hate it when people offer a third-party component as an answer, but I'll mention the TMS THTMLabel as an alternative for what you want to do. It has the Transparent property of the TLabel, and allows you to use HTML as the caption, and so you can do multiple links as per your example.
The csParentBackground and csOpaque styles both require cooperation from other parts of the control's code. Merely setting them wouldn't have much effect; if it did, then the control would probably have a public Transparent property already.
You can look at TCustomLabel.Paint to see how it respects the csOpaque style. It checks for that style by reading its Transparent property before it paints its background:
if not Transparent then
begin
Canvas.Brush.Color := Self.Color;
Canvas.Brush.Style := bsSolid;
FillRect(ClientRect);
end;
The csParentBackground style has no effect on TCustomLabel because that style only affects windowed controls; TCustomLabel descends from TGraphicControl, not TWinControl.
I don't have TLinkLabel, so I can't look at its source code to find out what it would need to change. If it's a TGraphicControl descendant, then it would need to include code like I showed above from TCustomLabel. If it descends from TWinControl, then I'd adapt code from TCustomStaticText instead. That's a little more complicated; it calls DrawParentBackground in response to the cn_CtlColorStatic notification message. It also doesn't paint itself in Delphi code. The control is a wrapper for the Win32 "static" control type.
TLinkLabel evidently paints its background unconditionally. To fix this, you'll need to override the Paint method. Removing functionality (background-painting, in this case) is hard to do with the traditional way of overriding virtual methods because you won't be able to call the inherited method to get all the text painted. Instead, You'll probably have to copy and paste the base class's implementation and then add the conditional parts in the middle somewhere.
One way I can think of is to create helper class under implementation
type
TLinkLabelHelper = class helper for TLinkLabel
public
procedure Add(const aBGColor: TColor; const S: string);
end;
procedure TLinkLabelHelper.Add(const aBGColor: TColor; const S: string);
begin
Color := aBGColor;
Caption := S;
end;
Then, I create a public
procedure AfterConstruction; override;
procedure Form_A.AfterConstruction;
begin
inherited;
LinkLabel1.Add(Self.Color, 'Hello World');
end;
Hope this works.
My advice: use simple TLabel. TLabel has a property named Transparent - this is what you need. Set your TLabels cursor to crHandPoint (AFAIR this is the link cursor), set font to blue underline, and write OnClick event handler, that will open web browser to navigate to the pointed url.
You can even have one default event handler.
procedure OnClickOnMyLinkTLabels(Sender : TObject);
var
Address : string;
begin
if NOT (Sender is TLabel) then Exit;
Address := (Sender as TLabel).Caption;
ShellExecute(self.WindowHandle,'open',PChar(Address),nil,nil, SW_SHOWNORMAL);
end;
Edit:
If you do not want to have address in your caption, you can use Tag property to retrieve address and set caption to whatever you want:
procedure OnClickOnMyLinkTLabels(Sender : TObject);
var
Address : string;
begin
if NOT (Sender is TLabel) then Exit;
Address := GetAddresByTag( (Sender as TLabel).Tag );
ShellExecute(self.WindowHandle,'open',PChar(Address),nil,nil, SW_SHOWNORMAL);
end;
How you will implement GetAddresByTag is your choice. The most simple one is use an array of strings:
//in your form defintion
private
FAddresses : array of string;
function GetAddresByTag(id : integer): string;
begin
if (i<Low(FAddresses)) OR (I> High(FAddresses)) then
raise EXception.Create('wrong id sent!');
Result:= FAddresses[id];
end;
If your text is static, then you can still do this using labels. Lay out your entire text block INCLUDING the words you want as links. Set the label as transparent. Next, drop separate label components (also set to transparent) that will be the link. Change the color to clNavy, font style to fsunderline and the cursor to crHand. Then position the label OVER the existing text. Then write a onClick handler for each "link" label to perform your hot link.
While this is not optimal, it does work as long as you don't want to bold the text and are willing to keep the text the same font size. Of course this doesn't work so well if the block is dynamic, as you would have to calculate the position of the link labels in code, which is fairly complicated if you are using wordwrap. If not, you can use the canvas.textwidth and canvas.textheight methods to determine the necessary offset positions for your link labels.

Making a TPageControl flat in Delphi 7

I don't know whether this question can be answered here, but I hope it will.
I wrote a simple text editor in Delphi 7 that serves as my primary IDE for writing C code under Windows. I run Windows in a VM and I needed something light.
In any case, it uses a TpageControl that gets a new tab whenever you open or create a new file. Pretty standard.
Now, the TPageControl under Delphi has no flat property.
NO I don't mean setting the tab style to tsButtons or tsFlatButtons
the borders cannot be set to "none" and it looks pretty bad when you add a text editor into the tab control.
Is there any way to make a TpageControl flat?
EDIT:
On an open source PageControl that supports flat here's what I found:
procedure TCustomTabExtControl.WndProc(var Message: TMessage);
begin
if(Message.Msg=TCM_ADJUSTRECT) and (FFlat) then
begin
Inherited WndProc(Message);
Case TAbPosition of
tpTop : begin
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Top:=PRect(Message.LParam)^.Top-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
tpLeft : begin
PRect(Message.LParam)^.Top:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Left:=PRect(Message.LParam)^.Left-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
tpBottom : begin
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Bottom:=PRect(Message.LParam)^.Bottom-4;
PRect(Message.LParam)^.Top:=0;
end;
tpRight : begin
PRect(Message.LParam)^.Top:=0;
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=PRect(Message.LParam)^.Right-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
end;
end else Inherited WndProc(Message);
end;
The thing is when I tried something similar on the main application it won't work. It won't even compile.
When the tabs are drawn as buttons, no border is drawn around the display area, so set the Style property to tsButtons or tsFlatButtons. (For non-VCL programmers, this is equivalent to including the tcs_Buttons window style on the tab control.)
An alternative is to use a TNotebook. It holds pages, but it doesn't do any painting at all. You'd have to provide the tabs yourself, such as by setting the tab control's height equal to the height of the tabs, or by using a TTabSet. (TTabSet is available in Delphi 2005; I'm not sure about Delphi 7.)
Regarding the code you found, it would be helpful if you indicated why it doesn't compile, or if you gave a link to where you found it, since I suppose the compilation error was because it refers to fields or properties of the custom class rather than the stock one. Here's what you can try to put it in your own code, without having to write a custom control.
Make two new declarations in your form like this:
FOldTabProc: TWndMethod;
procedure TabWndProc(var Msg: TMessage);
In the form's OnCreate event handler, assign that method to the page control's WindowProc property:
FOldTabProc := PageControl1.WindowProc;
PageControl1.WindowProc := TabWndProc;
Now implement that method and handle the tcm_AdjustRect messsage:
procedure TForm1.TabWndProc(var Msg: TMessage);
begin
FOldTabProc(Msg);
if Msg.Msg = tcm_AdjustRect then begin
case PageControl1.TabPosition of
tpTop: begin
PRect(Msg.LParam)^.Left := 0;
PRect(Msg.LParam)^.Right := PageControl1.ClientWidth;
Dec(PRect(Msg.LParam)^.Top, 4);
PRect(Msg.LParam)^.Bottom := PageControl1.ClientHeight;
end;
end;
end;
end;
You can fill in the other three cases if you need them. Tcm_AdjustRect is a message identifier declared in the CommCtrl unit. If you don't have that message in that unit, declare it yourself; its value is 4904.
I suspect this doesn't stop the control from drawing its borders. Rather, it causes the contained TTabSheet to grow a little bigger and cover up the borders.
I'm using Delphi XE8 and the following seems to do the trick:
ATabControl.Tabs.Clear;
ATabControl.Style := TTabStyle.tsFlatButtons;
ATabControl.Brush.Color := clWhite;
You could always use a commercial solution. I would strongly recommend Raize components, which support flat TPageControls with tabs. The component set is very easy to work with, and supports numerous visual enhancements which in my opinion give a better feel to any application.
(source: raize.com)
Drop two TPageControls, one with tabs as Tabs, with a global height equal to the tabs, and one with flatbuttons and Tabvisible properties set to false, which would be aligned under the first one. Then make sure the tab change on the first TPagecontrol makes the tabs also change in the second one.

Resources