What's so special with Frames that when you create them run-time it gets assigned a name automatically ? This causes a problem when you use them in a loop i.e. :
for i := 0 to 3 do
TMyFrame.Create(self); //Error on 2nd pass
On first pass, the frame is explicityl named 'MyFrame'.
On second pass, it will try to name it again 'MyFrame' which
obivously will cause an exception as there is already a
component with such name.
The other components or forms are happy to be created without
me assigning them a name. What gives ?
This is on Delphi 2006 btw.
Cheers
If you are using TFrame to create your Frame, then it wont raise that error.
i.e., If your code looks like this, you wont have any problems
for i := 0 to 3 do
Frame1 := TFrame.Create(self);
because here the Name property is unassigned. If you want you can check by using the statement ShowMessage(Frame1.Name);
But if you use TMyFrame i.e., the frame which you derived from TFrame, then it will giving the problem.
I think the reason is, Delphi was explicitly assigning the same Name every time you create the TMyName.
you're trying to create the same frame 4 times, each time you create the frame it will have the same name, one way to avoid is
...
var
i: Integer;
lFrame: TFrame;
begin
for i := 0 to 3 do begin
lFrame := TFrame.Create(Self);
// assign a unique name to the frame
lFrame.Name := Format('MyFrame%d', [i]);
// set the parent, align, etc...
end;
end;
Co-incidentally I just bumped into this myself (again).
If you take the name out of your frame component then Delphi moans that "Root Component must have a name" - I guess it because somewhere Delphi is calling RegisterClass(RootComponentName) and then to create it (or a descendent) it's calling Findclass(RootComponentName) or similar. As you can do this yourself to create components that you don't actually know the classname of a design time, why would Delphi not do the same?
Whereas with a TButton etc. it is already a registered Class.
Related
I'm trying to create a dynamic array of TLabels, then insert already existing TLabels created in the IDE into it so I can then use the array in the code.
My purpose is to use this method for several similar processes.
I would like to make it by using loops.
I saw this thread and it was very helpful to understand about creating arrays of TLabels and populating them with TLabels for the desired purposes, but I couldnt find the particular solution for the case when the labels are already created.
Use variables for object name in Delphi
Basically, what I'm trying to automate is this:
var
LabelArray : array of TLabel;
SetLength(LabelArray, 17);
LabelArray[0] := M2;
LabelArray[1] := M3;
.
.
LabelArray[16] := M18
M2 to M18 are 17 labels that are the TLabels already created and positioned on the Form.
You can use the form's FindComponent method to add the labels to your array.
The code below depends on your having declared LabelArray as a private variable at the form level, so that it exists and is visible when the form is created and the code executes. It also hard-codes in 17 as the number of labels, so if you delete or rename a label you'll have an empty spot in the array - I did not include any error checking to make sure a label is found before putting it into the array and the element could end up nil if a label is missing.
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(LabelArray, 17);
{
I use Low() and High() here to avoid having multiple places where
you would have to change the code if you end up adding or removing
labels in the future.
Also note that the index into the array starts at 0, so the code that
calls Format() adjusts that index by 2 to start at M2 instead of M0.
}
for i := Low(LabelArray) to High(LabelArray) do
LabelArray[i] := TLabel(FindComponent(Format('M%d', [i + 2])));
end;
If your labels cannot be found using a common name scheme, but you simply want to get all labels on the same panel / group box etc. into the array, you can use the Controls[] property of the parent control:
SetLength(LabelArray, ParentCtrl.ControlCount);
cnt := 0;
for i := 0 to ParentCtrl.ControlCount - 1 do begin
if ParentCtrl.Controls[i] is TLabel then begin
LabelArray[cnt] := TLabel(ParentCtrl.Controls[i]);
Inc(cnt);
end;
end;
SetLength(LabelArray, cnt);
ParentCtrl is the panel / groupbox or even the form itself that is the common parent of all the labels you are interested in.
If you simply want all labels on a form, even if they are located on other controls, you can use the Components[] property of the form in the same way.
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.
I'm was trying to write a dll library in Delphi wih a function that creates an instance of a TFrame descendant and returns it. But when I imported this function in an application, every time I called it I would get an exception like "the 'xxx' control has no parent window". I'm not 100% sure, but the exception appeared in the constructor of that class when any of GUI controls was accessed.
Could you please tell me what the reason of that behaviour is? Should I just use TForm descendants instead or is there a better solution?
Thank you!
About the error
That error message is raised from the Controls.pas unit, from the TWinControl.CreateWnd method. Essentially that code is used to create the Window handle for your TWinControl descendant (TFrame, TButton, TEdit... if it can have keyboard focus it's an TWinControl descendant), and it's actually an very sensible error message: You can't have a Window without an WindowParent, and since we're talking about the VCL here, it makes a lot of sense to try and get the parent window handle from TWinControl.Parent; And that's not assigned.
That's not WHY the error message is popping up. You get to see that error message because some of the code you're using to set up the frame requires an Window handle for some operation. It could be anything, like setting the Caption of some component (that internally requires an window handle do to some calculation). I personally really hate it when that happens. When I create GUI's from code I try to delay the assignment of Parent as much as possible, in an attempt to delay the creation of the window, so I got bitten by this many times.
Specific to your DLL usage, possible fix
I'm going to put my psycho mind reader hat on. Since you need to return a FRAME from your DLL, and you can't return the actual Frame because that's an Delphi-specific object and you're not allowed to return Delphi-specific objects over DLL boundaries, my guess is you're returning an Window Handle, as all the nice API's do, using a function definition like this:
function GiveMeTheNiceFrame:HWND;
The trouble is, that routine requires the creation of the actual Window Handle, by a call to TWinControl.CreateWnd, and in turn that call requires an parent window handle to set up the call to Windows.CreateWindowEx, and the routine can't get an parent window handle, so it errors out.
Try replacing your function with something allong the lines of:
function GiveMeTheNiceFrame(OwnerWindow:HWND):HWND;
begin
Result := TMyNiceFrame.CreateParanted(OwnerWindow).Handle;
end;
... ie: use the CreateParented(AParentWindow:HWND) constructor, not the usual Create(AOwner:TComponent) and pass an owner HWND to your DLL.
There are a few important things to remember:
When using DLLs, both your DLL and your EXE each have an Application instance that are struggling for control. The Controls in your DLL will see the Application instance that belongs to the DLL; the Controls in your EXE will see the Application instance that belongs to the EXE. That struggle is not there when using packages, as then there will only be one Application instance.
Frames are Controls, but they are not Forms.
When using Controls in an application, they cannot visually exist without a parent Control (usually a Form or a container that has a parent hierarchy towards a Form).
Some Controls cannot expose their full functionality unless they exist visually and have a valid parent.
Try to reproduce your problem inside the EXE; if you cannot reproduce, it is probably the first thing in the above list.
--jeroen
Sounds like you simply need to assign the component (a form or part of a form, like a panel) that holds the frame to theframe.parent.
You cannot do GUI work before it is assigned. Frames are parts of forms for reuse, and normally need to assign some parent to them.
Move the GUI code to onshow or a procedure you call explicitely, so that the calling code can assign parent.
Or make the parent a parameter in the function.
I found this (CreateParams is called as part of CreateWnd):
procedure TCustomFrame.CreateParams(var Params: TCreateParams);
begin
inherited;
if Parent = nil then
Params.WndParent := Application.Handle;
end;
And Application.Handle = 0 so it always throws the error later in CreateWnd.
After reading this
Delphi: How to call inherited inherited ancestor on a virtual method?
I have solved it by overriding CreateParams in my frame to miss out the tCustomFrame version:
type
tCreateParamsMethod = procedure(var Params: TCreateParams) of object;
type
tMyScrollingWinControl = class(TScrollingWinControl);
procedure TDelphiFrame.CreateParams(var Params: TCreateParams);
var
Proc: tCreateParamsMethod;
begin
TMethod(Proc).Code := #TMyScrollingWinControl.CreateParams;
TMethod(Proc).Data := Self;
Proc(Params);
end;
Now it's just throwing errors when trying to set the focus on subcontrols, which I think I will fix by intercepting WM_FOCUS but we'll how it goes from here.
function CreateFrame(hwndParent: HWnd): HWnd; stdcall;
var
frame: tFrame;
begin
Result := 0;
try
frame := TDelphiFrame.CreateParented(hwndParent);
Result := frame.Handle;
except on e: Exception do
ShowMessage(e.Message);
end;
end;
You can avoid this message by assigning nil to the parent OnClose event, sometimes it works:
SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;
I think this is very cool solution. I think it is not tried before :)
I'm using a Dummy Parent (which is a Form).
function MyFrame_Create(hApplication, hwndParent:THandle; X, Y, W, H:Integer):Pointer; stdcall;
var Fr: TMyFrame;
F: TForm;
CurAppHandle: THandle;
begin
CurAppHandle:=Application.Handle;
Application.Handle:=hApplication;
//---
F:=TForm. Create(Application);//Create a dummy form
F.Position:=poDesigned;
F.Width:=0; F.Top:=0; F.Left:=-400; F.Top:=-400;//Hide Form
F.Visible:=True;
//---
Fr:=TMyFrame.Create(Application);
Fr.Parent:=F;//Set Frame's parent
//Fr.ParentWindow:=hwndParent;
Windows.SetParent(Fr.Handle, hwndParent);//Set Frame's parent window
if CurAppHandle>0 then Application.Handle:=CurAppHandle;
//---
Fr.Left:=X;
Fr.Top:=Y;
Fr.Width:=W;
Fr.Height:=H;
Result:=Fr;
end;//MyFrame_Create
procedure MyFrame_Destroy(_Fr:Pointer); stdcall;
var Fr: TMyFrame;
F: TObject;
begin
Fr:=_Fr;
F:=Fr.Parent;
Fr.Parent:=Nil;
if (F is TForm) then F.Free;
//SetParent(Fr.Handle, 0);
//Fr.ParentWindow:=0;
Fr.Free;
end;//MyFrame_Destroy
I have some forms in my application which have different "states" depending on what the user is doing; for exemple when listing through his files the form displays some data about that file in a grid, but if he clicks on some button the grid is replaced by a graph relating to it. Simply said, the controls in the form depends on the what the user wants to do.
Of course the obvious way of doing that was showing/hidding controls as needed, works like a charm for small numbers but once you reach 10/15+ controls per state (or more than 3 states really) it's unusable.
I'm experimenting with TFrames right now: I create a frame for every state, I then create an instance of each frame on my form on top of each other and then I only display the one I want using Visible - while having some controls on top of it, out of any frame since they all share them.
Is this the right way to do what I want, or did I miss something along the way ? I thought I could create only one tframe instance and then chose which one to display in it but it doesn't look that way.
Thanks
Looks like Frames are an excellent choice for this scenario. I'd like to add that you can use a Base Frame and Visual Inheritance to create a common interface.
And to the second part: You design a Frame like a Form but you use it like a Control, very few restrictions. Note that you could just as easily use Create/Free instead of Show/Hide. What is better depends on how resource-heavy they are.
There's a better way to handle dealing with the frames that won't take up nearly as much memory. Dynamically creating the frames can be a very elegant solution. Here's how I've done it in the past.
On the form, add a property and a setter that handles the placement of it on the form:
TMyForm = class(TForm)
private
FCurrentFrame : TFrame;
procedure SetCurrentFrame(Value : TFrame);
public
property CurrentFrame : TFrame read FCurrentFrame write SetCurrentFrame;
end;
procedure TMyForm.SetCurrentFrame(Value : TFrame)
begin
if Value <> FCurrentFrame then
begin
if assigned(FCurrentFrame) then
FreeAndNil(FCurrentFrame);
FCurrentFrame := Value;
if assigned(FCurrentFrame) then
begin
FCurrentFrame.Parent := Self; // Or, say a TPanel or other container!
FCurrentFrame.Align := alClient;
end;
end;
end;
Then, to use it, you simply set the property to a created instance of the frame, for example in the OnCreate event:
MyFrame1.CurrentFrame := TSomeFrame.Create(nil);
If you want to get rid of the frame, simply assign nil to the CurrentFrame property:
MYFrame1.CurrentFrame := nil;
It works extremely well.
I have a word for you : TFrameStack. Simply what the name suggests.
It has a few methods: PushFrame(AFrame), PopFrame, PopToTop(AFrame), PopToTop(Index),
and a few Properties: StackTop; Frames[Index: Integer]; Count;
Should be self explanatory.
The Frame at StackTop is the visible one. When doing ops like Back/Previous you don't need to know what frame was before the current one :)
When creating the Frame you can create and push it in one go FrameStack.Push(TAFrame.Create) etc, which creates it calls the BeforeShow proc and makes it visible, returning its index in the stack :)
But it does rely heavily on Inheriting your frames from a common ancestor. These frames all (in my Case) have procedures: BeforeShow; BeforeFree; BeforeHide; BeforeVisible.
These are called by the FrameStack Object during push, pop and top;
From your main form you just need to access FrameStack.Stacktop.whatever. I made my Stack a global :) so it's really easy to access from additional dialogs/windows etc.
Also don't forget to create a Free method override to free all the frames ( if the owner is nil) in the stack when the app is shut down - another advantage you don't need to track them explicitly:)
It took only a small amount of work to create the TFrameStack List object. And in my app work like a dream.
Timbo
I also use the approach described by #Tim Sullivan with some addition. In every frame I define the procedure for the frame initialization - setting default properties of its components.
TAnyFrame = class(TFrame)
public
function initFrame() : boolean; // returns FALSE if somesthing goes wrong
...
end;
And after the frame was created I call this procedure.
aFrame := TAnyFrame.Create(nil);
if not aFrame.initFrame() then
FreeAndNil(aFrame)
else
... // Associate frame with form and do somthing usefull
Also when you change visible frame it is not necessary to destroy previous one because it can contains useful data. Imagine that you input some data in a first frame/page, then go to the next, and then decide to change data on the first page again. If you destroy previous frame you lost the data it contains and need to restore them. The solution is to keep all created frames and creates new frame only when it is necessary.
page_A : TFrameA;
page_B : TFrameB;
page_C : TFrameC;
current_page : TFrame;
// User click button and select a frame/page A
if not assigned(page_A) then begin
// create and initialize frame
page_A := TFrameA.Create(nil);
if not page_A.initFrame() then begin
FreeAndNil(page_A);
// show error message
...
exit;
end;
// associate frame with form
...
end;
// hide previous frame
if assigned(current_page) then
current_page.hide();
// show new page on the form
current_page := page_A;
current_page.Show();