TFont Property to control font of subclassed controls - delphi

I have created a component descended from TPanel. In the constructor of the component I create several TButton components. I have created and surfaced a ButtonFont property of type TFont. This property controls the font of all the buttons on the component. Example:
TMyPanel = Class(TPanel)
private
FButtonFont : TFont;
FExampleButton : TButton;
procedure SetButtonFont(Value: TFont);
public
property ButtonFont: TFont read FButtonFont write SetButtonFont;
constructor Create (AOwner: TComponent); override;
end;
constructor TMyPanel.Create (AOwner: TComponent);
begin
FButtonFont := TFont.Create;
FExampleButton := TButton.Create(self);
FExampleButton.Parent := self;
.......
inherited;
end;
procedure TMyPanel.SetButtonFont(Value: TFont);
begin
FButtonFont.Assign(Value);
FExampleButton.Font := Value;
end;
The following will cause all subclassed buttons have their button font changed:
MyLabel.Font.Size := 22;
MyPanel.ButtonFont := label1.font;
I can see the SetButtonFont method is being called.
How can I get something like this to cause all subclassed buttons to change their font size:
MyPanel.ButtonFont.Size := 22;

Assign a handler to the font's OnChange event and update all the sub-controls' fonts in that handler:
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited;
FButtonFont := TFont.Create;
FButtonFont.OnChange := ButtonFontChanged; // <-- here
FExampleButton := TButton.Create(Self);
FExampleButton.Parent := Self;
...
end;
destructor TMyPanel.Destroy;
begin
...
FButtonFont.Free;
inherited;
end;
procedure TMyPanel.ButtonFontChanged(Sender: TObject);
begin
FExampleButton.Font := FButtonFont;
...
end;

Related

Flickering TSpeedButton in a TMaskEdit descendant

I try to create a TComboBox like component, inherited from TMaskEdit with a TSpeedButton inside the edit itself.
The problem when I type something into the edit the most of the button disappears (the right and the bottom edge still visible). If I move the mouse over the component or exit using TAB, the button appears again.
Here is the code:
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, Mask, Buttons;
type
TMyEdit = class(TCustomMaskEdit)
private
FButton: TSpeedButton;
protected
procedure CreateButton;
public
constructor Create(AOwner: TComponent); override;
procedure CreateWnd; override;
destructor Destroy; override;
end;
implementation
constructor TMyEdit.Create(AOwner: TComponent);
begin
inherited;
CreateButton;
end;
procedure TMyEdit.CreateButton;
begin
FButton := TSpeedButton.Create(Self);
FButton.Parent := Self;
FButton.Align := alRight;
FButton.Width := 16;
FButton.Caption := '';
FButton.Transparent := False;
end;
destructor TMyEdit.Destroy;
begin
FreeAndNil(FButton);
inherited;
end;
procedure TMyEdit.CreateWnd;
begin
inherited;
Perform(EM_SETMARGINS, EC_RIGHTMARGIN, (FButton.Width + 4) shl 16);
end;
What do I miss?
Solved.
WS_CLIPCHILDREN flag have to be included in the CreateParams() AND the button must be placed on TWinControl descendant (TPanel in my case) OR the button itself must be a TWinControl descendant (for example TButton). Graphic controls has no Handle, thats the problem.
Modified code:
procedure TMyEdit.CreateButton;
var
xDrawRect: TRect;
xPanel : TPanel;
begin
xPanel := TPanel.Create(Self);
xPanel.Parent := Self;
xPanel.SetBounds(Width - Height, 0, Height, Height);
xPanel.BevelOuter := bvNone;
FButton := TSpeedButton.Create(Self);
FButton.Parent := xPanel;
FButton.Align := alClient;
FButton.Caption := '';
end;
procedure TMyEdit.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or WS_CLIPCHILDREN;
end;

Delphi: delete inherited TStringGrid

