create Tframes on runtime: - delphi

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.

Related

Delphi: refer to a component from multiple frames

I want to refer to a component (using loop) which exists in multiple frames. The component is the same for all frames.
I have a ComboBox named "comboBoxAccount".
I also have multiple frames (which I instantiate at runtime).
The frames I create are named:
frm1, frm2, frm3
Each frame has a multple TabSheets created at runtime named:
page_1, page_2, page_3
So, I am currently using:
Tfrm1(FindComponent('page_'+i)).comboBoxAccount
Which means I use FindComponent with i in a loop to access all comboBoxAccount in all TabSheets.
The problem is that I have to use casting to make Delphi understand which component I want to access, so I've put Tfrm1( ) in front of FindComponent.
So, if I want to use casting with a loop what can I do?
I could just use:
Tfrm1(FindComponent('page_'+i)).comboBoxAccount
Tfrm2(FindComponent('page_'+i)).comboBoxAccount
Tfrm3(FindComponent('page_'+i)).comboBoxAccount
but is there a way to loop Tfrm without writing them one by one?
page_1, page_2, etc are TabSheets, not frames, but you are casting them to frames, which is wrong.
If each frame has multiple TabSheets on it, and each TabSheet has a ComboBox on it, you would not use FindComponent() at all. You would have to iterate through parent/child Controls[] lists instead, or use FindChildControl().
However, if the frame is the Owner of the ComboBox (which it would be if the ComboBox is added to the frame at design-time), then you would use FindComponent() on the frame itself. But you cannot have multiple child components with the same name, so each ComboBox on each TabSheet would need a unique name:
cb := TComboBox(frm1.FindComponent('comboBoxAccount_'+IntToStr(i)));
Repeating for each index of frm1, then all indexes of frm2, then frm3.
If your ComboBox really is named just comboBoxAccount then I wonder if you really have one parent container that has multiple TabSheets on it, and each TabSheet has a frame on it, and each frame has only one ComboBox named comboBoxAccount on it. If that is the case, you don't need to do any searching at all, just use your existing object pointers directly:
cb := frm1.comboBoxAccount;
cb := frm2.comboBoxAccount;
cb := frm3.comboBoxAccount;
But if you really want to use a loop, then put the frames into a list, eg:
private
Frames: TList;
...
Frames := TList.Create;
Frames.Add(frm1);
Frames.Add(frm2);
Frames.Add(frm3);
...
for I := 0 to Frames.Count-1 do
begin
// assuming TFrm1, TFrm2, etc do not have a common ancestor holding the ComboBox...
cb := TComboBox(TWinControl(Frames[i]).FindComponent('comboBoxAccount'));
...
end;
Or use an array:
private
// assuming TFrm1, TFrm2, etc do not have a common ancestor holding the ComboBox...
Frames: array[0..2] of TWinControl;
...
Frames[0] := frm1;
Frames[1] := frm2;
Frames[2] := frm3;
...
for I := Low(Frames) to High(Frames) do
begin
cb := TComboBox(Frames[i].FindComponent('comboBoxAccount'));
...
end;
Update: given new information you provided, you can find the ComboBox like this instead:
cb := Tfrm1(PageControl[i].Pages[j].FindChildControl('frame_'+IntToStr(i))).comboBoxAccount;
Your while loop is creating multiple tabsheets and frames that have the same Name, which is not valid. I would suggest not naming them at all.
while condition=true do
begin
TabSheet := TTabSheet.Create(PageControl[i]);
TabSheet.Caption := '';
//TabSheet.Name:='tabesheet_'+IntToStr(i);
TabSheet.PageControl := PageControl[i];
TabSheet.PageControl.ActivePageIndex:=0;
{I also create a frame for each TabSheet. In every frame there is the ComboBox that I want to access}
frame := Tfrm1.Create(TabSheet);
//frame.Name:='frame_'+IntToStr(i);
frame.Parent:=TabSheet;
end;
Assuming a frame is the only child of its TabSheet, you can then do this:
cb := Tfrm1(PageControl[i].Pages[j].Controls[0]).comboBoxAccount;

Why doesn't my new control appear if I pass the parent control to Create instead of assigning the Parent property?

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.

Frame names being assigned automatically

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.

The control 'xxx' has no parent window

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

Should I use delphi tframes for multi-pages forms?

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();

Resources