I'm trying to use assign on a TPanel configured in the designer but it doesn't work.
var
LPanel : TPanel;
begin
LPanel := TPanel.Create(nil);
LPanel.Assign(Panel1); // Panel1 is a panel made in the form designer
end;
The error message is something like "TPanel cannot be assigned to TPanel." (I have the german version of RAD Studio... The exact error message on german is "TPanel kann nicht zu TPanel zugewiesen werden.")
I designed a TPanel with other components in it using the Form Designer. Now I want to add new TPanel instances to a TLayout which should be the same as the TPanel I want to assign from, including all child controls.
Most VCL and FMX components, including TPanel, DO NOT implement Assign() at all. Typically only utility classes that are used for component properties implement Assign() for use in their property setters.
For what you are attempting, you should use a Frame instead of TPanel. You can design a Frame at design-time, just like a Form or DataModule, and then create instances of it at run-time as needed.
See Embarcadero's documentation for more details:
Frames in FireMonkey
Unfortunately I don't currently have access to Delphi to confirm. But it seems Assigning TPanel is deliberately blocked by the framework.
That said, what you're trying to achieve seems more appropriately handled with TFrame
Once you've created your frame, you should be able to use the following code to create a new instance at run-time.
uses
...
frMyFrame;
...
var
LNewFrame : TFrame;
begin
LNewFrame := TMyFrame.Create(nil); //Are you sure you don't want to assign an owner?
LNewFrame.Parent := Self; //Assuming you want to position the frame directly on the form
//Otherwise you could place it on a simple panel.
//Set attributes for positioning
//Don't forget resource management (see ownership comment)
...
end;
Related
I am developing a Delphi 10.1 VCL application for Windows.
For integer or float input I need a number input field which is connected with a slider. When the user changes the number in the input field the slider position changes accordingly. When the user changes the slider position the number in the number field is updated.
I can solve this by using a TEdit and a TTrackBar and add the necessary update functionality in their OnChange event handlers.
The problem is that I need many of such inputs on different forms. Therefore I would like to create a new component which combines the two controls TEdit and TTrackBar in one component.
Is the creation of a new component the best strategy for the multiple use of such a slider input?
What is the best way to create such a new component?
Is the creation of a new component the best strategy for the multiple
use of such a slider input?
Not necessarily true all the time. (by my standards at least).
What is the best way to create such a new component?
I know three ways to solve your problem.
Way number 1:
create the component using the new component wizard where you create dynamically the TEdit and the TTrackBar sub components in a TGroupBox descendant.
the following is how I would do that.
unit Combindedittrack;
interface
uses
System.SysUtils,
System.Classes,
Vcl.Controls,
Vcl.comctrls,
Vcl.StdCtrls;
type
TCombindEditTrack = class(TGroupBox)
private
{ Private declarations }
FEdit: TEdit;
FTrackBar: TTrackBar;
procedure EditOnChangeProc(Sender: TObject);
procedure TrackBaroOnChangeProc(Sender: TObject);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TCombindEditTrack]);
end;
constructor TCombindEditTrack.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetBounds(0, 0, 250, 50);
FEdit := TEdit.Create(Self);
with FEdit do
begin
Text := ''; //<-- you control the appearence here
Top := 10;
Left := 10;
Height := 27;
Width := 50;
Parent := Self;
OnChange := EditOnChangeProc; // your Onchange event handler for the Tedit
end;
FTrackBar := TTrackBar.Create(self);
with FTrackBar do
begin
Top := 10; //<-- you control the appearence here
Left := 60;
Height := 30;
Width := 50;
Parent := self;
Onchange := TrackBaroOnChangeProc; // your Onchange event handler for the Ttrackbar
end;
end;
destructor TCombindEditTrack.Destroy;
begin
FTrackBar.Free;
FEdit.Free;
inherited;
end;
procedure TCombindEditTrack.TrackBaroOnChangeProc(Sender: TObject);
begin
// <-- track bar onchange handling here.
end;
procedure TCombindEditTrack.EditOnChangeProc(Sender: TObject);
begin
// <-- edit onchange handling here.
end;
end.
Way number 2:
Use frames like this (I'm on delphi 10 seattle).
File-->New-->Other-->(search for frames on delphi files).
Now add the edit and the track bar and set their Onchange events.
Save the unit.
on the tool palette (the standard component section) click on the frame component.
choose the frame you just created.
You will have a replica of the frame each time you use it.
Way number 3:
Use component template like this (again I'm on delphi 10 seattle)
Select your already created and modified tedit and ttrackbar.
On the toolbar's "component" click "create component template".
Name your template and press OK.
Select the Template palette and then your template.
Now notice that even your code (events) are added as well to your project.
Finally
With the level I have on delphi and the IDE I'm really unable to give you a clear answer to which is the best way but nevertheless I have shared all what I know that could help you.
edit: Since a lot of comments are insisting that the answer should state which is the best way to do this. this is the best way based on the following.
let's put some of the key point that should be accounted for when choosing
1. Ease of modifying the combined control(s) if you wish so (by my experience you will).
2. time needed to complete this task (it means the time it will take
you to fully complete the task with minimum debugging and coding).
3. general source code readability.
4. usefulness for the future of your projects.
Now lets start criticizing the three methods based on the those criteria.
Way number 1:
C1(criteria number 1): Just modify the the source implementation of the component and each replica/use will have the same effects and properties. However this is not the case for way number 3.
C2: It depends on your knowledge of component writing but for this component it took me 5 min to create it and I'm only a beginner in delphi. For the debugging if something went wrong and the problem is in the component implementation than you just need to fix once (see C1)
C3: their is no implementation in your form(s) source code for your component just add it to your form and every thing is hidden (for example add a tedit and go to see the implementation in your forms source).
C4: You are creating a component after all this will open the door for you to create your own set of components like the Flatstyle or Indy open source components. so next time you need some thing like this you just drop it in your form designer and you are done.
Way number 2: frames
C1: It is like way number 1 because you are creating a component but it is visually this time. modifying the source frame will change the effects and properties of the replicas, also you can add extra handling to your replicas.
the event handler of the replica's Onchange event is like this
procedure TForm1.Frame2Edit1Change(Sender: TObject);
begin
Frame2.Edit1Change(Sender); //<-- this is the original onchange event you setup at the beginning
//<-- you can extra handling here if you want one of the replicas to behave differently than the original
end;
C2: same time and maybe faster than way number 1.
C3: over all, it has the same out come as way number 1.
C4: unlike way number 1 you can not use frames created in project A in project B. So your coding and debugging will stay in project A.
Way number 3: component template.
C1: you are not creating a component you are creating a repleca/macro of the exact steps you did in your last project. changing one will not change the others they are separated.
C2: same time and maybe faster than way number 1.
C3: each time you add a template to your form the events code will be added (not a good view if it is a long Onchange code).
C4: You can use templates created in project A in project B. However what you wrote in project A will be in project B (see c1) even the references of variables that don't exist in project B (this can be hard to debug and misleading, considering the period of time between each use of the template).
Conclusion: each of the ways presented will consume time to code and debug and all of them will do the task, How ever for the sake of simplicity and the reuse with minimum risks Way number 1 is the safe choice here because it will give you the chance to update and upgrade safely. also debug faster.
the good thing also about way number 1 is that after a while when you will forget the implementation and how things are working internally. The only thing that should stay in mind is the purpose of the component because it will become one of the various component you use (you don't know how Tedit is implemented and you don't need to but yet you use it in every single project you create).
based on the criteria given Way number 1 is the best.
Maybe using a container control that contains both controls is a simpler alternative. I am using ccpack for this.
https://sourceforge.net/projects/ccpack/
Custom Containers Pack (CCPack) is an integrated tool and component mini-
library to produce and maintain composite controls (or simply “composites”)
and other containers (forms, data modules and frames). The process of
building composite components looks like ActiveForm and Frame creating, but
the result is the native VCL component. You can create new composites just
as usual forms.
You can create a Frame and then register that Frame as a component. The end result is very similar to creating a code only component where the sub components are created in the constructor (Nasreddine's number 1 option). However this method allows you to visually design the component and use the object inspector to create your event handlers.
Here is a Stack Overflow question that shows how to register the frame:
How to Improve the Use of Delphi Frames
I saw the introduction of the TActivityIndicator in Delphi 10 Seattle and thought cool I could make use of this somewhere. I wanted to use this to show that some dynamically created sections of my form were still loading the data before populating the form. So I thought I'd do this before I start loading my data in FormShow, where self is the form.
indicator := TActivityIndicator.Create(self);
indicator.IndicatorSize := TActivityIndicatorSize.aisLarge;
Sadly when I try to create them dynamically and set then TActivityIndicator.IndicatorSize I get an exception ... EInvalidOperation with message 'Control '<name>' has no parent window' Which stepping through the VCL takes me to Vcl.Controls TWinControl.CreateWnd specifically
if (WndParent = 0) and (Style and WS_CHILD <> 0) then
if (Owner <> nil) and (csReading in Owner.ComponentState) and
(Owner is TWinControl) then
WndParent := TWinControl(Owner).Handle
else
raise EInvalidOperation.CreateFmt(SParentRequired, [Name]);
I've checked Owner is the form which is of course a TWinControl but (csReading in Owner.ComponentState) returns false. Stepping through Owner.ComponentState = [] on FormCreate and [csFreeNotification] on FormShow.
I've found that if you try to change the IndicatorSize of a TActivityIndicator that was created at design time then it works perfectly. So what am I missing here or is it not possible to create TActivityIndicators at runtime?
The error message is pretty clear. You need to assign a Parent on which the activity indicator will draw itself. The Owner is the component responsible for freeing the control when the Owner is destroyed; the Parent is the control on which the control will be drawn (parented) for display.
The solution is to assign that parent in code:
Indicator := TActivityIndicator.Create(Self);
Indicator.Parent := Self; // <-- here
// Set any other properties here
The same issue is common on all visual controls (such as TEdit, TLabel, TMemo, and so forth), which all need to have a Parent assigned in order to have a place to paint themselves. And in some cases, a Parent is required in order for various properties in the child control to function correctly when they depend on the child having an HWND window, which requires a Parent window, and so on.
If I understand your intent, I think you're going to be disappointed, however. TActivityIndicator is pretty static; it's not threaded, which means it will cease updating if your form is busy and doesn't process timer messages (which it uses internally).
The declaration of my component is:
MyComponentX = class(TActiveXComponent, IspdInterfaceX)
TActivexComponent:
TActiveXComponent = class(TActiveXControl, IOleControl)
When I open a new DataModule in any Delphi version, the component disappears from the Component Palette.
I try to change the ClassGroup in Delphi XE2, but this don't works.
Data modules can only host non-visual controls, and TActiveXControl is not a non-visual control.
You ask what is the definition of non-visual control. According to this article, the definition is that if the component is derived from TComponent and not derived from TControl, then it is a non-visual control.
That said, the inheritance hierarchy for TActiveXControl is: TObject, TComObject, TTypedComObject, TAutoObject, TActiveXControl. So I am at something of a loss at to why it ever appears on your component palette since it is not derived from TComponent. It would be interesting to know how you registered it. All the same, it's not a non-visual component in the meaning of the act.
In this case, you can create a class wrapper to use your ActiveX in this data module.
TWrapper = class(TComponent)
private
FYourActiveX: TYourActiveX;
public
procedure Method;
end;
procedure TWrapper.Method;
begin
FYourActiveX.Method;
end;
In Lazarus I'm trying this:
TabSaveButton := TButton.Create(nil);
with TabSaveButton do
begin
Parent:=NewTab;
Width:=75;
Height:= 25;
Top:=530;
Left:=715;
Caption:='Save';
end;
And it works. I.e., I get the button and it's clickable, and it is the child of a dynamically created tab sheet.
But the following does not show the button, nor errors:
TabSaveButton := TButton.Create(NewTab);
with TabSaveButton do
begin
Width:=75;
Height:= 25;
Top:=530;
Left:=715;
Caption:='Save';
end;
Why does the second method not work?
Is this the same effect on both Lazarus and Delphi?
The argument of Create sets the owner of the control. The owner is the component responsible for freeing the component in question. For instance, if you free a component, then all components owned by it are also freed. The parent is a completely different thing. It is the window (control) hosting the control in question.
There is no difference between Delphi and Lazarus here.
is it posible to create runtime frame and add existing panels like setting the parent of panel to the frame? and when it added, dulplicate the frame and use it?
like:
f:= Tframe. create(..)
...
panel3.parent = f; //where panel3 has many controls.
then duplicate the f? was it posible? how? or any other suggerstion?
e
I don't think you would solve this by duplicating. What you need is a function like this:
function CreateFrameAndHostPanel(Owner: TComponent; Parent: TWinControl; Panel: TPanel): TFrame;
begin
Result := TFrame.Create(Owner);
Try
Result.Parent := Parent;
Panel.Parent := Result;
Except
FreeAndNil(Result);
raise;
End;
end;
You need to remember that all controls have a parent and an owner. Owners could be nil but then you need to free those controls through code, so most controls are owned by some other component.
Thus, if the owner gets destroyed, the panel would be destroyed too. And if the panel was created in design-time then it's owned by the form that it's on!
Destroying that form would destroy the panel!
But if you create the panels in runtime and set Application as owner instead of a form, they could be moved over multiple forms and frames.
But is it a good design pattern? I don't know what you're trying to do but it's likely a bad idea!
In general, it would be more practical to design the whole frame with panels in design-time. Then add some code that would allow the frame to be created by copying data from another panel or control. That would be a better design pattern...
You must create the new frame (FRAME2) with the same code that you have used to create the first (FRAME1); And later, you must create all the component included (created on runtime) inside FRAME1 on FRAME2.
For to this, use:
for i := 0 to (FRAME1.ComponentCount - 1) do
...
cmp := TComponent(FRAME1.Component[i]);
... create cmp on Frame2
You can try a second alternative; Save the FRAME1 using a TMemoryStream (SaveComponent) and later create the new Frame and retrieve the saved information on Stream (I don't have test this option).
Regards.