I want to have a custom StringGrid element.
I created a class:
type
TClassStringGrid = class(TCustomControl)
...
with
constructor TClassStringGrid.Create(AOwner: TForm);
begin
inherited Create(nil);
myGroupBox1 := TGroupBox.Create(AOwner);
myGroupBox1.Parent := AOwner;
myStringGrid1 := TStringGrid.Create(self);
myStringGrid1.Parent := myGroupBox1;
myStringGrid1.Options := myStringGrid1.Options + [goEditing];
end;
destructor TClassStringGrid.Destroy;
begin
if myStringGrid1 <> nil then begin
FreeAndNil(myStringGrid1);
end;
if myGroupBox1 <> nil then begin
DestroyComponents;
FreeAndNil(myGroupBox1);
end;
// Call the parent class destructor
inherited;
end;
I created a class in Form1 and show it. It works. But if I put some value into the StringGrid (Form1) and then try to close Form1 I get an exception "the element has no parent window" in FreeAndNil(myStringGrid1);.
What is wrong by Destroy?
I would be thankfull for any information you can provide me.
Assuming you want to show a String grid in a Group box on this control, then this is how it should look like:
type
TMyStringGrid = class(TCustomControl)
private
FGroupBox: TGroupBox;
FStringGrid: TStringGrid;
public
constructor Create(AOwner: TComponent); override;
end;
constructor TMyStringGrid.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FGroupBox := TGroupBox.Create(Self);
FGroupBox.Parent := Self;
FStringGrid := TStringGrid.Create(Self);
FStringGrid.Parent := FGroupBox;
end;
In this manner, your newly designed control is owner and parent of the sub controls. Destruction is done automatically because of that.

Propagate Event from internal component

I'm writing a component that include few components.
TMyComponent = class(TPanel)
private
fGrid : TExCustomDBGrid;
fOnCellClick : TDBGridClickEvent;
public
constructor Create(AOwner: TComponent); override;
published
property OnCellClick: TDBGridClickEvent read FOnCellClick write FOnCellClick;
End;
...
constructor TMyComponent .Create(AOwner: TComponent);
begin
inherited;
fGrid := TExCustomDBGrid.Create(self);
fGrid.parent := self;
fGrid.Align := alClient;
end;
I want to be able to propagate the Event from the component (TPanel), to the fGrid included.
How can I reach that goal ?
I guess I should declare an Event with the same type on the TPanel (as container component). Then how to propagate into the fGrid ?
It's a bit unclear what you're asking, but based on the code I see, write an event handler and assign it to the grid...
procedure TMyComponent.DBGridCellClicked(Column: TColumn);
begin
if Assigned(fOnCellClick) then
fOnCellClick(Column);
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
fGrid := TExCustomDBGrid.Create(self);
fGrid.Parent := self;
fGrid.Align := alClient;
fGrid.OnCellClick := DBGridCellClicked;
end;

How to draw a colored border around Edit field using TShape?

