I create a component and add a Tbutton to it.
now I want to create OnClick event for my Component that execute when user click my component's Button at run time
How can I do that?
#LU_RD's answer is probably what you are looking for.
I wrote a smaller example that should be similar to what you are doing.
interface
TMyComponent = class(TCustomControl)
private
embeddedButton: TButton;
fOnButtonClick: TNotifyEvent;
procedure EmbeddedButtonClick(Sender: TObject);
protected
procedure DoEmbeddedButtonClick; virtual;
public
constructor Create(AOwner: TComponent); override;
published
property OnButtonClick: TNotifyEvent read fOnButtonClick write fOnButtonClick;
end;
implementation
// Attach embedded button event handler onto embedded button
constructor TMyComponent.Create(AOwner: TComponent);
begin
// .. other code
embeddedButton.OnClick := EmbeddedButtonClick;
// .. more code
end;
// EmbeddedButtonClick fires internal overridable event handler;
procedure TMyComponent.EmbeddedButtonClick(Sender: TObject);
begin
// If you want to preserve the Sender, extend this method
// with a sender argument.
DoEmbeddedButtonClick;
end;
procedure TMyComponent.DoEmbeddedButtonClick;
begin
// Optionally if you need to do additional internal work
// when the button is clicked, you can do it here.
// Check if event handler has been assigned
if Assigned(fOnButtonClick) then
begin
// Fire user-assigned event handler
fOnButtonClick(Self);
end;
end;
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I'm creating custom components. This custom component is inherited from TControl which have a TAlign property. I want to execute a method from my descending classes whenever TAlign is set a value
This is the draft of the descending class:
TWidget = class(TControl)
public
procedure Resize;
end;
When I write a value to TAlign (From TControl class), another method, from TWidget is called. Like so:
var
t: TWidget;
begin
t := TWidget.Create(Self);
t.Align := alRight; // When this is executed, "Resize" from TWidget should be called
When a control's Align property is changed, it calls the control's SetBounds() and RequestAlign() methods.
SetBounds() is virtual, so a descendant can override it directly. This is also the same method that is called by the control's Left, Top, Width, and Height property setters. After applying the new bounds, SetBounds() issues a WM_WINDOWPOSCHANGED message to the control (which can be caught by override'ing the control's virtual WndProc() method, or by subclassing its WindowProc property, or by declaring a message handler), as well as calls the RequestAlign() and Resize() methods.
RequestAlign() calls Parent.AlignControl(), which does a lot of work, but it basically boils down to simply repositioning each of the Parent's visible child controls relative to each other based on their respective Align and Anchors values. Those repositions are done by calling SetBounds() on each child control.
Resize() just fires the control's OnResize event handler, if assigned.
So, the best way to have your custom control react to changes in its size or position is to simply override its SetBounds() method, or handle the WM_WINDOWPOSCHANGED message, eg:
type
TWidget = class(TControl)
public
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
end;
procedure TWidget.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
// use Left/Top/Width/Height properties as needed...
end;
Or:
type
TWidget = class(TControl)
protected
procedure WndProc(var Message: TMessage); override;
end;
procedure TWidget.WndProc(var Message: TMessage);
begin
inherited WndProc(Message);
if Message.Msg = WM_WINDOWPOSCHANGED then
begin
// use Left/Top/Width/Height properties as needed...
end;
end;
Or:
type
TWidget = class(TControl)
private
procedure WMWindowPosChanged(var Message: TWMWindowPosChanged); message WM_WINDOWPOSCHANGED;
end;
procedure TWidget.WMWindowPosChanged(var Message: TWMWindowPosChanged);
begin
inherited;
// use Left/Top/Width/Height properties as needed...
end;
so I've created a custom Button based on cxButton . I wish to show a Popupmenu when I click this button . But for some reason the Popupmenu is not Showing up.
I don't even get a error , I have no idea why .
type
TcxGridButton = class(TcxButton)
private
FGridView : TcxGridDBTableView;
FPopup : TPopupMenu;
procedure AutoSize(Sender : TObject);
procedure ClearFilter(Sender : TObject);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner : TComponent); override;
procedure Click; override;
published
property GridView : TcxGridDBTableView read FGridView write FGridView;
end;
And here is the Part where I Create the Popupmenu
constructor TcxGridButton.Create(AOwner: TComponent);
var Item : TMenuItem;
P : TPoint;
begin
inherited;
Text:='Options';
FPopup := TPopupMenu.Create(Self);
Item := TMenuItem.Create(FPopup);
Item.Caption:='Nach Excel exportieren';
Item := TMenuItem.Create(FPopup);
Item.Caption:='Automatische Größenanpassung';
Item.OnClick:=AutoSize;
Item := TMenuItem.Create(FPopup);
Item.Caption:='Filter löschen';
Item.OnClick:=ClearFilter;
end;
Now when I place this Button on the Form it has the Text Options displayed imediately so the Constructor seems to be running ok .
But when I click this button , I get the Click , Self.ToString and Done.
But the Popup menu never Pops up . What is my mistake ?
procedure TcxGridButton.Click;
begin
inherited; // call the inherited Click method.
ShowMessage('CLICK');
if not Assigned(FGridView) then Exit;
ShowMessage(Self.ToString);
FPopup.Popup(0,0);
ShowMessage('DONE');
end;
The answer is pretty simple - you forgot to add the items to your popup menu:
{ after creating each item }
FPopup.Items.Add(Item);
In case you're not bound to TCxButton you can use standard VCL button that provides the functionality you're trying to implement via property Style set to bsSplitButton and property DropDownMenu. Otherwise you can at least study VCL's TCustomButton source code as an inspiration for your own implementation.
I have done a very simply subclass of the TSwitch that will not respond to mouse clicks or even allow setting IsChecked at runtime. I have not created this as a component so its only runtime constructed. It works if I create a TSwitch at runtime but will not work if its my subclassed switch.
TLayoutSwitch = class(TCustomSwitch, ILayoutBaseControl)
The issue appears to be in SendMessage called by TSwitchModel.SetValue. In TMessageSender.SendMessage. I cannot figure out how TSwitchModel is constructed so that the Receiver object is set.
RAD Studio 10 Seattle
TLayoutSwitch = class(TCustomSwitch, ILayoutBaseControl)
private
FGroupID: integer;
procedure SetGroupID(const Value: integer);
function GetIBHeight: Single;
function GetIBWidth: Single;
procedure SetIBHeight(const Value: Single);
procedure SetIBWidth(const Value: Single);
procedure DoSwitchEvent(Sender: TObject);
public
LayoutControlType: TLayoutControlType;
property LFIBGroup_ID: integer read FGroupID write SetGroupID;
property LFIBWidth: Single read GetIBWidth write SetIBWidth;
property LFIBHeight: Single read GetIBHeight write SetIBHeight;
procedure WriteToStream(ms: TStream);
procedure ReadFromStream(ms: TStream; NewWidth: Single = 1; NewHeight: Single = 1);
constructor Create(AOwner: TComponent); override;
end;
Instantiate code
ctrl := TLayoutSwitch.Create(Background);
ctrl.Parent := Background;
ctrl.BringToFront;
(ctrl as ILayoutBaseControl).ReadFromStream(ms, Background.Width/tmpW, Background.Height/tmpH);
Your class name TLayoutSwitch "misguides" FMX to search for a presenter named LayoutSwitch-style which of course doesn't exist in the framework. However, it is possible to change that name to the ordinary Switch-style in the OnPresentationNameChoosing event which is fired directly after the standard name construction.
Declare a TPresenterNameChoosingEvent procedure in your class, for example:
procedure ChoosePresentationName(Sender: TObject; var PresenterName: string);
and assign this to the event in the constructor
constructor TLayoutSwitch.Create(Owner: TComponent);
begin
inherited;
OnPresentationNameChoosing := ChoosePresentationName;
...
end;
Implementation could be as simple as
procedure TLayoutSwitch.ChoosePresentationName(Sender: TObject; var PresenterName: string);
begin
PresenterName := 'Switch-style';
end;
The Switch-style presenter/presentation is the one used by TSwitch. Therefore it now looks and behaves the same.
I have a Delphi XE+ application with 3 forms, 2 of them created dynamically, like so:
form_main is triggering form_equip
form_equip is triggering form_certif
form_main -> form_equip -> form_certif
1'st: Open form_equip
procedure Tform_main.button_equip_addClick(Sender: TObject);
var
form_equip: Tform_equip;
begin
form_equip:= Tform_equip.Create(Self);
form_equip.equip_id:= 0;
form_equip.ShowModal;
FreeAndNil(form_equip);
end;
On form_equip I have a public procedure has_changes
2'nd: Open form_certif
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(Self);
form_certif.index:= 0;
form_certif.ShowModal;
FreeAndNil(form_certif);
end;
Now, when I press OK button from form_certif
procedure Tform_certif.button_okClick(Sender: TObject);
begin
//do something...
form_equip.has_changes; //this public procedure from form_equip is not visible because form was created as local var on form_main
end;
The question is, how can I transmit the sender/parent name to form_certif so can I see the public procedures and variables from form_equip?
A simple way is to declare inside unit_equip as global:
var
form_equip: Tform_equip
but I avoid to do this because form_equip is made to be opened dynamically in multiple windows with different names...
Pass all needed information from form_equip to form_certif. That way form_certif is decoupled from any dependence of form_equip.
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(nil);
try
form_certif.index:= 0;
// Pass all other needed variable values to form_certif
// including callback methods
form_certif.Has_Changes_Method := Self.Has_Changes();
if form_certif.ShowModal = mrOk then
begin
// take care of changes
end;
finally
FreeAndNil(form_certif);
end;
end;
And this is how it would look in the form_certif unit:
type
THas_Changes_Method = procedure of Object;
TForm_Certif = class(TForm)
...
private
FIndex: Integer;
FHasChanges: THas_Changes_Method;
public
property Index: Integer read FIndex write FIndex;
property Has_Changes_Method: THas_Changes_Method read fHasChanges write fHasChanges;
end;
Pass the information as a parameter in the constructor. Declare the constructor like this:
constructor Create(AOwner: TComponent; const ParentName: string);
In the implementation of the constructor, make a note of the name that was passed.
Since you are creating forms in code, you can add custom constructor to the form and pass other form as parameter.
Tform_certif = class(TForm)
...
protected
form_equip: Tform_equip;
public
constructor Create(AOwner: TComponent; Aform_equip: Tform_equip); reintroduce;
end;
constructor Tform_certif.Create(AOwner: TComponent; Aform_equip: Tform_equip);
begin
inherited Create(AOwner);
form_equip := Aform_equip;
end;
So now you can call form_equip.has_changes, because it is a field that you have initialized during construction of your Tform_certif form and it points to particular instance of Tform_equip that created this particular instance of Tform_certif.
procedure Tform_certif.button_okClick(Sender: TObject);
begin
//do something...
// test whether form_equip is assigned to avoid AV by calling methods on nil object
if Assigned(form_equip) then form_equip.has_changes;
end;
And to create your Tform_certif you would use following code
procedure Tform_equip.button_certif_addClick(Sender: TObject);
var
form_certif: Tform_certif;
begin
form_certif:= Tform_certif.Create(Self, Self);
form_certif.index:= 0;
form_certif.ShowModal;
FreeAndNil(form_certif);
end;
There is also variation of above constructor code, where you only need to send one parameter and then test in constructor whether AOwner it is of Tform_equip type and you don't need to change code in button_certif_addClick for using that kind of solution.
Tform_certif = class(TForm)
...
protected
form_equip: Tform_equip;
public
constructor Create(AOwner: TComponent); override;
end;
constructor Tform_certif.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
if AOwner is Tform_equip then form_equip := Tform_equip(AOwner);
end;
I have created a component with TFrame as ancestor with the following code:
type
TCHAdvFrame = class(TFrame)
private
{ Private declarations }
FOnShow : TNotifyEvent;
FOnCreate : TNotifyEvent;
protected
procedure CMShowingChanged(var M: TMessage); message CM_SHOWINGCHANGED;
public
{ Public declarations }
constructor Create(AOwner: TComponent) ; override;
published
property OnShow : TNotifyEvent read FOnShow write FOnShow;
property OnCreate : TNotifyEvent read FOnCreate write FOnCreate;
end;
implementation
{$R *.dfm}
{ TCHAdvFrame }
procedure TCHAdvFrame.CMShowingChanged(var M: TMessage);
begin
inherited;
if Assigned(OnShow) then
begin
ShowMessage('onShow');
OnShow(self);
end;
end;
constructor TCHAdvFrame.Create(AOwner: TComponent);
begin
ShowMessage('OnCreate1');
inherited ;
ShowMessage('OnCreate2');
if Assigned(OnCreate) then
begin
ShowMessage('OnCreate3');
OnCreate(self);
end;
I have registered the new component and did some tests. ShowMessage('OnCreate1'); and ShowMessage('OnCreate2'); are correctly executed but not ShowMessage('OnCreate3');
This prevents to add code during the implementation of a new instance of TCHAdvFrame.
Why is it and how can I solve this ?
A frame is streamed in as part of its ultimate owner's constructor. Typically that will be a form. The form processes the .dfm file. It encounters new objects and creates them. Then it sets the properties of the newly created object. So, the frame's properties are set after its constructor returns.
This is the reason that TFrame does not have an OnCreate event. There is simply no way for the event to be fired because the event by necessity is assigned too late. The VCL designers omitted this event for the very same reason that led you to ask this question. So I do suspect that you likewise should not add this event.
How to solve this? Hard to say for sure unless we had a more detailed description of the problem. Perhaps you could override the frame's Loaded method to good effect. Or perhaps all you need to do is let consumers of your component override the constructor in their derived frames.
Related reading: http://delphi.about.com/od/delphitips2007/qt/tframe_oncreate.htm