How to enforce VCL style overrides when dynamically creating components? - delphi

In Delphi XE2 I have successfully created overrides for the VCL Styles for a custom component class I have created. What I have found though is that the styles do not appear to apply during runtime creation of the controls.
To be specific I have extended TPanel and am filling a TScrollBox with dynamically created Panels, setting each to a specific color. I also use the API to suspend redraws on the ScrollBox during the creation process.
When the loading is complete, I am left with TPanels set to clWindow (visually) but when I drag and drop the TPanel to another location/control the colors I set in code "kick in". So something is not letting/allowing those colors to apply... or the Panels simply are not refreshing.
So I'm not quite sure if there is a "refresh" I need to call with VCL Style overrides on dynamic component creation, or if the suspension of redraws on TScrollBox are causing interference with the color not updating on the Panel when created.. since it is a child of the suspended ScrollBox.
I'm wondering if there is just a simple & known "gotcha" I am overlooking with what I am trying to do.
I've stripped down the project to bare essentials and it still has the issue.
This is a simple extension of TPanel adding a label.
unit InfluencePanel;
interface
uses
System.SysUtils, System.Classes, Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls,
Vcl.Graphics;
type
TInfluencePanel = class(TPanel)
private
{ Private declarations }
oCaptionLabel : TLabel;
FLabelCaption : String;
procedure SetLabelCaption(sCaption : String);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
property LabelCaption : string read FLabelCaption write SetLabelCaption;
published
{ Published declarations }
end;
procedure Register;
implementation
constructor TInfluencePanel.Create(AOwner: TComponent);
begin
inherited;
oCaptionLabel := TLabel.Create(Self);
with oCaptionLabel do
begin
Caption := 'Caption';
Top := 0;
Left := 0;
Align := alTop;
WordWrap := True;
Parent := Self;
end;
end;
procedure TInfluencePanel.SetLabelCaption(sCaption: string);
begin
FLabelCaption := sCaption;
if oCaptionLabel <> nil then oCaptionLabel.Caption := FLabelCaption;
end;
procedure Register;
begin
RegisterComponents('Influence Elements', [TInfluencePanel]);
end;
end.
This is the simple project that should show the issue. Button 1 loads five instances of the TInfluencePanel into ScrollBox1. They appear with the default windows color and no style instead of the color in code. Button2 moves the controls to ScrollBox2 where they appear with the coded colors. This has all the suspended redraws taken out, etc.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Themes, InfluencePanel;
type
TInfluencePanelStyleHookColor = class(TEditStyleHook)
private
procedure UpdateColors;
protected
procedure WndProc(var Message: TMessage); override;
public
constructor Create(AControl: TWinControl); override;
end;
type
TForm1 = class(TForm)
ScrollBox1: TScrollBox;
ScrollBox2: TScrollBox;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
Vcl.Styles;
type
TWinControlH= class(TWinControl);
constructor TInfluencePanelStyleHookColor.Create(AControl: TWinControl);
begin
inherited;
UpdateColors;
end;
procedure TInfluencePanelStyleHookColor.UpdateColors;
var
LStyle: TCustomStyleServices;
begin
if Control.Enabled then
begin
Brush.Color := TWinControlH(Control).Color;
FontColor := TWinControlH(Control).Font.Color;
end
else
begin
LStyle := StyleServices;
Brush.Color := LStyle.GetStyleColor(scEditDisabled);
FontColor := LStyle.GetStyleFontColor(sfEditBoxTextDisabled);
end;
end;
procedure TInfluencePanelStyleHookColor.WndProc(var Message: TMessage);
begin
case Message.Msg of
CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
begin
UpdateColors;
SetTextColor(Message.WParam, ColorToRGB(FontColor));
SetBkColor(Message.WParam, ColorToRGB(Brush.Color));
Message.Result := LRESULT(Brush.Handle);
Handled := True;
end;
CM_ENABLEDCHANGED:
begin
UpdateColors;
Handled := False;
end
else
inherited WndProc(Message);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
iPanel, iLastPosition : Integer;
oPanel : TInfluencePanel;
begin
iLastPosition := 0;
for iPanel := 1 to 5 do
begin
oPanel := TInfluencePanel.Create(ScrollBox1);
with oPanel do
begin
Align := alLeft;
Left := iLastPosition;
Width := 90;
Parent := ScrollBox1;
Color := RGB(200,100,iPanel*10);
LabelCaption := 'My Panel ' + IntToStr(iPanel);
Margins.Right := 5;
AlignWithMargins := True;
end;
iLastPosition := iLastPosition + 90;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
iPanel : Integer;
begin
for iPanel := ScrollBox1.ControlCount - 1 downto 0 do
begin
if ScrollBox1.Controls[iPanel].ClassType = TInfluencePanel then
TInfluencePanel(ScrollBox1.Controls[iPanel]).Parent := ScrollBox2;
end;
end;
initialization
TStyleManager.Engine.RegisterStyleHook(TInfluencePanel,TInfluencePanelStyleHookColor);
end.