I try to draw a colored border around TEdit field using TShape. I define the following component:
type TGEdit = class(TEdit)
private
m_shape : TShape;
protected
procedure setBorderColor( brd_col : TColor );
procedure setBorderWidth( brd_wid : integer );
public
constructor create(AOwner : TComponent); override;
destructor destroy(); override;
published
property borderColor : TColor read m_border_color write setBorderColor default clBlack;
property borderWidth : integer read m_border_width write setBorderWidth default 1;
end;
Define a TShape object in constructor.
constructor TGEdit.create(AOwner : TComponent);
begin
inherited;
Self.BorderStyle:= bsNone;
m_border_color := clBlack;
m_border_width := 1;
m_shape := TShape.Create(AOwner);
m_shape.Parent := Self.Parent;
m_shape.Shape := stRectangle;
m_shape.Width := Self.Width+2*m_border_width;
m_shape.Height := Self.Height+2*m_border_width;
m_shape.Left := Self.Left-m_border_width;
m_shape.Top := self.Top-m_border_width;
m_shape.Brush.Style := bsClear;
m_shape.Pen.Color := m_border_color;
m_shape.Pen.Style := psSolid;
end;
destructor TGNumberEdit.destroy();
begin
m_shape.Free();
inherited;
end;
Define a procedures to change border's color and width
procedure TGEdit.setBorderColor( brd_col : TColor );
begin
if m_border_color = brd_col then
exit;
m_border_color := brd_col;
m_shape.Pen.Color := m_border_color;
end;
procedure TGEdit.setBorderWidth( brd_wid : integer );
begin
if (m_border_width = brd_wid) or (brd_wid < 0) then
exit;
m_border_width := brd_wid;
m_shape.Pen.Width := m_border_width;
end;
But when I put component on form the Shape doesn't drawn. Where is an error in my code?
TShape is a TGraphicControl derived control, and as such can never appear on top of a TWinControl derived control other than its own Parent.
Your TGEdit constructor has an error in it. Self.Parent is nil in the constructor, so you are assigning a nil Parent to the TShape, and thus it will never be visible.
If you want the TShape to have the same Parent as your TGEdit then you need to override the virtual SetParent() method, which is called after construction is finished. You will also have to override the virtual SetBounds() method to make sure your TShape moves around whenever your TGEdit moves around, eg:
type
TGEdit = class(TEdit)
...
protected
...
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
procedure SetParent(AParent: TWinControl); override;
...
end;
procedure TGEdit.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
inherited;
if m_shape <> nil then
m_shape.SetBounds(Self.Left - m_border_width, Self.Top - m_border_width, Self.Width + (2*m_border_width), Self.Height + (2*m_border_width));
end;
procedure TGEdit.SetParent(AParent: TWinControl);
begin
inherited;
if m_shape <> nil then
m_shape.Parent := Self.Parent;
end;
Now, with all of that said, there is an alternative solution - derive your component from TCustomPanel instead and have it create a TEdit on top of itself. You can set the Panel's color, bordering, etc as needed.

Using TFrame, how do I properly access the TCanvas property just as in a TForm?

I need to draw on the frames Canvas at runtime just like you would do with a normal form but for some reason they decided not to add the Canvas property to the frame even tho both TCustomFrame and TCustomForm come from the same parent class that handles the Canvas.
I've made it work up to the part where I can draw something by overriding the PaintWindow procedure but I still can't seem to use the Canvas property at runtime as if I'm missing a big chunk of the code.
Here's what I've done up to now :
TCustomFrameEx = class(TCustomFrame)
private
FCanvas: TControlCanvas;
function GetCanvas: TCanvas;
public
property Canvas: TCanvas read GetCanvas;
end;
TFrame = class(TCustomFrameEx)
protected
procedure PaintWindow(DC: HDC); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy(); override;
published
...
end;
constructor TFrame.Create(AOwner: TComponent);
begin
inherited;
FCanvas := TControlCanvas.Create();
end;
destructor TFrame.Destroy();
begin
FreeAndNil(fCanvas);
inherited;
end;
function TCustomFrameEx.GetCanvas : TCanvas;
begin
Result := fCanvas;
end;
procedure TFrame.PaintWindow(DC: HDC);
begin
inherited;
FCanvas.Handle := DC;
FCanvas.Control := Self;
FCanvas.Brush.Color := clWhite;
fCanvas.FillRect(GetClientRect);
FCanvas.Handle := 0;
end;
I assume I'm not properly assigning the handle or missing some paint event?
The easiest way would be
procedure TFrame2.PaintWindow(DC: HDC);
Var
c:TCanvas;
begin
inherited;
c := Tcanvas.Create;
try
c.Handle := DC;
c.Brush.Color := clWhite;
c.FillRect(GetClientRect);
c.Brush.Color := clBlue;
//c.Ellipse(0,0,200,200);
finally
c.Free;
end;
end;
The PaintWindow method of a frame is only called if the frame has children. You'll need to add a paint box control (or similar) to your frame, or some children (perhaps invisible).

Resources