How to create Delphi component inherited from few other components? - delphi

Tutorials that I found about how to create delphi components were nice, but they only used one of existing components as object to inherit actions from. Something like this
unit CountBtn;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
type
TCountBtn = class(TButton)
private
FCount: integer;
protected
procedure Click;override;
public
procedure ShowCount;
published
property Count:integer read FCount write FCount;
constructor Create(aowner:Tcomponent); override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Mihan Components', [TCountBtn]);
end;
constructor TCountBtn.Create(aowner:Tcomponent);
begin
inherited create(Aowner);
end;
procedure Tcountbtn.Click;
begin
inherited click;
FCount:=FCount+1;
end;
procedure TCountBtn.ShowCount;
begin
Showmessage('On button '+ caption+' you clicked: '+inttostr(FCount)+' times');
end;
end.
But what should I do if I need component which use few elements? Lets say, I got Button and Edit field. And on button click there in edit field should appers text the same as on button. I start to make it like this, but seems like it's not gonna work as I want:
unit TestComp;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TUiCompU = class(TCustomControl)
private
{ Private declarations }
FButton: TButton;
FEdit: TEdit;
protected
{ Protected declarations }
procedure Paint; override;
//wrong!
procedure FButton.Click;override
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
//wrong!
property ButtonText: String read FButton.Caption write FButton.Caption;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Ui', [TUiCompU]);
end;
{ TUiCompU }
constructor TUiCompU.Create(AOwner: TComponent);
begin
inherited;
Width := 200;
Height := 50;
FButton := TButton.Create(Self);
FButton.SetSubComponent(True);
FButton.Parent := Self;
FButton.Top := 8;
FButton.Left := 50;
FButton.Width := 35;
FButton.Name := 'Button';
FEdit := TEdit.Create(Self);
FEdit.SetSubComponent(True);
FEdit.Parent := Self;
FEdit.Top := 8;
FEdit.Left := 84;
FEdit.Width := 121;
FEdit.Name := 'Edit';
end;
procedure TUiCompU.Paint;
begin
Canvas.Rectangle(ClientRect);
end;
end.
How should I add here Click procedure, which is realte to click on the button? And is there are good tutorial about how to made good components using others? (I need to create something like slideshow component btw).
Thank you, and sorry for my english.

You can write methods for the subcomponent events, but it has one big weakness; if you publish those subcomponents, there is a risk that someone will steal you this binding by writing own method:
type
TUiCompU = class(TCustomControl)
private
FEdit: TEdit;
FButton: TButton;
procedure ButtonClick(Sender: TObject);
procedure EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
public
constructor Create(AOwner: TComponent); override;
end;
implementation
constructor TUiCompU.Create(AOwner: TComponent);
begin
inherited;
FButton := TButton.Create(Self);
...
FButton.OnClick := ButtonClick;
FEdit := TEdit.Create(Self);
...
FEdit.OnKeyDown := EditKeyDown;
end;
procedure TUiCompU.ButtonClick(Sender: TObject);
begin
// do whatever you want here
end;
procedure TUiCompU.EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
// do whatever you want here
end;

Related

How can I do PING threads, reading OnReply event in Delphi 6?