Your Style hook has not effect in the paint process because The TPanel doesn't use a style hook to draw the control. you must override the paint method in your component like so.
unit InfluencePanel;
interface
uses
System.SysUtils, System.Classes, Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls,
Vcl.Graphics;
type
TInfluencePanel = class(TPanel)
private
{ Private declarations }
oCaptionLabel : TLabel;
FLabelCaption : String;
procedure SetLabelCaption(sCaption : String);
protected
procedure Paint; override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
property LabelCaption : string read FLabelCaption write SetLabelCaption;
published
{ Published declarations }
end;
procedure Register;
implementation
uses
Winapi.Windows,
System.Types,
Vcl.Themes;
constructor TInfluencePanel.Create(AOwner: TComponent);
begin
inherited;
oCaptionLabel := TLabel.Create(Self);
with oCaptionLabel do
begin
Caption := 'Caption';
Top := 0;
Left := 0;
Align := alTop;
WordWrap := True;
Parent := Self;
end;
end;
procedure TInfluencePanel.SetLabelCaption(sCaption: string);
begin
FLabelCaption := sCaption;
if oCaptionLabel <> nil then oCaptionLabel.Caption := FLabelCaption;
end;
procedure TInfluencePanel.Paint;
const
Alignments: array[TAlignment] of Longint = (DT_LEFT, DT_RIGHT, DT_CENTER);
VerticalAlignments: array[TVerticalAlignment] of Longint = (DT_TOP, DT_BOTTOM, DT_VCENTER);
var
Rect: TRect;
LColor: TColor;
LStyle: TCustomStyleServices;
LDetails: TThemedElementDetails;
TopColor : TColor;
BottomColor : TColor;
LBaseColor : TColor;
LBaseTopColor : TColor;
LBaseBottomColor: TColor;
Flags: Longint;
procedure AdjustColors(Bevel: TPanelBevel);
begin
TopColor := LBaseTopColor;
if Bevel = bvLowered then
TopColor := LBaseBottomColor;
BottomColor := LBaseBottomColor;
if Bevel = bvLowered then
BottomColor := LBaseTopColor;
end;
begin
Rect := GetClientRect;
LBaseColor := Color;//use the color property value to get the background color.
LBaseTopColor := clBtnHighlight;
LBaseBottomColor := clBtnShadow;
LStyle := StyleServices;
if LStyle.Enabled then
begin
LDetails := LStyle.GetElementDetails(tpPanelBevel);
if LStyle.GetElementColor(LDetails, ecEdgeHighLightColor, LColor) and (LColor <> clNone) then
LBaseTopColor := LColor;
if LStyle.GetElementColor(LDetails, ecEdgeShadowColor, LColor) and (LColor <> clNone) then
LBaseBottomColor := LColor;
end;
if BevelOuter <> bvNone then
begin
AdjustColors(BevelOuter);
Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
end;
if not (LStyle.Enabled and (csParentBackground in ControlStyle)) then
Frame3D(Canvas, Rect, LBaseColor, LBaseColor, BorderWidth)
else
InflateRect(Rect, -Integer(BorderWidth), -Integer(BorderWidth));
if BevelInner <> bvNone then
begin
AdjustColors(BevelInner);
Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
end;
with Canvas do
begin
if not LStyle.Enabled or not ParentBackground then
begin
Brush.Color := LBaseColor;
FillRect(Rect);
end;
if ShowCaption and (Caption <> '') then
begin
Brush.Style := bsClear;
Font := Self.Font;
Flags := DT_EXPANDTABS or DT_SINGLELINE or
VerticalAlignments[VerticalAlignment] or Alignments[Alignment];
Flags := DrawTextBiDiModeFlags(Flags);
if LStyle.Enabled then
begin
LDetails := LStyle.GetElementDetails(tpPanelBackground);
if not LStyle.GetElementColor(LDetails, ecTextColor, LColor) or (LColor = clNone) then
LColor := Font.Color;
LStyle.DrawText(Handle, LDetails, Caption, Rect, TTextFormatFlags(Flags), LColor)
end
else
DrawText(Handle, Caption, -1, Rect, Flags);
end;
end;
end;
procedure Register;
begin
RegisterComponents('Influence Elements', [TInfluencePanel]);
end;
end.
Also in the runtime creation set the ParentBackground property to False
for iPanel := 1 to 5 do
begin
oPanel := TInfluencePanel.Create(ScrollBox1);
with oPanel do
begin
Align := alLeft;
Left := iLastPosition;
Width := 90;
Parent := ScrollBox1;
ParentBackground:=False;// <----
Color := RGB(200,100,iPanel*20);
LabelCaption := 'My Panel ' + IntToStr(iPanel);
Margins.Right := 5;
AlignWithMargins := True;
end;
iLastPosition := iLastPosition + 90;
end;

Related

Toggle Delphi Label color with Firemonkey

