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;
Related
When you are in a TFrame and you do TThread.ForceQueue(nil, MyFrame.OneProc, 200) how can you check in the MyFrame.OneProc procedure that MyFrame was not destroyed in the mean time?
In other words, what mechanism can be used in such common scenario?
You can use guardian interface that will be fully functioning instance you can use to check whether guarded object is released in the meantime.
type
IGuardian = interface
function GetIsDismantled: Boolean;
procedure Dismantle;
property IsDismantled: Boolean read GetIsDismantled;
end;
TGuardian = class(TInterfacedObject, IGuardian)
private
FIsDismantled: Boolean;
function GetIsDismantled: Boolean;
public
procedure Dismantle;
property IsDismantled: Boolean read GetIsDismantled;
end;
procedure TGuardian.Dismantle;
begin
FIsDismantled := True;
end;
function TGuardian.GetIsDismantled: Boolean;
begin
Result := FIsDismantled;
end;
And then you need to add guardian field in your frame
type
TMyFrame = class(TFrame)
private
FGuardian: IGuardian;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Guardian: IGuardian read FGuardian;
end;
constructor TMyFrame.Create(AOwner: TComponent);
begin
inherited;
FGuardian := TGuardian.Create;
end;
destructor TMyFrame.Destroy;
begin
// prevent AV when destroying partially
// constructed instance
if Assigned(FGuardian) then
FGuardian.Dismantle;
inherited;
end;
But you cannot directly queue frame's MyProc, you need to use anonymous methods and capture that guardian variable so its life will be extended beyond the lifetime of the frame.
Reference counting will keep the guardian object instance alive even after MyFrame is released and its memory will be automatically managed.
It is important to use locally declared Guardian interface variable and capture that variable instead of directly capturing MyFrame.Guardian field because that field address will no longer be valid after MyFrame is released.
procedure CallMyProc;
var
Guardian: IGuardian;
begin
Guardian := MyFrame.Guardian;
TThread.ForceQueue(nil,
procedure
begin
if Guardian.IsDismantled then
Exit;
MyFrame.OneProc;
end, 200);
end;
Note: Even if you use TThread.Queue without a delay, it is possible that frame will be released before queued procedure runs. So you need to protect your frame is such scenarios, too.
You can't call a method on an object that has been destroyed. The preferred solution is to simply remove the method from the queue if it hasn't been called yet, before destroying the object. TThread has a RemoveQueuedEvents() method for exactly that purpose.
For example:
TThread.ForceQueue(nil, MyFrame.OneProc, 200);
...
TThread.RemoveQueuedEvents(MyFrame.OneProc);
MyFrame.Free;
Alternatively, use the frame's destructor instead:
TThread.ForceQueue(nil, MyFrame.OneProc, 200);
...
destructor TMyFrame.Destroy;
begin
TThread.RemoveQueuedEvents(OneProc);
inherited;
end;
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;
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
I am creating a custom control derived from TCustomControl, for example:
type
TMyCustomControl = class(TCustomControl)
private
FText: string;
procedure SetText(const Value: string);
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Text: string read FText write SetText;
end;
Note, the above is incomplete for purpose of the example to keep it short and simple.
Anyway, in my control I have a Paint event which displays text (from FText field) using Canvas.TextOut.
When my component is added to the Delphi Form Designer (before any user changes can be made to the component) I want the TextOut to display the name of the Component - TButton, TCheckBox, TPanel etc are examples of this with their caption property.
If I try to assign the name of my Component to FText in the constructor it returns empty, eg '';
constructor TMyCustomControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FText := Name; //< empty string
ShowMessage(Name); //< empty message box too
end;
If I change FText := Name to FText := 'Name'; it does output the text to my Component so I do know it is not a problem within the actual code, but obviously this outputs 'Name' and not the actual Component name like MyCustomControl1, MyCustomControl2 etc.
So my question is, how can you get the name of your Component from its constructor event?
The Name property has not been assigned yet when the constructor is running. At design-time, the IDE assigns a value to the Name property after the component has been dropped onto the Designer, after the control's constructor has exited. At runtime, the Name property is set by the DFM streaming system instead, which is also invoked after the constructor has exited.
Either way, the TControl.SetName() property setter validates the new value, and then sets the new value to the control's Text property to match if the current Text value matches the old Name value and the control's ControlStyle property includes the csSetCaption flag (which it does by default). When the Text property changes for any reason, the control automatically sends itself a CM_TEXTCHANGED notification. You can have your control catch that message and call Invalidate() on itself to trigger a new repaint. Inside of your Paint() handler, simply draw the current Name as-is, whatever value it happens to be. If it is blank, so be it. Don't try to force the Name, let the VCL handle it for you normally.
I believe the proper way to handle this is to use the inherited Text or Caption property of TCustomControl, and to make sure that the csSetCaption ControlStyle is set.
To apply the name you may override TComponent.Loaded method.
But i don't think You should copy Name to Text. Those are semantically separate properties and adding unexpected binding to them would hurt you some day.
Rather WMPaint method should check if the Text is empty and then render Name then, but the very property of Text should not be changed.
procedure TMyComponent.WMPaint; message WM_Paint; var InternalCaption: string;
begin
....
InternalCaption := Self.Text;
If InternalCaption = '' then InternalCaption := Self.Name;
If InternalCaption = '' then InternalCaption := Self.ClassName;
....
Self.Canvas.OutText(InternalCaption);
If anything - you should keep properties separated just for the simple reason that Name := 'AAA'; Name := 'BBB'; should not make Text and name out of sync. And with your approach 1st statement would settle the Text and the second would make old Name still displayed after the actual name changed.
Un easy way is to override the method SetName:
TMyCaptionString = type of WideString;
TMyLabel = class(TCustomControl)
private
FCaption: TMyCaptionString;
FCaptionAsName: Boolean;
procedure SetCaption(Value: TMyCaptionString);
protected
procedure SetName(const NewName: TComponentName); override;
public
constructor Create(AOwner: TComponent); override;
property Caption: TMyCaptionString read FCaption write SetCaption;
end;
implementation
constructor TMyLabel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := ControlStyle + [csOpaque, csReplicatable,csSetCaption];
FCaptionAsName := (csDesigning in ComponentState) and not (csReadingState in ControlState);
...
end;
procedure TMyLabel.SetName(const NewName: TComponentName);
begin
if FCaptionAsName then
begin
FCaptionAsName := FCaption = Name;
FCaption := NewName;
invalidate;
end;
inherited SetName(NewName);
end;
procedure TMyLabel.SetCaption(Value: TMyCaptionString);
begin
if FCaption <> Value then
begin
FCaption := Value;
Invalidate;
FCaptionAsName := False;
end;
end;
I needed my own variable for the Caption poreprty, because I want to use widestring instead unicode and to write a custom Property editor. Sorry that i'm writing in old topic, but i hope this will helpfull.
I've used frames in Delphi for years, and they are one of the most powerful features of the VCL, but standard use of them seems to have some risk such as:
It's easy to accidentally move or edit the frame sub-components on a frame's host form without realising that you are 'tweaking' with the frame - I know this does not affect the original frame code, but it's generally not what you would want.
When working with the frame you are still exposed to its sub-components for visual editing, even when that frame is years old and should not be touched.
So I got to thinking....
Is there a way of 'grouping' components such that their positions are 'locked'? This would be useful for finished forms as well as frames. Often other developers return code to me where only the form bounds have changed and even they did not intend any change.
Is there any way of turning a frame and its components into a single Delphi component? If so, the frame internals would be completely hidden and its useability would increase further.
I'm interested in any thoughts...
Brian.
Registering your frames as a component solves both 1. and 2.:
the components on the frame are locked when you put that frame control on a form or other frame
you will get a component (actually: control) that you can design visually
But: there are a few catches (which can be solved, see article link), of which the most important is this one:
When you put components on your frame, and later drop that frame as a component on a Delphi form or frame, the components are visible in the Structure Pane.
The problem is that because they are visible in the structure pane, you can delete them, causing access violations.
The trick to solve this to not forget the 'sprig'.
I learned that valuable lesson from Ray Konopka during DelphiLive 2009.
Since the lesson is so valuable, I wrote a blog post on it that describes it in detail.
The essential portion is this little piece of code (more details in the blog post):
procedure RegisterFramesAsComponents(const Page: string; const FrameClasses: array of TFrameClass);
var
FrameClass: TFrameClass;
begin
for FrameClass in FrameClasses do
begin
RegisterComponents(Page, [FrameClass]);
RegisterSprigType(FrameClass, TComponentSprig);
end;
end;
Hope this helps.
--jeroen
Yes, just register them as components. :-)
Design your frame normally and after this register it. Also be sure to not have unwanted dependencies on different units since these are linked when your 'component' is used. Also you can add published properties in order to use them in the Object Inspector later. See for example the following code generated by the IDE (see also my comments):
unit myUnit;
uses
...
type
TmyComp = class(TFrame) //set your frame name to be the name your component
ToolBar1: TToolBar; //different components added in the form designer
aliMain: TActionList;
...
published //this section is added by hand
property DataSource: TDataSource read FDataSource write SetDataSource; //some published properties added just for exemplification
property DefFields: string read FDefFields write SetDefFields;
...
end;
procedure Register; //added by hand
implementation
{$R *.DFM}
procedure Register;
begin
RegisterComponents('MyFrames', [TmyComp]); //register the frame in the desired component category
end;
Compile the above in a package of your choice, install it and check you component palette. :-)
HTH
I'm almost always creating frame instances in code. This is easy and worked well for me so far.
Just for increasing contribution, note that if you go to Structure window and right-click on the TFrame name that you chose, and click on the Add to Palete menu option.
This will make a component out of your Frame and you don't need to create any Register procedure. ;-)
I also ran into that problem when trying to use frames as components. There are various possibilities to fix the obvious issues, but they all undermine the principle of information hiding (all the frame's subcomponents are exposed as published properties, which means everyone can access them).
I solved it by implementing a generic "frame control" component:
unit RttiBrow.Cbde.FrameControl;
interface
uses
Classes, Controls, Forms, Messages, ExtCtrls;
type
TFrameClass = class of TFrame;
TComponentFrame = class (TFrame)
private
function GetClientHeight: Integer;
function GetClientWidth: Integer;
procedure SetClientHeight(const Value: Integer);
procedure SetClientWidth(const Value: Integer);
function GetOldCreateOrder: Boolean;
procedure SetOldCreateOrder(const Value: Boolean);
function GetPixelsPerInch: Integer;
procedure SetPixelsPerInch(const Value: Integer);
function GetTextHeight: Integer;
procedure SetTextHeight(const Value: Integer);
published
{ workarounds for IDE bug }
property ClientWidth: Integer read GetClientWidth write SetClientWidth stored False;
property ClientHeight: Integer read GetClientHeight write SetClientHeight stored False;
property OldCreateOrder: Boolean read GetOldCreateOrder write SetOldCreateOrder stored False;
property PixelsPerInch: Integer read GetPixelsPerInch write SetPixelsPerInch stored False;
property TextHeight: Integer read GetTextHeight write SetTextHeight stored False;
end;
TComponentFrame<TFrameControl: class { TControl }> = class (TComponentFrame)
private
function GetController: TFrameControl; inline;
protected
property Controller: TFrameControl read GetController;
public
constructor Create (AOwner: TComponent); override;
end;
TFrameControl<T: TFrame> = class (TWinControl)
private
FFrame: T;
function PlainFrame: TFrame;
protected
procedure CreateParams (var Params: TCreateParams); override;
property Frame: T read FFrame;
public
constructor Create (AOwner: TComponent); override;
property DockManager;
published
property Align;
property Anchors;
property BiDiMode;
property Color;
property Constraints;
property Ctl3D;
property UseDockManager default True;
property DockSite;
property DoubleBuffered;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property Font;
property ParentBiDiMode;
property ParentBackground;
property ParentColor;
property ParentCtl3D;
property ParentDoubleBuffered;
property ParentFont;
property ParentShowHint;
property ShowHint;
property TabOrder;
property TabStop;
property Touch;
property Visible;
property OnAlignInsertBefore;
property OnAlignPosition;
property OnCanResize;
property OnConstrainedResize;
property OnDockDrop;
property OnDockOver;
property OnDragDrop;
property OnDragOver;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnGesture;
property OnGetSiteInfo;
property OnMouseActivate;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnResize;
property OnStartDock;
property OnStartDrag;
property OnUnDock;
end;
implementation
uses
Windows;
{ TFrameControl<T> }
constructor TFrameControl<T>.Create(AOwner: TComponent);
begin
inherited;
FFrame := T (TFrameClass (T).Create (Self));
PlainFrame.Parent := Self;
PlainFrame.Align := alClient;
end;
procedure TFrameControl<T>.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.Style := Params.Style or WS_CLIPCHILDREN;
Params.ExStyle := Params.ExStyle or WS_EX_CONTROLPARENT;
end;
function TFrameControl<T>.PlainFrame: TFrame;
begin
Result := FFrame; // buggy compiler workaround
end;
{ TComponentFrame }
function TComponentFrame.GetOldCreateOrder: Boolean;
begin
Result := False;
end;
function TComponentFrame.GetPixelsPerInch: Integer;
begin
Result := 0;
end;
function TComponentFrame.GetTextHeight: Integer;
begin
Result := 0;
end;
procedure TComponentFrame.SetClientHeight(const Value: Integer);
begin
Height := Value;
end;
procedure TComponentFrame.SetClientWidth(const Value: Integer);
begin
Width := Value;
end;
procedure TComponentFrame.SetOldCreateOrder(const Value: Boolean);
begin
end;
procedure TComponentFrame.SetPixelsPerInch(const Value: Integer);
begin
end;
procedure TComponentFrame.SetTextHeight(const Value: Integer);
begin
end;
function TComponentFrame.GetClientHeight: Integer;
begin
Result := Height;
end;
function TComponentFrame.GetClientWidth: Integer;
begin
Result := Width;
end;
{ TComponentFrame<TFrameControl> }
constructor TComponentFrame<TFrameControl>.Create(AOwner: TComponent);
begin
inherited;
Assert (AOwner <> nil);
Assert (AOwner.InheritsFrom (TFrameControl));
end;
function TComponentFrame<TFrameControl>.GetController: TFrameControl;
begin
Result := TFrameControl (Owner);
end;
end.
With this class, adding a frame as a component becomes a two-stage process:
// frame unit
type
TFilteredList = class;
TFrmFilteredList = class (TComponentFrame<TFilteredList>)
// lots of published sub-components and event methods like this one:
procedure BtnFooClick(Sender: TObject);
end;
TFilteredList = class (TFrameControl<TFrmFilteredList>)
private
procedure Foo;
public
// the component's public interface
published
// the component's published properties
end;
procedure Register;
...
procedure Register;
begin
RegisterComponents ('CBDE Components', [TFilteredList]);
end;
procedure TFrmFilteredList.BtnFooClick(Sender: TObject);
begin
Controller.Foo;
end;
procedure TFilteredList.Foo;
begin
end;
...
When using this approach, the user of your component won't see your sub-components.