I have a problem with Delphi 6 and Indy's TIdIcmpClient component.
I get this message when compiling the following code, in the marked line (51):
FPing.OnReply := OnPingReply;
[Error] fire.pas(51): Incompatible types: 'TComponent' and 'TIdIcmpClient'
How should I fix it?
unit fire;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdRawBase, IdRawClient,
IdIcmpClient;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TMyThread = class(TThread)
private
FIndex: Integer;
FPing: TIdIcmpClient;
FIP: string;
protected
procedure Execute; override;
procedure OnPingReply(ASender: TIdIcmpClient; AReplyStatus: TReplyStatus);
public
constructor Create(AIndex: Integer);
destructor Destroy; override;
end;
constructor TMyThread.Create(AIndex: Integer);
begin
inherited Create(False);
FIndex := AIndex;
FIP := '192.168.1.' + IntToStr(FIndex + 1);
FPing := TIdIcmpClient.Create(nil);
FPing.Host:=FIP;
FPing.ReceiveTimeout:=1500;
FPing.OnReply := OnPingReply;
end;
destructor TMyThread.Destroy;
begin
FPing.Free;
inherited;
end;
//var// icmp:array[0..10] of TIdIcmpClient;
// ip:string;
procedure TMyThread.Execute; // aici e ce face thread-ul
var
i: Integer;
begin
FPing.Ping;
// ICMP.Ping('a',1000);
// Sleep(1300);
// form1.memo1.lines.add(IntToStr(findex)+' '+ICMP.ReplyStatus.fromipaddress);
for i := 1 to 1 do
begin
// 'findex' este indexul thread-ului din matrice
form1.memo1.lines.add(inttostr(findex)+' Thread running...');
application.ProcessMessages;
Sleep(1000);
end;
end;
procedure TMyThread.OnPingReply(ASender: TIdIcmpClient; AReplyStatus: TReplyStatus);
begin
if AReplyStatus.BytesReceived > 0 then
form1.memo1.Lines.add(FIP+ ' is reachable')
else
form1.memo1.Lines.add(FIP+ ' is not reachable: ');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyThreads: array[0..10] of TMyThread;
// icmp:array[0..10] of TIdIcmpClient;
i: Integer;
begin
{ for i := 0 to 10 do //10 fire
begin
icmp[i]:=tidicmpclient.create(nil);
icmp[i].ReceiveTimeout:=1200;
ip:=Format('%s.%d', ['192.168.1', i]);
ICMP[i].Host :=ip;
end; }
for i := 0 to 10 do //10 fire
begin
MyThreads[i] := TMyThread.Create(i);
MyThreads[i].Resume;
application.ProcessMessages;
end;
// Readln;
for i := 0 to 10 do
begin
MyThreads[i].Free;
// icmp[i].Free;
end;
end;
end.
I expected it to be compilable, but I don't see the reason why it is not.
Your event handler is declared wrong. The ASender parameter needs to be TComponent rather than TIdIcmpClient, and the AReplyStatus parameter needs to be const:
procedure OnPingReply(ASender: TComponent; const AReplyStatus: TReplyStatus);
That being said, you don't need to use the OnReply event at all in this situation. TIdIcmpClient operates synchronously, so you can simply use the TIdIcmpClient.ReplyStatus property after the TIdIcmpClient.Ping() method exits:
procedure TMyThread.Execute; // aici e ce face thread-ul
var
...
begin
FPing.Ping;
if FPing.ReplyStatus.BytesReceived > 0 then
...
else
...
...
end;
Also, you must synchronize with the main UI thread when accessing UI controls in a worker thread. You can use TThread.Synchronize() method for that.
And, you do not need to call Application.ProcessMessages() in a worker thread. Doing so will have no effect on the main UI thread.
With all of that said, try something more like this:
unit fire;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdRawBase, IdRawClient,
IdIcmpClient;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure AddText(const AText: String);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TMyThread = class(TThread)
private
FIndex: Integer;
FPing: TIdIcmpClient;
FIP: string;
FText: String;
procedure AddTextToUI(const AText: String);
procedure DoSyncText;
protected
procedure Execute; override;
public
constructor Create(AIndex: Integer);
destructor Destroy; override;
end;
constructor TMyThread.Create(AIndex: Integer);
begin
inherited Create(False);
FIndex := AIndex;
FIP := '192.168.1.' + IntToStr(FIndex + 1);
FPing := TIdIcmpClient.Create(nil);
FPing.Host := FIP;
FPing.ReceiveTimeout := 1500;
end;
destructor TMyThread.Destroy;
begin
FPing.Free;
inherited;
end;
procedure TMyThread.AddTextToUI(const AText: String);
begin
FText := AText;
Synchronize(DoSyncText);
end;
procedure TMyThread.DoSyncText;
begin
Form1.AddText(FText);
end;
procedure TMyThread.Execute; // aici e ce face thread-ul
begin
AddTextToUI(IntToStr(FIndex) + ' Thread running...');
try
FPing.Ping;
except
AddTextToUI('Error pinging ' + FIP);
Exit;
end;
if FPing.ReplyStatus.BytesReceived > 0 then
AddTextToUI(FIP + ' is reachable')
else
AddTextToUI(FIP + ' is not reachable');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyThreads: array[0..10] of TMyThread;
I: Integer;
begin
for I := Low(MyThreads) to High(MyThreads) do //10 fire
begin
MyThreads[I] := TMyThread.Create(I);
end;
for I := Low(MyThreads) to High(MyThreads) do
begin
MyThreads[i].WaitFor;
MyThreads[i].Free;
end;
end;
procedure TForm1.AddText(const AText: String);
begin
Memo1.Lines.Add(AText);
end;
end.

