If I create n Tabsheets at runtime and create one Webbrowser inside each tabsheet using a method such as:
procedure createTab;
var crm: TWebbrowser;
var ts: TsTabSheet;
begin
//Instance of tabsheet
ts := TsTabSheet.Create(pageControl);
ts.PageControl := pageControl;
//Instance of webbrowser
crm := TWebbrowser.Create(ts);
crm.Parent := TWinControl(ts);
crm.Align := alClient;
end;
When one of tabsheet instance is active how could I know which webbrowser is inside it? Sample:
procedure navigateToActiveTabsheet(url: string);
begin
//TO DO - How navigate to webbrowser inside active tabsheet?
end;
The Controls property of a windowed control allows you to obtain every child control. Because these children can be any TControl descendent, you'll need to cast to TWebBrowser. Use the as operator to benefit from runtime validity checking of the cast:
procedure navigateToActiveTabsheet(url: string);
var
wb: TWebBrowser;
begin
wb := pageControl.ActivePage.Controls[0] as TWebBrowser;
wb.Navigate(url);
end;
var
WB: TWebBrowser;
WB := TWebBrowser(pageControl.ActivePage.Controls[0]);
Related
i want to ask how to retain controlls when im making a copy of a control. for example i have an edit box that can be controlled with a slider for value change. when i make a copy using this code i achieve a copy of the items but the slider stops controlling editbox values. how can i fix that?
TypInfo;
procedure CloneProperties(const Source: TControl; const Dest: TControl);
var
ms: TMemoryStream;
OldName: string;
begin
OldName := Source.Name;
Source.Name := ''; // needed to avoid Name collision
try
ms := TMemoryStream.Create;
try
ms.WriteComponent(Source);
ms.Position := 0;
ms.ReadComponent(Dest);
finally
ms.Free;
end;
finally
Source.Name := OldName;
end;
end;
procedure CloneEvents(Source, Dest: TControl);
var
I: Integer;
PropList: TPropList;
begin
for I := 0 to GetPropList(Source.ClassInfo, [tkMethod], #PropList) - 1 do
SetMethodProp(Dest, PropList[I], GetMethodProp(Source, PropList[I]));
end;
procedure DuplicateChildren(const ParentSource: TWinControl;
const WithEvents: Boolean = True);
var
I: Integer;
CurrentControl, ClonedControl: TControl;
begin
for I := ParentSource.ControlCount - 1 downto 0 do
begin
CurrentControl := ParentSource.Controls[I];
ClonedControl := TControlClass(CurrentControl.ClassType).Create(CurrentControl.Owner);
ClonedControl.Parent := ParentSource;
CloneProperties(CurrentControl, ClonedControl);
ClonedControl.Name := CurrentControl.Name + '_';
if WithEvents then
CloneEvents(CurrentControl, ClonedControl);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DuplicateChildren(Panel1);
end;
Unless I'm misunderstanding you, your CloneProperties doesn't seem to have anything to do with the question you're asking. In your example of an edit control E1 and a slider S1, you can clone both of them to produce E2 and S2, but somewhere in your code there must be a statement that changes the value in E1 depending on the value of S1. However, in the way you've most likely written it, that statement doesn't apply to E2 and S2.
The simplest way around that is to write a method which takes the component instances and links the operation of the two together. e.g.
procedure TForm1.SetEditControlFromSlider(AnEdit : TEdit; ASlider : { TWhatever the slider actually is);
begin
// Set AnEdit's value from ASlider's properties
end;
Then, you can call this with Edit/Slider pairs like this
SetEditControlFromSlider(E1, S1);
[...]
SetEditControlFromSlider(E2, S2);
I can imagine you might not like having to do that.
IMO, the cleanest solution is to avoid attempting to clone components altogether and create a TFrame containing the Edit, Slider and the code that connects them, and then add to your form as many instances of the frame as you need. It's as easy as falling off a log.
type
TEditFrame = class(TFrame) // needs to be in its own unit, Used by your form
Edit1: TEdit;
TrackBar1: TTrackBar;
procedure TrackBar1Change(Sender: TObject);
private
public
end;
[...]
procedure TEditFrame.TrackBar1Change(Sender: TObject);
begin
Edit1.Text := IntToStr(TrackBar1.Position)
end;
Then, you can add clones of the frame to TForm1 by
procedure TForm1.Button1Click(Sender: TObject);
var
AFrame : TEditFrame;
begin
Inc(FrameCount); // Field of TForm1
AFrame := TEditFrame.Create(Self);
AFrame.Name := AFrame.Name + IntToStr(FrameCount);
AFrame.Parent := Self;
AFrame.Top := AFrame.Height * FrameCount;
end;
Note that because the code which links the two components, TrackBar1Change, it compiled into the frame's unit, it is automatically shared by every instance of the frame you create, without any need to "clone" the code.
I'm using Delphi's GetObjectProp function to get the properties of the form components, I get all the properties of several components, but I can not get the TextSettings.Font.Style (Bold, Italic, ...) property of components like TLabel for example. I need to know if the component text is bold or italic. The procedure I am working on trying to get these properties follows below:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
TextSettings: TTextSettings;
Fonte: TFont;
Estilo: TFontStyle;
Componente_cc: TControl;
begin
Componente_cc := TControl(Label1);
if IsPublishedProp(Componente_cc, 'TextSettings') then
begin
TextSettings := GetObjectProp(Componente_cc, 'TextSettings') as TTextSettings;
if Assigned(TextSettings) then
Fonte := GetObjectProp(TextSettings, 'Font') as TFont;
if Assigned(Fonte) then
Estilo := GetObjectProp(Fonte, 'Style') as TFontStyle; // <-- error in this line
if Assigned(Estilo) then
Edit1.text := GetPropValue(Estilo, 'fsBold', true);
end
end;
The error displayed on the line where I marked above is.
[dcc64 Error] uPrincipal.pas(1350): E2015 Operator not applicable to this operand type
What am I doing wrong?
GetObjectProp(Fonte, 'Style') will not work since Style is not an object-based property to begin with, it is a Set-based property. And GetPropValue(Estilo, 'fsBold', true) is just plain wrong (not that you would get far enough to call it anyway), because fsBold is not a property, it is a member of the TFontStyle enum. To retreive the Style property value, you would have to use GetOrdProp(Fonte, 'Style'), GetSetProp(Fonte, 'Style'), or GetPropValue(Fonte, 'Style') instead (as an integer, string, or variant, respectively).
That being said, once you have retrieved the TextSettings object, you don't need to use RTTI at all to access its Font.Style property, just access the property directly.
Try this instead:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
Componente_cc: TControl;
TextSettings: TTextSettings;
begin
Componente_cc := ...;
if IsPublishedProp(Componente_cc, 'TextSettings') then
begin
TextSettings := GetObjectProp(Componente_cc, 'TextSettings') as TTextSettings;
Edit1.Text := BoolToStr(TFontStyle.fsBold in TextSettings.Font.Style, true);
end;
end;
A better (and preferred) solution is to not use RTTI at all. FMX classes that have a TextSettings property also implement the ITextSettings interface for exactly this situation, eg:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
Componente_cc: TControl;
Settings: ITextSettings;
begin
Componente_cc := ...;
if Supports(Componente_cc, ITextSettings, Settings) then
begin
Edit1.Text := BoolToStr(TFontStyle.fsBold in Settings.TextSettings.Font.Style, true);
end;
end;
Read Embarcadero's documentation for more details:
Setting Text Parameters in FireMonkey
I have a application which load plugins that are in the form of bpls. Each plugin contains a form to be embedded into another package called CoreInf (form name ManageF) which is just the base gui of the application that contains a TTabcontrol known as MTabcontrol1. Here basically what happens
A list of plugins are dynamically loaded at runtime in sequential order to layout the interface. It loads the plugins based on the interface IPluginFrame(Shared.bpl) . If it contains the interface it then tries to create a new tab in MTabcontrol1 and embedded the form. What im trying to do is make a dynamically created speedbutton onClick focus a TEdit box on a specific ebedded form but it keeps coming up as access violation errors.
Shared.bpl that contains the interface
unit PluginIntf;
interface
uses
FMX.Types, FMX.Controls;
type
IPluginFrame = interface
['{3B4943DB-951B-411B-8726-03BF1688542F}']
function GetBaseControl: TControl;
end;
implementation
end.
Form and Button that is to be ebedded into the interface
InventoryInf.pas has the form to be embeeded
var
InventoryF: TInventoryF;
implementation
{$R *.fmx}
function TInventoryF.GetBaseControl: TControl;
begin
Result := InvenLayout; //<---- This is a a TLayout which align
//to client this is the parent of every item on form
end;
//Below is the on click event for the TSpeedButton invBtn
//Which looks for the embedded form that is embedded
//in MTabcontrol1 as a TTabitem that has the name InventoryF
procedure TInventoryF.Look4Tabitem(Sender: TObject);
var
o : TTabitem;
s :TEdit;
begin
o := TTabitem(ManageF.MTabcontrol1.FindComponent('InventoryF'));
ManageF.MTabcontrol1.ActiveTab := o;
s := TEdit(ManageF.MTabcontrol1.FindComponent('VendorF').FindComponent('SearchEdit1')); <-- Dont think it actually found the TEdit
s.Setfocus; <------------------ Not focusing giving access violtaion error
with DataConModule1.InventoryQuery do
...
end;
TSpeedButton invBtn that is injected into Panel to the side
unit InjectedInvBtn;
interface
implementation
uses
FMX.Types, InventoryInf, FMX.Controls, FMX.Forms, InjectedControlsHelper, FMX.StdCtrls,
ManageInf;
var
SysBtn: TSpeedButton;
initialization
SysBtn := TInjectedControl<TSpeedButton>.Create;
SysBtn.Align := TAlignLayout.Top;
SysBtn.Name := 'invBtn';
SysBtn.Text := 'INVENTORY';
ManageF.MenuPanel.AddObject(SysBtn);
SysBtn.OnClick := InventoryF.Look4Tabitem;
end.
**ManageF Showing how it loads the forms into tabs MTabControl1 **
uses Shared;
function IsPluginFrameClass(AType: TRttiType; var AFormClass: TCustomFormClass): Boolean;
var
LClass: TClass;
begin
if not (AType is TRttiInstanceType) then Exit(False);
LClass := TRttiInstanceType(AType).MetaclassType;
Result := LClass.InheritsFrom(TCustomForm) and Supports(LClass, IPluginFrame);
if Result then
AFormClass := TCustomFormClass(LClass);
end;
function TSettingsF.LoadPluginTabs(const AFileName: string): HMODULE;
var
Context: TRttiContext;
Frame: TCustomForm;
FrameClass: TCustomFormClass;
LType: TRttiType;
Package: TRttiPackage;
Tab: TTabItem;
// Statusbar: TTabItem;
//Butz: TButton;
begin
Result := LoadPlugin(AFileName);
try
{ Cycle through the RTTI system's packages list to find the one we've just loaded. }
for Package in Context.GetPackages do
if Package.Handle = Result then
begin
{ Cycle through the package's types looking for implementors of the
IPluginFrameinterface defined in the shared package. }
for LType in Package.GetTypes do
if IsPluginFrameClass(LType, FrameClass) then
begin
{ For each frame, create a new tab to host its contents. In the case of
a VCL application, we could require an actual TFrame object, or failing
that, embed the form directly. FireMonkey has neither frames proper nor
supports embedded forms, so instead we ask the implementing form to
nominate a base control that will get embedded. }
Tab := TTabItem.Create(ManageF.MTabcontrol1);
Frame := FrameClass.Create(Tab);
Tab.Text := ' ' + Frame.Caption;
Tab.Name := Frame.Name;
MyTablist.Add(Tab);
(Frame as IPluginFrame).GetBaseControl.Parent := Tab;
{ Associate the tab with the plugin - since it owns the 'frame' form,
and that form owns its own components, freeing the tab will have the
effect of freeing all the actual plugin objects too. }
RegisterPluginComponent(Result, Tab);
ManageF.MTabcontrol1.AddObject(Tab);
Tab.Width := Tab.Canvas.TextWidth(Tab.Text + 'w');
end;
if IsStatusFrameClass(LType, FrameClass) then
begin
....
{ Associate the tab with the plugin - since it owns the 'frame' form,
and that form owns its own components, freeing the tab will have the
effect of freeing all the` actual plugin objects too. }
// RegisterPluginComponent(Result, Statusbar);
// ManageF.StatusMenuPanel1.AddObject(Statusbar);
//Statusbar.Width := Statusbar.Canvas.TextWidth(Statusbar.Name + 'w');
end;
Break;
end;
except
UnloadPlugin(Result);
raise;
end;
end;
Hope i illustrated the problem properly. Please Help =(
Found a work around by looping through the components of the Tcustomform that is embedded. Here is what i did.
instead of TEdit i used a tms edit box TTMSFMXSearchEdit
procedure TVendorF.focuscheck;
var
i: integer;
j: integer;
Fieldname: string;
o : TTabitem;
e : TTMSFMXSearchEdit;
begin
if ManageF.MTabcontrol1.FindComponent('VendorF').Name = 'VendorF' then
begin
o := TTabitem(ManageF.MTabcontrol1.FindComponent('VendorF'));
//ShowMessage(IntToStr(o.ComponentCount)) ;
// ShowMessage((o.Components[0].tostring));
for i := 0 to ManageF.MTabcontrol1.ActiveTab.ComponentCount - 1 do
if (ManageF.MTabcontrol1.ActiveTab.Components[i]) is TCustomForm then
begin
// ShowMessage('TCustomForm Recognized gonna look for child components now');
// ShowMessage(IntToStr(ManageF.MTabcontrol1.ActiveTab.Components[i].ComponentCount));
for j := 0 to ManageF.MTabcontrol1.ActiveTab.Components[i].ComponentCount - 1 do
if (ManageF.MTabcontrol1.ActiveTab.Components[i].Components[j]) is TTMSFMXSearchEdit then
begin
// ShowMessage('Edit box found =)')
if (ManageF.MTabcontrol1.ActiveTab.Components[i].Components[j].Name = 'VenSearchEdit1') then
begin
//ShowMessage('Edit1 box found =)');
//ShowMessage('See if we can focus it');
e := TTMSFMXSearchEdit(ManageF.MTabcontrol1.ActiveTab.Components[i].Components[j]) ;
e.SetFocus;
end;
end;
end;
end;
end;
Its a little sloppy but it works =). If anyone else got a better way let me know
I have 5 forms created at design time. I need to dynamically create an instance of each form and put on a tab.
My question: If the form names are in an array of strings and I call my procedure like this:
ShowForm(FormName[3]);// To show the 3rd form on a tab page.
How can I define and create the new instance for each form?
This is what I have for now:
procedure TForm1.ShowFormOnTab(pProcName:String);
var
NewForm: TfrmSetupItemCategories;//***HERE IS MY PROBLEM***
NewTab: TTabSheet;
FormName: String;
begin
NewTab := TTabSheet.Create(PageControl1);
NewTab.PageControl:= PageControl1;
NewTab.Caption:='hi';
PageControl1.ActivePage := NewTab;
if pProcName='ProcfrmSetupItemCategories' Then
begin
NewForm:=TfrmSetupItemCategories.Create(NewTab);
NewTab.Caption := NewForm.Caption;
end;
if pProcName='ProcfrmZones' Then
begin
NewForm:=TfrmZones.Create(NewTab);
NewTab.Caption := NewForm.Caption;
end;
.
.
.
end;
the line that reads "HERE IS MY PROBLEM" is where I need help. I can't reuse NewForm as a variable with a second form in this way...
Note: My problem is NOT the tab. Rather it's creating a new instance of the form using the same variable name.
Declare the NewForm variable as TForm:
var
NewForm: TForm;
begin
NewForm := TMyForm.Create(Tab1); //compiles OK
NewForm := TMyOtherForm.Create(Tab2); //also compiles OK
end;
I'm assuming TMyForm and TMyOtherForm both are derivatives of TForm.
DRY
You can also reduce your repeating code using a class reference variable, like this:
procedure TForm1.ShowFormOnTab(pProcName:String);
var
NewForm: TForm;
ClassToUse: TFormClass;
NewTab: TTabSheet;
FormName: String;
begin
NewTab := TTabSheet.Create(PageControl1);
NewTab.PageControl:= PageControl1;
NewTab.Caption:='hi';
PageControl1.ActivePage := NewTab;
if pProcName='ProcfrmSetupItemCategories' then
ClassToUse := TfrmSetupItemCategories
else if pProcName='ProcfrmZones' then
ClassToUse := TfrmZones
else
ClassToUse := nil;
if Assigned(ClassToUse) then
begin
NewForm := ClassTouse.Create(NewTab);
NewTab.Caption := NewForm.Caption;
//if you access custom properties or methods, this is the way:
if NewForm is TfrmZones then
TfrmZones(NewForm).ZoneInfo := 'MyInfo';
end;
end;
Register your classes and then create the forms from a string
As Sir Rufo points in his comment, you can even go further registering your classes (I'm not sure if this can be done in Lazarus, that exercise is up to you).
First, register the form classes you want to instantiate from the class name, previous to any call to your ShowFormOnTab method, for example:
procedure TMainForm.FormCreate(Sender: TObject);
begin
RegisterClass(TfrmSetupItemCategories);
RegisterClass(TfrmZones);
//and other classes
end;
Then, you can change the code to get the class reference from the class name string:
procedure TForm1.ShowFormOnTab(pProcName:String);
var
NewForm: TForm;
ClassToUse: TFormClass;
ClassNameToUse: string;
NewTab: TTabSheet;
FormName: String;
begin
NewTab := TTabSheet.Create(PageControl1);
NewTab.PageControl:= PageControl1;
NewTab.Caption:='hi';
PageControl1.ActivePage := NewTab;
//get rid of 'Proc' and add the T
//or even better, pass directly the class name
ClassNameToUse := 'T' + Copy(pProcName, 5, MaxInt);
ClassToUse := TFormClass(FindClass(ClassNameToUse));
if Assigned(ClassToUse) then
begin
NewForm := ClassTouse.Create(NewTab);
NewTab.Caption := NewForm.Caption;
//if you access custom properties or methods, this is the way:
if NewForm is TfrmZones then
TfrmZones(NewForm).ZoneInfo := 'MyInfo';
end;
end;
That way, the code remains the same for any number of classes.
For more info about this, take a look at Creating a Delphi form from a string in delphi.about.com.
Declare your variable as an ancestor type:
var
NewForm: TForm;
or
var
NewForm: TCustomForm;
Drawback: you'll need to cast the variable to the specific class if you want to call any methods of your form that you have declared yourself.
Use a 'soft' cast if you want to have the compiler check that NewForm is actually a TMyForm at runtime:
(NewForm as TMyForm).MyMethod;
When you are absolutely sure that NewForm is a TMyForm (like when you just created it), you can also use a 'hard' cast:
TMyForm(NewForm).MyMethod;
With registered classes, in the initialization of the used forms, you could shorten it to
Function CreateAndDock(pc:TPageControl;const FormName:String):Boolean;
begin
Result := false;
if Assigned(GetClass(FormName)) and GetClass(FormName).InheritsFrom(TCustomForm) then
With TFormClass( GetClass(FormName)).Create(pc.Owner) do
begin
ManualDock(pc);
Show;
Result := true;
end;
end;
procedure TForm4.Button1Click(Sender: TObject);
begin
ShowMessage(IntToStr(Integer(CreateAndDock(pagecontrol1,'TDockForm'))));
ShowMessage(IntToStr(Integer(CreateAndDock(pagecontrol1,'TNotExists'))));
end;
Is it possible to, for instance, replace and free a TEdit with a subclassed component instantiated (conditionally) at runtime? If so, how and when it should be done? I've tried to set the parent to nil and to call free() in the form constructor and AfterConstruction methods but in both cases I got a runtime error.
Being more specific, I got an Access violation error (EAccessViolation). It seems François is right when he says that freeing components at frame costruction messes with Form controls housekeeping.
This more generic routine works either with a Form or Frame (updated to use a subclass for the new control):
function ReplaceControlEx(AControl: TControl; const AControlClass: TControlClass; const ANewName: string; const IsFreed : Boolean = True): TControl;
begin
if AControl = nil then
begin
Result := nil;
Exit;
end;
Result := AControlClass.Create(AControl.Owner);
CloneProperties(AControl, Result);// copy all properties to new control
// Result.Left := AControl.Left; // or copy some properties manually...
// Result.Top := AControl.Top;
Result.Name := ANewName;
Result.Parent := AControl.Parent; // needed for the InsertControl & RemoveControl magic
if IsFreed then
FreeAndNil(AControl);
end;
function ReplaceControl(AControl: TControl; const ANewName: string; const IsFreed : Boolean = True): TControl;
begin
if AControl = nil then
Result := nil
else
Result := ReplaceControlEx(AControl, TControlClass(AControl.ClassType), ANewName, IsFreed);
end;
using this routine to pass the properties to the new control
procedure CloneProperties(const Source: TControl; const Dest: TControl);
var
ms: TMemoryStream;
OldName: string;
begin
OldName := Source.Name;
Source.Name := ''; // needed to avoid Name collision
try
ms := TMemoryStream.Create;
try
ms.WriteComponent(Source);
ms.Position := 0;
ms.ReadComponent(Dest);
finally
ms.Free;
end;
finally
Source.Name := OldName;
end;
end;
use it like:
procedure TFrame1.AfterConstruction;
var
I: Integer;
NewEdit: TMyEdit;
begin
inherited;
NewEdit := ReplaceControlEx(Edit1, TMyEdit, 'Edit2') as TMyEdit;
if Assigned(NewEdit) then
begin
NewEdit.Text := 'My Brand New Edit';
NewEdit.Author := 'Myself';
end;
for I:=0 to ControlCount-1 do
begin
ShowMessage(Controls[I].Name);
end;
end;
CAUTION: If you are doing this inside the AfterConstruction of the Frame, beware that the hosting Form construction is not finished yet.
Freeing Controls there, might cause a lot of problems as you're messing up with Form controls housekeeping.
See what you get if you try to read the new Edit Caption to display in the ShowMessage...
In that case you would want to use
...ReplaceControl(Edit1, 'Edit2', False)
and then do a
...FreeAndNil(Edit1)
later.
You have to call RemoveControl of the TEdit's parent to remove the control. Use InsertControl to add the new control.
var Edit2: TEdit;
begin
Edit2 := TEdit.Create(self);
Edit2.Left := Edit1.Left;
Edit2.Top := Edit2.Top;
Edit1.Parent.Insertcontrol(Edit2);
TWinControl(Edit1.parent).RemoveControl(Edit1);
Edit1.Free;
end;
Replace TEdit.Create to the class you want to use, and copy all properties you need like I did with Left and Top.
You can actually use RTTI (look in the TypInfo unit) to clone all the matching properties. I wrote code for this a while back, but I can't find it now. I'll keep looking.