With VCL I can do this:
procedure TForm1.FormCreate(Sender: TObject);
begin
Label1.Color := clBlue;
Label1.Font.Color := clYellow;
label1.Caption := ' My Label '
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if Label1.Color = clBlue then
begin
Label1.Color := clYellow;
Label1.Font.Color := clBlue
end
else
begin
Label1.Color := clBlue;
Label1.Font.Color := clYellow
end
end;
As you can see, the color of the label background and the text toggles from blue to yellow and vice-versa.
I want to do the same with Firemonkey but, all the search I made only says that FMX labels has no background color (I don't understand why),
and don't give me a effetive clue how to do the same thing as in VCL.
Can someone write here the equivalent FMX code snippet?
Thank you.
In Firemonkey many controls do not have a color. Instead you're supposed to layer controls out of different components.
In this case if you want a background use a TRectangle.
In the designer Delphi insists that you cannot have a label be parented by a rectangle, but this if of course not true, in FMX any control can parent any other.
Just use the structure pane to drag the label on top of the rectangle and voila label and rectangle are joined together.
The equivalent code to the above would look something like this.
unit Unit45;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Objects;
type
TForm45 = class(TForm)
Rectangle1: TRectangle;
Label1: TLabel;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
Rects: array of TRectangle;
Labels: array of TLabel;
public
{ Public declarations }
end;
var
Form45: TForm45;
implementation
{$R *.fmx}
uses
System.UIConsts;
procedure TForm45.Button1Click(Sender: TObject);
var
i: integer;
begin
for i:= Low(Rects) to High(Rects) do begin
if Rects[i].Fill.Color <> claBlue then
Rects[i].Fill.Color:= claBlue
else Rects[i].Fill.Color:= claYellow;
end;
end;
procedure TForm45.FormCreate(Sender: TObject);
var
i: integer;
begin
SetLength(Rects,2);
SetLength(Labels,2);
for i:= 0 to 1 do begin
Rects[i]:= TRectangle.Create(self);
Rects[i].Parent:= self;
Labels[i]:= TLabel.Create(self);
Labels[i].Parent:= Rects[i];
Rects[i].Width:= Rectangle1.Width;
Rects[i].Height:= Rectangle1.Height;
Rects[i].Position.y:= 0 + i * Rects[i].Height;
Rects[i].Position.x:= 0 + i * Rects[i].Width;
Rects[i].Stroke.Kind:= TBrushKind.None;
Labels[i].AutoSize:= true;
Labels[i].Text:= 'Test'+IntToStr(i+1);
Labels[i].Position:= Label1.Position;
end;
end;
end.
Note that I've done the construction of the labels and rects in runtime, but you can do this in design time as well.
The color constants in FMX have changed from the VCL, see: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Colors_in_FireMonkey
As an alternative, you can create a custom style for your TLabel component:
right-click the Label ("LabelXX") and select "Edit Custom Style...";
add a "TRectangle" component from the "Tool Palette" to the new style created ("LabelXXStyle1");
select the new "Rectangle1Style" object and send it to back (Edit -> "Send to Back");
Set the "Rectangle1Style" properties:
"Align" : "Client";
"Fill / Bitmap / Color" : any background color;
Apply changes (close the "Style Designer").
Set the "StyleLookup" property of the other TLabel(s) you need to "LabelXXStyle1".
If you are interested, here is one of my samples:
function CreateLabel(
AOwner: TFmxObject; ARangeWidth, ARangeHeight, ASizeMin, ASizeMax: Integer;
AText: String; AColor: TAlphaColor): TLabel;
var
LFMXObj: TFMXObject;
LFontSize: Integer;
begin
Result := TLabel.Create(AOwner);
with Result do
begin
Parent := AOwner;
Text := AText;
ApplyStyleLookup;
LFMXObj := FindStyleResource('text');
if Assigned(LFMXObj) then
begin
LFontSize := ASizeMin + Random(ASizeMax - ASizeMin);
//TText(LFMXObj).Fill.Color := AColor; // XE2
TText(LFMXObj).Color := AColor;
TText(LFMXObj).Font.Size := LFontSize;
TText(LFMXObj).Font.Style := TText(LFMXObj).Font.Style + [TFontStyle.fsBold];
TText(LFMXObj).WordWrap := False;
TText(LFMXObj).AutoSize := True;
Canvas.Font.Assign(TText(LFMXObj).Font);
Position.X := Random(ARangeWidth - Round(Canvas.TextWidth(Text)));
Position.Y := Random(ARangeHeight - Round(Canvas.TextHeight(Text)));
end;
{
// test background label painting
with TRectangle.Create(Result) do
begin
Parent := AOwner;
Fill.Color := TAlphaColors.Lightgrey;
Fill.Kind := TBrushKind.bkSolid;
Width := Result.Canvas.TextWidth(Result.Text);
Height := Result.Canvas.TextHeight(Result.Text);
Position.X := Result.Position.X;
Position.Y := Result.Position.Y;
Result.BringToFront;
end;
}
AutoSize := True;
Visible := True;
end;
end;

"Control has no parent" in Create ComboBox

In this code :
unit MSEC;
interface
uses
Winapi.Windows, Vcl.Dialogs, Vcl.ExtCtrls, System.SysUtils, System.Classes, Vcl.Controls, Vcl.StdCtrls;
type
TMSEC = class(TWinControl)
private
FOpr :TComboBox;
public
constructor Create(AOwner: TComponent); override;
end;
implementation
const
DEF_OPERATIONS :array[0..3] of Char = ('+', '-', '*', '/');
constructor TMSEC.Create(AOwner: TComponent);
var i :Integer;
begin
inherited;
FOpr:= TComboBox.Create(Self);
with FOpr do begin
Parent:= Self;
Align:= alLeft;
Width:= DEF_OPERATIONS_WIDTH;
Style:= csDropDownList;
//error in next lines :
Items.Clear;
for i := Low(DEF_OPERATIONS) to High(DEF_OPERATIONS) do Items.Add(DEF_OPERATIONS[i]);
ItemIndex:= 0;
end;
end;
end.
When I change ComboBox items, the program breaks with the message :
'Control' has no parent.
How can I fix this error or initialize ComboBox items in another way?
TComboBox requires an allocated HWND in order to store strings in its Items property. In order for TComboBox to get an HWND, its Parent needs an HWND first, and its Parent needs an HWND, and so on. The problem is that your TMSEC object does not have a Parent assigned yet when its constructor runs, so it is not possible for the TComboBox to get an HWND, hense the error.
Try this instead:
type
TMSEC = class(TWinControl)
private
FOpr: TComboBox;
protected
procedure CreateWnd; override;
public
constructor Create(AOwner: TComponent); override;
end;
constructor TMSEC.Create(AOwner: TComponent);
begin
inherited;
FOpr := TComboBox.Create(Self);
with FOpr do begin
Parent := Self;
Align := alLeft;
Width := DEF_OPERATIONS_WIDTH;
Style := csDropDownList;
Tag := 1;
end;
end;
procedure TMSEC.CreateWnd;
var
i :Integer;
begin
inherited;
if FOpr.Tag = 1 then
begin
FOpr.Tag := 0;
for i := Low(DEF_OPERATIONS) to High(DEF_OPERATIONS) do
FOpr.Items.Add(DEF_OPERATIONS[i]);
FOpr.ItemIndex := 0;
end;
end;
Remy explained the problem well, but for a more general solution, you could create a descendant of TComboBox, for example:
type
TComboBoxSafe = class(TComboBox)
strict private
FSafeItemIndex: Integer;
FSafeItems: TArray<string>;
function GetSafeItemIndex: Integer;
function GetSafeItems: TArray<string>;
procedure SetSafeItemIndex(const AValue: Integer);
procedure SetSafeItems(const AValue: TArray<string>);
strict protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
public
constructor Create(AOwner: TComponent); override;
property SafeItemIndex: Integer read GetSafeItemIndex write SetSafeItemIndex;
property SafeItems: TArray<string> read GetSafeItems write SetSafeItems;
end;
{ TComboBoxSafe }
constructor TComboBoxSafe.Create(AOwner: TComponent);
begin
inherited;
FSafeItemIndex := -1;
end;
procedure TComboBoxSafe.CreateWnd;
var
LOnChange: TNotifyEvent;
begin
inherited;
LOnChange := OnChange;
OnChange := nil;
try
Items.Text := string.Join(sLineBreak, FSafeItems);
ItemIndex := FSafeItemIndex;
finally
OnChange := LOnChange;
end;
end;
procedure TComboBoxSafe.DestroyWnd;
begin
FSafeItemIndex := ItemIndex;
FSafeItems := Items.ToStringArray;
inherited;
end;
function TComboBoxSafe.GetSafeItemIndex: Integer;
begin
if WindowHandle <> 0 then
Result := ItemIndex
else
Result := FSafeItemIndex;
end;
function TComboBoxSafe.GetSafeItems: TArray<string>;
begin
if WindowHandle <> 0 then
Result := Items.ToStringArray
else
Result := FSafeItems;
end;
procedure TComboBoxSafe.SetSafeItemIndex(const AValue: Integer);
begin
if WindowHandle <> 0 then
ItemIndex := AValue
else
FSafeItemIndex := AValue;
end;
procedure TComboBoxSafe.SetSafeItems(const AValue: TArray<string>);
begin
if WindowHandle <> 0 then
Items.Text := string.Join(sLineBreak, AValue)
else
FSafeItems := AValue;
end;

Performance issues re-sizing large amount of components on form resize

I feel my failure so far lies in search terms as information on this has to be pretty common. Basically I am looking for common solutions and best practices when performing resizes on several components while a form is resized.
I have a form with a component that is based upon TScrollBox. The ScrollBox contains rows which are added dynamically at run time. They are basically a subcomponent. Each one has an image on the left and a memo on the right. The height is set based upon the width and aspect ratio of the image. Upon the resize of the scroll box a loop sets the width of the rows triggering the rows own internal resize. The loop also sets the relative top position if the heights have changed.
Screen shot:
Around 16 rows performs fine. My goal is closer to 32 rows which is very choppy and can peg a core at 100% usage.
I have tried:
Added a check to prevent a new resize starting while the previous has yet to complete. It answered if it occured and it does sometimes.
I tried preventing it resizing more often than every 30 ms which would allow for 30 frame per second drawing. Mixed results.
Changed the rows base component from TPanel to TWinControl. Not sure if there is a performance penalty using the Panel but its an old habit.
With and without double buffering.
I would like to allow row resizing to occur during a resize as a preview to how large the image will be in the row. That eliminates one obvious solution that in some applications is an acceptable loss.
Right now the resize code internally for the row is completely dynamic and based upon the dimensions of each image. Next thing I plan to try is to basically specify the Aspect Ratio, Max Width/Height based on the largest image in the collection. This should reduce the amount of math per row. But it seems like the issues are more the resize event and the loop itself?
Full unit code for the components:
unit rPBSSVIEW;
interface
uses
Classes, Controls, Forms, ExtCtrls, StdCtrls, Graphics, SysUtils, rPBSSROW, Windows, Messages;
type
TPBSSView = class(TScrollBox)
private
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure ResizeRows(Sender: TObject);
procedure AddRow(FileName: String);
procedure FillRow(Row: Integer; ImageStream: TMemoryStream);
end;
var
PBSSrow: Array of TPBSSRow;
Resizingn: Boolean;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Standard', [TScrollBox]);
end;
procedure TPBSSView.AddRow(FileName: String);
begin
SetLength(PBSSrow,(Length(PBSSrow) + 1));
PBSSrow[Length(PBSSrow)-1] := TPBSSRow.create(self);
With PBSSrow[Length(PBSSrow)-1] do
begin
Left := 2;
if (Length(PBSSrow)-1) = 0 then Top := 2 else Top := ((PBSSRow[Length(PBSSRow) - 2].Top + PBSSRow[Length(PBSSRow) - 2].Height) + 2);
Width := (inherited ClientWidth - 4);
Visible := True;
Parent := Self;
PanelLeft.Caption := FileName;
end;
end;
procedure TPBSSView.FillRow(Row: Integer; ImageStream: TMemoryStream);
begin
PBSSRow[Row].LoadImageFromStream(ImageStream);
end;
procedure TPBSSView.ResizeRows(Sender: TObject);
var
I, X: Integer;
begin
if Resizingn then exit
else
begin
Resizingn := True;
HorzScrollBar.Visible := False;
X := (inherited ClientWidth - 4);
if Length(PBSSrow) > 0 then
for I := 0 to Length(PBSSrow) - 1 do
Begin
PBSSRow[I].Width := X; //Set Width
if not (I = 0) then //Move all next ones down.
begin
PBSSRow[I].Top := (PBSSRow[(I - 1)].Top + PBSSRow[(I - 1)].Height) + 2;
end;
Application.ProcessMessages;
End;
HorzScrollBar.Visible := True;
Resizingn := False;
end;
end;
constructor TPBSSView.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
OnResize := ResizeRows;
DoubleBuffered := True;
VertScrollBar.Tracking := True;
Resizingn := False;
end;
destructor TPBSSView.Destroy;
begin
inherited;
end;
end.
Row Code:
unit rPBSSROW;
interface
uses
Classes, Controls, Forms, ExtCtrls, StdCtrls, Graphics, pngimage, SysUtils;
type
TPBSSRow = class(TWinControl)
private
FImage: TImage;
FPanel: TPanel;
FMemo: TMemo;
FPanelLeft: TPanel;
FPanelRight: TPanel;
FImageWidth: Integer;
FImageHeight: Integer;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure MyPanelResize(Sender: TObject);
procedure LeftPanelResize(Sender: TObject);
published
procedure LoadImageFromStream(ImageStream: TMemoryStream);
property Image: TImage read FImage;
property Panel: TPanel read FPanel;
property PanelLeft: TPanel read FPanelLeft;
property PanelRight: TPanel read FPanelRight;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Standard', [TWinControl]);
end;
procedure TPBSSRow.MyPanelResize(Sender: TObject);
begin
if (Width - 466) <= FImageWidth then FPanelLeft.Width := (Width - 466)
else FPanelLeft.Width := FImageWidth;
FPanelRight.Width := (Width - FPanelLeft.Width);
end;
procedure TPBSSRow.LeftPanelResize(Sender: TObject);
var
AspectRatio: Extended;
begin
FPanelRight.Left := (FPanelLeft.Width);
//Enforce Info Minimum Height or set Height
if FImageHeight > 0 then AspectRatio := (FImageHeight/FImageWidth) else
AspectRatio := 0.4;
if (Round(AspectRatio * FPanelLeft.Width)) >= 212 then
begin
Height := (Round(AspectRatio * FPanelLeft.Width));
FPanelLeft.Height := Height;
FPanelRight.Height := Height;
end
else
begin
Height :=212;
FPanelLeft.Height := Height;
FPanelRight.Height := Height;
end;
if Fimage.Height >= FImageHeight then FImage.Stretch := False else Fimage.Stretch := True;
if Fimage.Width >= FImageWidth then FImage.Stretch := False else Fimage.Stretch := True;
end;
procedure TPBSSRow.LoadImageFromStream(ImageStream: TMemoryStream);
var
P: TPNGImage;
n: Integer;
begin
P := TPNGImage.Create;
ImageStream.Position := 0;
P.LoadFromStream(ImageStream);
FImage.Picture.Assign(P);
FImageWidth := P.Width;
FImageHeight := P.Height;
end;
constructor TPBSSRow.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
BevelInner := bvNone;
BevelOuter := bvNone;
BevelKind := bkNone;
Color := clWhite;
OnResize := MyPanelResize;
DoubleBuffered := True;
//Left Panel for Image
FPanelLeft := TPanel.Create(Self);
with FPanelLeft do
begin
SetSubComponent(true);
Align := alLeft;
Parent := Self;
//SetBounds(0,0,100,100);
ParentBackground := False;
Color := clBlack;
Font.Color := clLtGray;
Constraints.MinWidth := 300;
BevelInner := bvNone;
BevelOuter := bvNone;
BevelKind := bkNone;
BorderStyle := bsNone;
OnResize := LeftPanelResize;
end;
//Image for left panel
FImage := TImage.Create(Self);
FImage.SetSubComponent(true);
FImage.Align := alClient;
FImage.Parent := FPanelLeft;
FImage.Center := True;
FImage.Stretch := True;
FImage.Proportional := True;
//Right Panel for Info
FPanelRight := TPanel.Create(Self);
with FPanelRight do
begin
SetSubComponent(true);
Parent := Self;
Padding.SetBounds(2,5,5,2);
BevelInner := bvNone;
BevelOuter := bvNone;
BevelKind := bkNone;
BorderStyle := bsNone;
Color := clLtGray;
end;
//Create Memo in Right Panels
FMemo := TMemo.create(self);
with FMemo do
begin
SetSubComponent(true);
Parent := FPanelRight;
Align := alClient;
BevelOuter := bvNone;
BevelInner := bvNone;
BorderStyle := bsNone;
Color := clLtGray;
end;
end;
destructor TPBSSRow.Destroy;
begin
inherited;
end;
end.
A few tips:
TWinControl already ís a container, you do not need another panel inside it to add controls
You do not need an TImage component to view a graphic, that can also with TPaintBox, or as in my example control below, a TCustomControl,
Since all of your other panels are not recognizable (borders and bevels are disabled), loose them altogether and place the TMemo directly on your row control,
SetSubComponent is only for design time usage. You do not need it. Nor the Register procedures for that matter.
Put the global rows array inside your class definition, otherwise multiple TPBSSView controls will use the same array!
TWinControl already tracks all its child controls, so you won't need the array anyway, see my example below,
Make use of the Align property to save you from realigning manually,
If the memo control is just for showing text, then remove it and paint the text yourself.
Try this one for starters:
unit PBSSView;
interface
uses
Windows, Messages, Classes, Controls, SysUtils, Graphics, ExtCtrls, StdCtrls,
Forms, PngImage;
type
TPBSSRow = class(TCustomControl)
private
FGraphic: TPngImage;
FStrings: TStringList;
function ImageHeight: Integer; overload;
function ImageHeight(ControlWidth: Integer): Integer; overload;
function ImageWidth: Integer; overload;
function ImageWidth(ControlWidth: Integer): Integer; overload;
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
procedure WMWindowPosChanging(var Message: TWMWindowPosChanging);
message WM_WINDOWPOSCHANGING;
protected
procedure Paint; override;
procedure RequestAlign; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure LoadImageFromStream(Stream: TMemoryStream);
property Strings: TStringList read FStrings;
end;
TPBSSView = class(TScrollBox)
private
function GetRow(Index: Integer): TPBSSRow;
procedure WMEnterSizeMove(var Message: TMessage); message WM_ENTERSIZEMOVE;
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
procedure WMExitSizeMove(var Message: TMessage); message WM_EXITSIZEMOVE;
protected
procedure PaintWindow(DC: HDC); override;
public
constructor Create(AOwner: TComponent); override;
procedure AddRow(const FileName: TFileName);
procedure FillRow(Index: Integer; ImageStream: TMemoryStream);
property Rows[Index: Integer]: TPBSSRow read GetRow;
end;
implementation
{ TPBSSRow }
constructor TPBSSRow.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Width := 300;
Height := 50;
FStrings := TStringList.Create;
end;
destructor TPBSSRow.Destroy;
begin
FStrings.Free;
FGraphic.Free;
inherited Destroy;
end;
function TPBSSRow.ImageHeight: Integer;
begin
Result := ImageHeight(Width);
end;
function TPBSSRow.ImageHeight(ControlWidth: Integer): Integer;
begin
if (FGraphic <> nil) and not FGraphic.Empty then
Result := Round(ImageWidth(ControlWidth) * FGraphic.Height / FGraphic.Width)
else
Result := Height;
end;
function TPBSSRow.ImageWidth: Integer;
begin
Result := ImageWidth(Width);
end;
function TPBSSRow.ImageWidth(ControlWidth: Integer): Integer;
begin
Result := ControlWidth div 2;
end;
procedure TPBSSRow.LoadImageFromStream(Stream: TMemoryStream);
begin
FGraphic.Free;
FGraphic := TPngImage.Create;
Stream.Position := 0;
FGraphic.LoadFromStream(Stream);
Height := ImageHeight + Padding.Bottom;
end;
procedure TPBSSRow.Paint;
var
R: TRect;
begin
Canvas.StretchDraw(Rect(0, 0, ImageWidth, ImageHeight), FGraphic);
SetRect(R, ImageWidth, 0, Width, ImageHeight);
Canvas.FillRect(R);
Inc(R.Left, 10);
DrawText(Canvas.Handle, FStrings.Text, -1, R, DT_EDITCONTROL or
DT_END_ELLIPSIS or DT_NOFULLWIDTHCHARBREAK or DT_NOPREFIX or DT_WORDBREAK);
Canvas.FillRect(Rect(0, ImageHeight, Width, Height));
end;
procedure TPBSSRow.RequestAlign;
begin
{eat inherited}
end;
procedure TPBSSRow.WMEraseBkgnd(var Message: TWmEraseBkgnd);
begin
Message.Result := 1;
end;
procedure TPBSSRow.WMWindowPosChanging(var Message: TWMWindowPosChanging);
begin
inherited;
if (FGraphic <> nil) and not FGraphic.Empty then
Message.WindowPos.cy := ImageHeight(Message.WindowPos.cx) + Padding.Bottom;
end;
{ TPBSSView }
procedure TPBSSView.AddRow(const FileName: TFileName);
var
Row: TPBSSRow;
begin
Row := TPBSSRow.Create(Self);
Row.Align := alTop;
Row.Padding.Bottom := 2;
Row.Parent := Self;
end;
constructor TPBSSView.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
VertScrollBar.Tracking := True;
end;
procedure TPBSSView.FillRow(Index: Integer; ImageStream: TMemoryStream);
begin
Rows[Index].LoadImageFromStream(ImageStream);
end;
function TPBSSView.GetRow(Index: Integer): TPBSSRow;
begin
Result := TPBSSRow(Controls[Index]);
end;
procedure TPBSSView.PaintWindow(DC: HDC);
begin
{eat inherited}
end;
procedure TPBSSView.WMEnterSizeMove(var Message: TMessage);
begin
if not AlignDisabled then
DisableAlign;
inherited;
end;
procedure TPBSSView.WMEraseBkgnd(var Message: TWmEraseBkgnd);
var
DC: HDC;
begin
DC := GetDC(Handle);
try
FillRect(DC, Rect(0, VertScrollBar.Range, Width, Height), Brush.Handle);
finally
ReleaseDC(Handle, DC);
end;
Message.Result := 1;
end;
procedure TPBSSView.WMExitSizeMove(var Message: TMessage);
begin
inherited;
if AlignDisabled then
EnableAlign;
end;
end.
If this still performs badly, then there are multiple other enhancements possible.
Update:
Flickering eliminated by overriding/intercepting WM_ERASEBKGND (and intercepting PaintWindow for versions < XE2),
Better performance by making use of DisableAlign and EnableAlign.
I don't know if this will make a significant difference, but instead setting PBSSRow[I].Width and PBSSRow[I].Top separately, make one call to PBSSRow[I].SetBounds instead. This will save you one Resize event for that SubComponent.

Why doesn't my size-changing control work when it shares a form with a TSplitter?

I'm writing a panel control that allows the user to mimimize the panel and to hide the components on this panel.
A single THidePanel seems to work as expected, but not when I put two of them on a form separated by a splitter. The first panel is aligned alLeft; the second panel alClient:
When the second panel's button is clicked, it does not react to minimize or maximize. Here is all of my code. Why doesn't it work?
const
BoarderSize = 20;
type
TButtonPosition = (topleft, topright, buttomleft, buttomright);
///
/// a panel with a smaller panel inside and a button on the side
///
THidePanel = class(TPanel)
private
{ Private-Deklarationen }
///
/// a smaller working panel
WorkingPanel: TPanel;
FLargeHight: Integer;
FLargeWidth: Integer;
FActivateButton: TButton;
FExpandState: Boolean;
FButtonPosition: TButtonPosition;
FOnActivateBtnClick: TNotifyEvent;
procedure SetButtonPosition(const Value: TButtonPosition);
protected
{ Protected-Deklarationen }
public
{ Public-Deklarationen }
constructor create(aOwner: TComponent); override;
procedure WMSize(var Msg: TWMSize); message WM_SIZE;
procedure HideComponents;
procedure H_ActivateButtonClick(Sender: TObject);
procedure SetState(astate: Boolean);
procedure free;
destructor destroy; override;
published
{ Published-Deklarationen }
property OnActivateButtonClick: TNotifyEvent read FOnActivateBtnClick
write FOnActivateBtnClick;
property ButtonPosition: TButtonPosition read FButtonPosition
write SetButtonPosition;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [THidePanel]);
end;
{ THidePanel }
constructor THidePanel.create(aOwner: TComponent);
begin
inherited;
WorkingPanel := TPanel.create(self);
WorkingPanel.Caption := 'V01';
FActivateButton := TButton.create(self);
FActivateButton.Parent := self;
FActivateButton.Caption := '<';
FActivateButton.OnClick := H_ActivateButtonClick;
FActivateButton.Width := BoarderSize;
FActivateButton.Height := BoarderSize;
WorkingPanel.Caption := '';
FLargeWidth := self.Width;
SetButtonPosition(topright);
end;
destructor THidePanel.destroy;
begin
inherited;
end;
procedure THidePanel.free;
begin
inherited;
WorkingPanel.free;
FActivateButton.free;
end;
procedure THidePanel.HideComponents;
var
i: Integer;
begin
for i := 0 to WorkingPanel.ControlCount - 1 do
WorkingPanel.Controls[i].Visible := False;
end;
procedure THidePanel.WMSize(var Msg: TWMSize);
begin
/// set inner panel size
WorkingPanel.Top := self.Top + BoarderSize;
WorkingPanel.Left := self.Left + BoarderSize;
WorkingPanel.Width := self.Width - 2 * BoarderSize;
WorkingPanel.Height := self.Height - 2 * BoarderSize;
/// move button
SetButtonPosition(FButtonPosition);
end;
procedure THidePanel.H_ActivateButtonClick(Sender: TObject);
begin
/// button is clicked!
///
FExpandState := not FExpandState;
SetState( FExpandState );
///
if (Assigned(FOnActivateBtnClick)) then
FOnActivateBtnClick(self);
end;
procedure THidePanel.SetButtonPosition(const Value: TButtonPosition);
begin
FButtonPosition := Value;
case FButtonPosition of
topleft:
begin
FActivateButton.Left := 0;
FActivateButton.Top := 0;
end;
topright:
begin
FActivateButton.Left := self.Width - BoarderSize;
FActivateButton.Top := 0;
end;
buttomleft:
begin
FActivateButton.Left := 0;
FActivateButton.Top := self.ClientWidth - BoarderSize;
end;
buttomright:
begin
FActivateButton.Top := self.ClientWidth - BoarderSize;
FActivateButton.Left := self.Width - BoarderSize;
end;
else
/// never go here
end;
end;
procedure THidePanel.SetState(astate: Boolean);
begin
if astate then
begin
/// ...
FActivateButton.Caption := '>';
self.Width := BoarderSize;
end
else
begin
/// ...
FActivateButton.Caption := '<';
self.Width := FLargeWidth;
end;
end;
When Control's Anchors set to alClient, you can not change the size . Set second panel align to alLeft or alRight . if you want fill form with this control, set AutoSize of form True or manually set max size of your control on resize it .
Like MohsenB already explained (+1ed), you cannot change the size of a control with Align = alClient. But since you are making this a component, I would choose to change the Align setting of the component temporarily, instead of dealing with this in the designer code: i.e. make it a feature of the component to be able to set its Align property to alClient and let it behave accordingly when situation requires.
I think you are looking for the following enhancements:
unit Unit2;
interface
uses
Messages, Classes, Controls, StdCtrls, ExtCtrls;
const
BorderSize = 20;
type
TButtonPosition = (bpTopLeft, bpTopRight, bpBottomLeft, bpBottomRight);
THidePanel = class(TPanel)
private
FActivateButton: TButton;
FButtonPosition: TButtonPosition;
FExpandState: Boolean;
FOldAlign: TAlign;
FOldWidth: Integer;
FOnActivateBtnClick: TNotifyEvent;
FWorkingPanel: TPanel;
procedure ActivateButtonClick(Sender: TObject);
procedure SetButtonPosition(Value: TButtonPosition);
protected
function CanResize(var NewWidth, NewHeight: Integer): Boolean; override;
procedure Resize; override;
public
constructor Create(AOwner: TComponent); override;
procedure SetState(AState: Boolean);
published
property ButtonPosition: TButtonPosition read FButtonPosition
write SetButtonPosition default bpTopRight;
property OnActivateButtonClick: TNotifyEvent read FOnActivateBtnClick
write FOnActivateBtnClick;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [THidePanel]);
end;
{ THidePanel }
constructor THidePanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FWorkingPanel := TPanel.Create(Self);
FWorkingPanel.Caption := '';
FWorkingPanel.SetBounds(BorderSize, BorderSize, Width - 2 * BorderSize,
Height - 2 * BorderSize);
FWorkingPanel.Anchors := [akLeft, akTop, akRight, akBottom];
FWorkingPanel.Parent := Self;
FActivateButton := TButton.Create(Self);
FActivateButton.Caption := '<';
FActivateButton.OnClick := ActivateButtonClick;
FActivateButton.Width := BorderSize;
FActivateButton.Height := BorderSize;
FActivateButton.Parent := Self;
SetButtonPosition(bpTopRight);
end;
procedure THidePanel.ActivateButtonClick(Sender: TObject);
begin
FExpandState := not FExpandState;
SetState(FExpandState);
if Assigned(FOnActivateBtnClick) then
FOnActivateBtnClick(Self);
end;
procedure THidePanel.SetButtonPosition(Value: TButtonPosition);
begin
if FButtonPosition <> Value then
begin
FButtonPosition := Value;
case FButtonPosition of
bpTopLeft:
begin
FActivateButton.Left := 0;
FActivateButton.Top := 0;
FActivateButton.Anchors := [akLeft, akTop];
end;
bpTopRight:
begin
FActivateButton.Left := Width - BorderSize;
FActivateButton.Top := 0;
FActivateButton.Anchors := [akRight, akTop];
end;
bpBottomLeft:
begin
FActivateButton.Left := 0;
FActivateButton.Top := ClientWidth - BorderSize;
FActivateButton.Anchors := [akLeft, akBottom];
end;
bpBottomRight:
begin
FActivateButton.Top := ClientWidth - BorderSize;
FActivateButton.Left := Width - BorderSize;
FActivateButton.Anchors := [akRight, akBottom];
end;
end;
end;
end;
procedure THidePanel.SetState(AState: Boolean);
begin
if AState then
begin
FActivateButton.Caption := '>';
FOldAlign := Align;
if FOldAlign = alClient then
Align := alLeft;
Width := BorderSize;
end
else
begin
FActivateButton.Caption := '<';
if FOldAlign = alClient then
Align := FOldAlign
else
Width := FOldWidth;
end;
end;
procedure THidePanel.Resize;
begin
if not FExpandState then
FOldWidth := Width;
inherited Resize;
end;
function THidePanel.CanResize(var NewWidth, NewHeight: Integer): Boolean;
begin
Result := inherited CanResize(NewWidth, NewHeight);
if FExpandState then
NewWidth := BorderSize;
end;
end.
Testing code:
unit Unit1;
interface
uses
Controls, Forms, Unit2, ExtCtrls;
type
TForm1 = class(TForm)
procedure FormClick(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormClick(Sender: TObject);
begin
with THidePanel.Create(Self) do
begin
Align := alLeft;
Parent := Self;
end;
with TSplitter.Create(Self) do
begin
Left := 200;
Parent := Self;
end;
with THidePanel.Create(Self) do
begin
Align := alClient;
Parent := Self;
end;
end;
end.

Delphi: Changing the Button Color using a Class Helper

I need to change the visual style of my delphi form controls inorder to show them from a .Net environment. To do this, I need to change the colors of delphi controls to blue ($00FCF5EE). I have used "TButton" controls widely which doesn't have a "Color" property.So, instead of changing the buttons to speed buttons, I have tried a different approach by introducing a parent form and inheriting all the other forms from this parent form. In the parent form, I have a class helper to change the color of buttons. Here is the code: (I am using Delphi 2007)
TButtonHelper=class helper for TButton
private
procedure doChangeColor;
public
procedure DrawChangeColor;
end;
TParentForm = class(TForm)
public
procedure AfterConstruction; override;
end;
And in the implementation section, I have
procedure TButtonHelper.doChangeColor;
var
SaveIndex: Integer;
FCanvas:TCanvas;
rect:TRect;
begin
if csDestroying in ComponentState then exit;
FCanvas:=TCanvas.Create;
SaveIndex := SaveDC(Self.Handle);
FCanvas.Lock;
try
FCanvas.Handle := Handle;
FCanvas.Font := Font;
FCanvas.Brush := self.Brush;
FCanvas.Brush.Color:=$00FCF5EE;
FCanvas.FillRect(BoundsRect);//Omitting the code to draw the text
finally
FCanvas.Handle := 0;
FCanvas.Unlock;
RestoreDC(handle, SaveIndex);
FCanvas.Free;
end;
end;
procedure TButtonHelper.DrawChangeColor;
begin
doChangeColor;
self.Repaint;
end;
procedure TParentForm.AfterConstruction;
var
i : Integer;
wc: TControl;
begin
inherited;
for i := 0 to self.ControlCount - 1 do begin
wc:=Controls[i];
if wc is TButton then
TButton(wc).DrawChangeColor;
end;
end;
But this doesn't work. Although the doChangeColor method is executed, it is not changing the color of the button.Please let me know what I am missing here.
Thanking you all,
Pradeep
here's a class that adds color properties to the standard TButton:
unit u_class_colorbutton;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, StdCtrls, Buttons, ExtCtrls;
type
TColorButton = class(TButton)
private
ShowBackColor : Boolean;
FCanvas : TCanvas;
IsFocused : Boolean;
FBackColor : TColor;
FForeColor : TColor;
FHoverColor : TColor;
procedure SetBackColor(const Value: TColor);
procedure SetForeColor(const Value: TColor);
procedure SetHoverColor(const Value: TColor);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure WndProc(var Message : TMessage); override;
procedure SetButtonStyle(Value: Boolean); override;
procedure DrawButton(Rect: TRect; State: UINT);
procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED;
procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
procedure CNMeasureItem(var Message: TWMMeasureItem); message CN_MEASUREITEM;
procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property BackColor : TColor read FBackColor write SetBackColor default clBtnFace;
property ForeColor : TColor read FForeColor write SetForeColor default clBtnText;
property HoverColor: TColor read FHoverColor write SetHoverColor default clBtnFace;
end;
procedure Register;
implementation
constructor TColorButton.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ShowBackColor := True;
FCanvas := TCanvas.Create;
BackColor := clBtnFace;
ForeColor := clBtnText;
HoverColor := clBtnFace;
end;
destructor TColorButton.Destroy;
begin
FreeAndNil(FCanvas);
inherited Destroy;
end;
procedure TColorButton.WndProc(var Message : TMessage);
begin
if (Message.Msg = CM_MOUSELEAVE) then
begin
ShowBackColor := True;
Invalidate;
end;
if (Message.Msg = CM_MOUSEENTER) then
begin
ShowBackColor := False;
Invalidate;
end;
inherited;
end;
procedure TColorButton.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do
Style := Style or BS_OWNERDRAW;
end;
procedure TColorButton.SetButtonStyle(Value: Boolean);
begin
if Value <> IsFocused then
begin
IsFocused := Value;
Invalidate;
end;
end;
procedure TColorButton.CNMeasureItem(var Message: TWMMeasureItem);
begin
with Message.MeasureItemStruct^ do
begin
itemWidth := Width;
itemHeight := Height;
end;
end;
procedure TColorButton.CNDrawItem(var Message: TWMDrawItem);
var
SaveIndex: Integer;
begin
with Message.DrawItemStruct^ do
begin
SaveIndex := SaveDC(hDC);
FCanvas.Lock;
try
FCanvas.Handle := hDC;
FCanvas.Font := Font;
FCanvas.Brush := Brush;
DrawButton(rcItem, itemState);
finally
FCanvas.Handle := 0;
FCanvas.Unlock;
RestoreDC(hDC, SaveIndex);
end;
end;
Message.Result := 1;
end;
procedure TColorButton.CMEnabledChanged(var Message: TMessage);
begin
inherited;
Invalidate;
end;
procedure TColorButton.CMFontChanged(var Message: TMessage);
begin
inherited;
Invalidate;
end;
procedure TColorButton.SetBackColor(const Value: TColor);
begin
if FBackColor <> Value then
begin
FBackColor:= Value;
Invalidate;
end;
end;
procedure TColorButton.SetForeColor(const Value: TColor);
begin
if FForeColor <> Value then
begin
FForeColor:= Value;
Invalidate;
end;
end;
procedure TColorButton.SetHoverColor(const Value: TColor);
begin
if FHoverColor <> Value then
begin
FHoverColor:= Value;
Invalidate;
end;
end;
procedure TColorButton.DrawButton(Rect: TRect; State: UINT);
var Flags, OldMode: Longint;
IsDown, IsDefault, IsDisabled: Boolean;
OldColor: TColor;
OrgRect: TRect;
NewCaption : string;
begin
NewCaption := Caption;
OrgRect := Rect;
Flags := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT;
IsDown := State and ODS_SELECTED <> 0;
IsDisabled := State and ODS_DISABLED <> 0;
IsDefault := State and ODS_FOCUS <> 0;
if IsDown then Flags := Flags or DFCS_PUSHED;
if IsDisabled then Flags := Flags or DFCS_INACTIVE;
if (IsFocused or IsDefault) then
begin
FCanvas.Pen.Color := clWindowFrame;
FCanvas.Pen.Width := 1;
FCanvas.Brush.Style := bsClear;
FCanvas.Rectangle(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom);
InflateRect(Rect, - 1, - 1);
end;
if IsDown then
begin
FCanvas.Pen.Color := clBtnShadow;
FCanvas.Pen.Width := 1;
FCanvas.Brush.Color := clBtnFace;
FCanvas.Rectangle(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom);
InflateRect(Rect, - 1, - 1);
end
else
begin
DrawFrameControl(FCanvas.Handle, Rect, DFC_BUTTON, Flags);
end;
if IsDown then OffsetRect(Rect, 1, 1);
OldColor := FCanvas.Brush.Color;
if ShowBackColor then
FCanvas.Brush.Color := BackColor
else
FCanvas.Brush.Color := HoverColor;
FCanvas.FillRect(Rect);
FCanvas.Brush.Color := OldColor;
OldMode := SetBkMode(FCanvas.Handle, TRANSPARENT);
FCanvas.Font.Color := ForeColor;
if IsDisabled then
DrawState(FCanvas.Handle, FCanvas.Brush.Handle, nil, Integer(NewCaption), 0,
((Rect.Right - Rect.Left) - FCanvas.TextWidth(NewCaption)) div 2,
((Rect.Bottom - Rect.Top) - FCanvas.TextHeight(NewCaption)) div 2,
0, 0, DST_TEXT or DSS_DISABLED)
else
begin
InflateRect(Rect, -4, -4);
DrawText(FCanvas.Handle, PChar(NewCaption), - 1, Rect, DT_WORDBREAK or DT_CENTER);
end;
SetBkMode(FCanvas.Handle, OldMode);
if (IsFocused and IsDefault) then
begin
Rect := OrgRect;
InflateRect(Rect, - 4, - 4);
FCanvas.Pen.Color := clWindowFrame;
FCanvas.Brush.Color := clBtnFace;
DrawFocusRect(FCanvas.Handle, Rect);
end;
end;
procedure Register;
begin
RegisterComponents('Standard', [TColorButton]);
end;
initialization
RegisterClass(TColorButton); // needed for persistence at runtime
end.
You can hack it into your application easily:
find/replace all TButton references to TColorButton
inside your .pas and .dfm files.
You can set separate colors for background, font and hovering.
If you want add styling application wide, maybe it is better to create a GUI with a library that has native support like DevExpress, TMS, ...
Personally, I like DevExpress the most but that's a matter of personal taste.

Resources