Bind parent object's event to temporary child object's method

I have an application where an invisible "Host" application object creates main form and main form creates temporarily a data monitoring dialog form.
There is an asynchronous data receiver in "Host" that has a trace output event. This event should be temporarily bound with data monitoring dialog form's method when dialog form exists and unbound when it is about to be destroyed.
I made a minimal equivalent to this application below. Could you check whether it is the right way to do so? Please pay attention to "Attention" comments.
program BindToTempObject;
uses
Vcl.Forms, System.Classes, Vcl.StdCtrls, Vcl.ExtCtrls, System.SysUtils;
type
TBoolEvent = procedure(b: Boolean) of object;
TDialogForm = class(TForm)
public
constructor Create(AOwner: TComponent); override;
procedure BoolEventHandler(b: Boolean);
end;
TMainForm = class(TForm)
private
_btn: TButton;
_onBoolEventRelay: TBoolEvent; //Attention
procedure _btnClick(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
procedure BoolEventRelay(b: Boolean); //Attention
end;
TAppObject = class
private
_mainForm: TMainForm;
_eventSource: TTimer;
_boolState: Boolean;
procedure _eventSourceTick(Sender: TObject);
public
OnBoolEvent: TBoolEvent;
constructor Create();
destructor Destroy(); override;
end;
var
app: TAppObject;
{ TAppObject }
constructor TAppObject.Create();
begin
Application.CreateForm(TMainForm, _mainForm);
OnBoolEvent := _mainForm.BoolEventRelay; //Attention
_eventSource := TTimer.Create(nil);
_eventSource.OnTimer := _eventSourceTick;
_eventSource.Enabled := True;
end;
destructor TAppObject.Destroy();
begin
_eventSource.OnTimer := nil;
_eventSource.Free();
inherited;
end;
procedure TAppObject._eventSourceTick(Sender: TObject);
begin
_boolState := not _boolState;
if Assigned(OnBoolEvent) then OnBoolEvent(_boolState);
end;
{ TMainForm }
constructor TMainForm.Create(AOwner: TComponent);
begin
inherited CreateNew(AOwner);
Caption := 'Main form';
_btn := TButton.Create(Self);
_btn.Parent := Self;
_btn.Caption := 'Click me';
_btn.OnClick := _btnClick;
end;
procedure TMainForm.BoolEventRelay(b: Boolean);
begin
if Assigned(_onBoolEventRelay) then _onBoolEventRelay(b); //Attention
end;
procedure TMainForm._btnClick(Sender: TObject);
var
dlg: TDialogForm;
begin
dlg := TDialogForm.Create(Self);
try
_onBoolEventRelay := dlg.BoolEventHandler; //Attention
dlg.ShowModal();
finally
_onBoolEventRelay := nil; //Attention
dlg.Free();
end;
end;
{ TDialogForm }
procedure TDialogForm.BoolEventHandler(b: Boolean);
begin
Caption := BoolToStr(b, True);
end;
constructor TDialogForm.Create(AOwner: TComponent);
begin
inherited CreateNew(AOwner);
Caption := 'Dialog form';
end;
begin
Application.Initialize();
app := TAppObject.Create();
try
Application.Run();
finally
app.Free();
end;
end.
You could do it that way, sure. A decent separation of responsibilities between classes, so they don't have to know about each other.
However, in your particular example, since everything is in a single unit, and the app object is globally accessible, you could simplifly the code a little bit by assigning the TDialogForm.BoolEventHandler() method directly to the TAppObject.OnBoolEvent event and get rid of TMainForm as a middle man:
program BindToTempObject;
uses
Vcl.Forms, System.Classes, Vcl.StdCtrls, Vcl.ExtCtrls, System.SysUtils;
type
TBoolEvent = procedure(b: Boolean) of object;
TDialogForm = class(TForm)
public
constructor Create(AOwner: TComponent); override;
destructor Destroy(); override;
procedure BoolEventHandler(b: Boolean);
end;
TMainForm = class(TForm)
private
_btn: TButton;
procedure _btnClick(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
end;
TAppObject = class
private
_mainForm: TMainForm;
_eventSource: TTimer;
_boolState: Boolean;
procedure _eventSourceTick(Sender: TObject);
public
OnBoolEvent: TBoolEvent;
constructor Create();
destructor Destroy(); override;
end;
var
app: TAppObject;
{ TAppObject }
constructor TAppObject.Create();
begin
Application.CreateForm(TMainForm, _mainForm);
_eventSource := TTimer.Create(nil);
_eventSource.OnTimer := _eventSourceTick;
_eventSource.Enabled := True;
end;
destructor TAppObject.Destroy();
begin
_eventSource.OnTimer := nil;
_eventSource.Free();
inherited;
end;
procedure TAppObject._eventSourceTick(Sender: TObject);
begin
_boolState := not _boolState;
if Assigned(OnBoolEvent) then OnBoolEvent(_boolState);
end;
{ TMainForm }
constructor TMainForm.Create(AOwner: TComponent);
begin
inherited CreateNew(AOwner);
Caption := 'Main form';
_btn := TButton.Create(Self);
_btn.Parent := Self;
_btn.Caption := 'Click me';
_btn.OnClick := _btnClick;
end;
procedure TMainForm._btnClick(Sender: TObject);
var
dlg: TDialogForm;
begin
dlg := TDialogForm.Create(Self);
try
dlg.ShowModal();
finally
dlg.Free();
end;
end;
{ TDialogForm }
procedure TDialogForm.BoolEventHandler(b: Boolean);
begin
Caption := BoolToStr(b, True);
end;
constructor TDialogForm.Create(AOwner: TComponent);
begin
inherited CreateNew(AOwner);
Caption := 'Dialog form';
app.OnBoolEvent := BoolEventHandler;
end;
destructor TDialogForm.Destroy();
begin
app.OnBoolEvent := nil;
inherited;
end;
begin
Application.Initialize();
app := TAppObject.Create();
try
Application.Run();
finally
app.Free();
end;
end.

delphi component property: TObjectList<TPicture>

I'm trying to create a VCL component, that lets you insert multiple TImages of different sizes as properties.
I was told to best use a TObjectList ( Delphi component with a variable amount of TPictures ), but now I'm struggling to make the single TPictures assignable in the Property editor.
What i have at the moment: (it compiles)
unit ImageMultiStates;
interface
uses
Vcl.Graphics, Vcl.StdCtrls, System.SysUtils, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Forms, Generics.Collections;
type
TImageMultiStates = class(TImage)
private
FPictures: TObjectList<TPicture>;
procedure SetPicture(Which: Integer; APicture: TPicture);
function GetPicture(Which: Integer): TPicture;
public
Count: integer;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Activate(Which: Integer);
published
// property Pictures: TObjectList<TPicture> read GetPicture write SetPicture;
// property Pictures[Index: Integer]: TObjectList<TPicture> read GetPicture write SetPicture;
property Pictures: TObjectList<TPicture> read FPictures write FPictures;
end;
procedure Register;
implementation
constructor TImageMultiStates.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FPictures := TObjectList<TPicture>.Create;
end;
destructor TImageMultiStates.Destroy;
begin
FPictures.Free;
inherited Destroy;
end;
procedure TImageMultiStates.SetPicture(Which: Integer; APicture: TPicture);
begin
FPictures[Which] := APicture;
if Which=0 then
Picture.Assign(APicture);
end;
function TImageMultiStates.GetPicture(Which: Integer): TPicture;
begin
Result := FPictures[Which];
end;
procedure TImageMultiStates.Activate(Which: Integer);
begin
Picture.Assign(FPictures[Which]);
end;
procedure Register;
begin
RegisterComponents('Standard', [TImageMultiStates]);
end;
end.
What doesn't work is the final result in the PropertyEditor. It shows one single item named "Pictures", with the value "(TObjectList)". Clicking it doesn't do anything, i don't get a proper editor. Other ideas for the line in question have been commented out, they bring other errors:
The first one throws the compiler error "E2008 Incompatible Types", The second one throws "Published property 'Pictures' can not be of type ARRAY".
The IDE has no idea how to edit a TObjectList at design-time, and the DFM streaming system has no idea how to stream a TObjectList. You would have to implement a custom property editor and custom streaming logic. While that is certainly possible, it is a LOT of work.
What you are attempting to do is better handled by using System.Classes.TCollection instead. Both the IDE and the DFM streaming system have built-in support for handling TCollection editing and streaming automatically for you.
Try something more like this:
unit ImageMultiStates;
interface
uses
System.Classes, Vcl.Controls, Vcl.ExtCtrls, Vcl.Graphics;
type
TImagePictureItem = class(TCollectionItem)
private
FPicture: TPicture;
procedure PictureChanged(Sender: TObject);
procedure SetPicture(Value: TPicture);
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
published
property Picture: TPicture read FPicture write SetPicture;
end;
TImagePictureEvent = procedure(Sender: TObject; Index: Integer) of object;
TImagePictures = class(TOwnedCollection)
private
FOnPictureChange: TImagePictureEvent;
function GetPicture(Index: Integer): TImagePictureItem;
procedure SetPicture(Index: Integer; Value: TImagePictureItem);
protected
procedure Update(Item: TCollectionItem); override;
public
constructor Create(Owner: TComponent); reintroduce;
property Pictures[Index: Integer]: TImagePictureItem read GetPicture write SetPicture; default;
property OnPictureChange: TImagePictureEvent read FOnPictureChange write FOnPictureChange;
end;
TImageMultiStates = class(TImage)
private
FActivePicture: Integer;
FPictures: TImagePictures;
function GetPicture(Index: Integer): TPicture;
procedure PictureChanged(Sender: TObject; Index: Integer);
procedure SetActivePicture(Index: Integer);
procedure SetPicture(Index: Integer; Value: TPicture);
procedure SetPictures(Value: TImagePictures);
protected
procedure Loaded; override;
public
constructor Create(Owner: TComponent); override;
function Count: integer;
property Pictures[Index: Integer]: TPicture read GetPicture write SetPicture;
published
property ActivePicture: Integer read FActivePicture write SetActivePicture default -1;
property Picture stored False;
property Pictures: TImagePictures read FPictures write SetPictures;
end;
procedure Register;
implementation
{ TImagePictureItem }
constructor TImagePictureItem.Create(Collection: TCollection);
begin
inherited Create(Collection);
FPicture := TPicture.Create;
FPicture.OnChange := PictureChanged;
end;
destructor TImagePictureItem.Destroy;
begin
FPicture.Free;
inherited;
end;
procedure TImagePictureItem.PictureChanged(Sender: TObject);
begin
Changed(False);
end;
procedure TImagePictureItem.SetPicture(Value: TPicture);
begin
FPicture.Assign(Value);
end;
{ TImagePictures }
constructor TImagePictures.Create(Owner: TComponent);
begin
inherited Create(Owner, TImagePictureItem);
end;
function TImagePictures.GetPicture(Index: Integer): TImagePictureItem;
begin
Result := TImagePictureItem(inherited GetItem(Index));
end;
procedure TImagePictures.SetPicture(Index: Integer; Value: TImagePictureItem);
begin
inherited SetItem(Index, Value);
end;
procedure TImagePictures.Update(Item: TCollectionItem);
begin
if Assigned(FOnPictureChange) then
begin
if Item <> nil then
FOnPictureChange(Self, Item.Index)
else
FOnPictureChange(Self, -1);
end;
end;
{ TImageMultiStates }
constructor TImageMultiStates.Create(Owner: TComponent);
begin
inherited Create(Owner);
FPictures := TImagePictures.Create(Self);
FPictures.OnPictureChange := PictureChanged;
FActivePicture := -1;
end;
procedure TImageMultiStates.Loaded;
begin
inherited;
PictureChanged(nil, FActivePicture);
end;
function TImageMultiStates.Count: Integer;
begin
Result := FPictures.Count;
end;
procedure TImageMultiStates.PictureChanged(Sender: TObject; Index: Integer);
begin
if (FActivePicture <> -1) and ((Index = -1) or (Index = FActivePicture)) then
Picture.Assign(GetPicture(FActivePicture));
end;
function TImageMultiStates.GetPicture(Index: Integer): TPicture;
begin
Result := FPictures[Index].Picture;
end;
procedure TImageMultiStates.SetPicture(Index: Integer; Value: TPicture);
begin
FPictures[Index].Picture.Assign(Value);
end;
procedure TImageMultiStates.SetActivatePicture(Value: Integer);
begin
if FActivePicture <> Value then
begin
if ComponentState * [csLoading, csReading] = [] then
Picture.Assign(GetPicture(Value));
FActivePicture := Value;
end;
end;
procedure Register;
begin
RegisterComponents('Standard', [TImageMultiStates]);
// the inherited TImage.Picture property is published, and you cannot
// decrease the visibility of an existing property. However, if you move
// this procedure into a separate design-time package, you can then use
// DesignIntf.UnlistPublishedProperty() to hide the inherited
// Picture property at design-time, at least:
//
// UnlistPublishedProperty(TImageMultiStates, 'Picture');
//
// Thus, users are forced to use the TImageMultiStates.Pictures and
// TImageMultiStates.ActivePicture at design-time. The inherited
// Picture property will still be accessible in code at runtime, though...
end;
end.

How to adjust hint properties of a control at runtime?

I have this code with which I can set the font size of the control hint, but I want to be able somehow to adjust it later at runtime. How can I do that ?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyHintWindow = class(THintWindow)
constructor Create(AOwner: TComponent); override;
end;
TMyButton = class(TButton)
protected
procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
MyButton: TMyButton;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
MyButton:=TMyButton.Create(Form1);
MyButton.Parent:=Form1;
MyButton.Caption:='Test';
MyButton.Left:=100;
MyButton.Top:=100;
MyButton.ShowHint:=true;
end;
procedure TMyButton.CMHintShow(var Message: TCMHintShow);
begin
inherited;
Message.HintInfo.HintWindowClass:=TMyHintWindow;
Message.HintInfo.HintStr:='My custom hint';
end;
constructor TMyHintWindow.Create(AOwner: TComponent);
begin
inherited;
Canvas.Font.Size:=25;
end;
end.
Since there is only one hint window instance at the time, and that instance will be created after call to CMHintShow, you can use class variables to do additional hint customization. Class variable is class member that is shared among all instances of the class and can be accessed directly through class type or class instance.
type
TMyHintWindow = class(THintWindow)
protected
class constructor ClassCreate;
public
class var FontSize: integer;
constructor Create(AOwner: TComponent); override;
end;
class constructor TMyHintWindow.ClassCreate;
begin
FontSize := 25;
end;
constructor TMyHintWindow.Create(AOwner: TComponent);
begin
inherited;
Canvas.Font.Size := FontSize;
end;
and then you can change FontSize in CMHintShow method
procedure TMyButton.CMHintShow(var Message: TCMHintShow);
begin
inherited;
TMyHintWindow.FontSize := 12;
Message.HintInfo.HintWindowClass := TMyHintWindow;
Message.HintInfo.HintStr := 'My custom hint';
end;
Starting from indications given by TLama I finally solved this problem. The key was to set Canvas.Font.Size in TMyHintWindow.CalcHintRect.
Here is the code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyHintData = record
FontSize: Integer;
end;
TMyHintWindow = class(THintWindow)
public
function CalcHintRect(MaxWidth: Integer; const AHint: string; AData: TCustomData): TRect; override;
end;
TMyButton = class(TButton)
private
procedure CMHintShow(var AMessage: TCMHintShow); message CM_HINTSHOW;
public
FMyHintData: TMyHintData;
constructor Create(AOwner: TComponent); override;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
MyButton: TMyButton;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
TMyButton(Sender).FMyHintData.FontSize:=44;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyButton:=TMyButton.Create(Form1);
MyButton.Parent:=Form1;
MyButton.Caption:='Test';
MyButton.Left:=100;
MyButton.Top:=100;
MyButton.ShowHint:=true;
MyButton.OnClick:=Button1Click;
end;
function TMyHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: TCustomData): TRect;
begin
Canvas.Font.Size:=TMyHintData(AData^).FontSize;
Result:=inherited;
end;
constructor TMyButton.Create(AOwner: TComponent);
begin
inherited;
FMyHintData.FontSize:=25;
end;
procedure TMyButton.CMHintShow(var AMessage: TCMHintShow);
begin
inherited;
AMessage.HintInfo.HintData:=#FMyHintData;
AMessage.HintInfo.HintWindowClass:=TMyHintWindow;
AMessage.HintInfo.HintStr:='My custom hint';
end;
end.
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnShowHint:=AppOnShowHint;
end;
procedure TForm1.AppOnShowHint(var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo);
begin
{Use HintInfo (type:THintInfo) to specify some property of hint-window}
{For example: set hint-window width to the width of longest word in the hint-text}
HintInfo.HintMaxWidth:=1;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
{Set HintFont at runtime}
Screen.HintFont.Size:=strtoint(Edit1.Text);
{It's necessary to recreate the Application.FHintWindow private variable, so:}
Application.ShowHint:=False;
Application.ShowHint:=True;
end;

Only last added Delphi component does action

I created my own component (it like a button which can move) in Delphi, installed it. Then I create a new project and new from there and added few new my component elements. But only last one added is able to move! Others not. Why does it happen? How could I fix it?
Here's the component code:
unit ModifiedButton;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
var Timer: TTimer;
type
TSpeed = (Slow,Normal,Fast);
TModifiedButton = class(TButton)
private
{ Private declarations }
FCount:integer;
Velocity:integer;
FSpeed:TSpeed;
protected
{ Protected declarations }
procedure Click;override;
procedure Move(Vel:Integer);
procedure OnTimer(Sender: TObject);
public
{ Public declarations }
procedure ShowCount;
published
{ Published declarations }
property Count:integer read FCount write FCount;
property Speed: TSpeed read FSpeed write FSpeed;
constructor Create(aowner:Tcomponent); override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyComponents', [TModifiedButton]);
end;
{ TModifiedButton }
procedure TModifiedButton.Click;
begin
inherited Click;
FCount:=FCount+1;
if (Speed = Slow) then
Velocity:=2;
if (Speed = Normal) then
Velocity:=10;
if (Speed = Fast) then
Velocity:= 20;
Timer.Enabled:=True;
end;
constructor TModifiedButton.Create(aowner: Tcomponent);
begin
inherited Create(aowner);
Timer:=TTimer.Create(self);
Timer.Enabled:=false;
Timer.OnTimer:=OnTimer;
Timer.Interval:=10;
end;
procedure TModifiedButton.Move(Vel: Integer);
begin
Left:=Left + Vel;
end;
procedure TModifiedButton.OnTimer(Sender: TObject);
begin
Move(Velocity);
end;
procedure TModifiedButton.ShowCount;
begin
ShowMessage('You cliked '+ caption+' for '+inttostr(FCount)+' times');
end;
end
.
Since the Timer is a global variable, each new button you create will overwrite the OnTimer event handler of the previous button. Solution, make the Timer a member of your TModifiedButton class:
unit ModifiedButton;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
type
TSpeed = (Slow,Normal,Fast);
TModifiedButton = class(TButton)
private
{ Private declarations }
FCount:integer;
Velocity:integer;
FSpeed:TSpeed;
Timer: TTimer;
protected
{ Protected declarations }
procedure Click;override;
procedure Move(Vel:Integer);
procedure OnTimer(Sender: TObject);
public
{ Public declarations }
procedure ShowCount;
published
{ Published declarations }
property Count:integer read FCount write FCount;
property Speed: TSpeed read FSpeed write FSpeed;
constructor Create(aowner:Tcomponent); override;
destructor Destroy; override;
end;
procedure Register;
implementation
{ TModifiedButton }
procedure TModifiedButton.Click;
begin
inherited Click;
FCount:=FCount+1;
Case Speed of
Slow : Velocity:=2;
Normal : Velocity:=10;
Fast : Velocity:= 20;
end;
Timer.Enabled:=True;
end;
procedure TModifiedButton.Move(Vel: Integer);
begin
Left:=Left + Vel;
end;
procedure TModifiedButton.OnTimer(Sender: TObject);
begin
Move(Velocity);
end;
procedure TModifiedButton.ShowCount;
begin
ShowMessage(Format('You clicked %s for %d times', [Caption, FCount]));
end;
constructor TModifiedButton.Create(AOwner: Tcomponent);
begin
inherited Create(AOwner);
Timer := TTimer.Create(self);
Timer.Enabled:=false;
Timer.OnTimer:=OnTimer;
Timer.Interval:=10;
end;
destructor Destroy;
begin
Timer.Enabled := False;
Timer.Free;
end;
procedure Register;
begin
RegisterComponents('MyComponents', [TModifiedButton]);
end;
end.

Resources