I have an application to implement CRUD on many tables.
The main form has a tab for each table and a single toolbar with Insert, Update, Delete buttons valid for all tabs.
Every time the tab is changed, a variable
frameClass: TFrameClass; (where TFrameClass = class of TFrame) gets the type of frame created under the tab, and other variable frame: TFrame; gets the frame created under the tab.
When, say, the INSERT button is clicked, I would like to direct to the Insert() procedure corresponding to the active tab, like:
frameClass(frame).insert // trying to cast
But the compiler says insert is not a valid method. But if I cast with the content of frameClass, it works:
TFrame1(frame).insert; // does not work in general case.
What am I doing wrong?
This is the sample code:
Unit1.pas
unit unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Menus,
FMX.TabControl,
unit2, unit3;
type
TFrameClass = class of TFrame;
TFormMain = class(TForm)
TabControl1: TTabControl;
TabItem1: TTabItem;
TabItem2: TTabItem;
PopupMenu1: TPopupMenu;
MenuItemInsert: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure TabControl1Change(Sender: TObject);
procedure MenuItemInsertClick(Sender: TObject);
public
frame: TFrame;
frameClass: TFrameClass;
frames: array[0..1] of TFrameClass;
end;
var
FormMain: TFormMain;
implementation
{$R *.fmx}
procedure TFormMain.FormCreate(Sender: TObject);
begin
frames[0]:= TFrame1;
frames[1]:= TFrame2;
end;
procedure TFormMain.MenuItemInsertClick(Sender: TObject);
begin
// want the insert click to work whatever the activeTab is
// (frame as FrameClass).insert; // insert is not a method
// THIS IS THE GIST OF MY QUESTION:
// TFrame1(frame).insert; // it works but want it general
// FrameClass(frame).insert; // this is how I'd like it to work
end;
procedure TFormMain.TabControl1Change(Sender: TObject);
begin
frameClass:= frames[tabControl1.tabIndex];
frame:= frameClass.Create(tabControl1.activeTab);
frame.Parent:= tabControl1.activeTab;end;
end.
Unit2.pas
unit Unit2;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation;
type
// if try to descend from other than TFrame, some properties like align, size, etc, are lost
TFrame1 = class(TFrame)
Label1: TLabel;
private
{ Private declarations }
public
{ Public declarations }
procedure insert;
end;
implementation
{$R *.fmx}
procedure TFrame1.insert;
begin
//
end;
end.
unit3.pas
unit Unit3;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation, FMX.Edit;
type
// if try to descend from other than TFrame, some properties like align, size, etc, are lost
TFrame2 = class(TFrame)
Edit1: TEdit;
private
{ Private declarations }
public
{ Public declarations }
procedure insert;
end;
implementation
{$R *.fmx}
procedure TFrame2.insert;
begin
//
end;
end.
It doesn't work the way you want because the base class TFrame doesn't have the methods you are looking for, only your derived frame classes do. When you access a TFrame object via the TFrameClass class reference (or a base TFrame object pointer), you can only access methods that are in TFrame itself. To access derived class methods, you would need to do something more like this:
if frame is TFrame1 then
TFrame1(frame).insert
else if frame is TFrame2 then
TFrame2(frame).insert;
Which defeats what you are trying to accomplish. For that, you need to have your frame classes derive from a common ancestor that declares the methods you want, and then you can access those methods via that ancestor when needed.
There are two ways you can do this:
create a new base class derived from TFrame and has your desired methods, and then change your frame classes to derive from that base.
Unit1.pas
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Menus,
FMX.TabControl,
MyFrameBase;
type
TMyFrameBaseClass = class of TMyFrameBase;
TFormMain = class(TForm)
TabControl1: TTabControl;
TabItem1: TTabItem;
TabItem2: TTabItem;
PopupMenu1: TPopupMenu;
MenuItemInsert: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure TabControl1Change(Sender: TObject);
procedure MenuItemInsertClick(Sender: TObject);
public
frame: TMyFrameBase;
frames: array[0..1] of TMyFrameBaseClass;
end;
var
FormMain: TFormMain;
implementation
{$R *.fmx}
uses
Unit2, Unit3;
procedure TFormMain.FormCreate(Sender: TObject);
begin
frames[0] := TFrame1;
frames[1] := TFrame2;
end;
procedure TFormMain.MenuItemInsertClick(Sender: TObject);
begin
frame.Insert;
end;
procedure TFormMain.TabControl1Change(Sender: TObject);
var
frameClass: TMyFrameBaseClass;
begin
frameClass := frames[TabControl1.TabIndex];
frame := frameClass.Create(TabControl1.ActiveTab);
frame.Parent := TabControl1.ActiveTab;
end;
end.
MyFrameBase.pas
unit MyFrameBase;
interface
uses
FMX.Forms;
type
TMyFrameBase = class(TFrame)
public
procedure Insert; virtual; abstract;
end;
implementation
end.
Unit2.pas
unit Unit2;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation,
MyFrameBase;
type
TFrame1 = class(TMyFrameBase)
Label1: TLabel;
private
{ Private declarations }
public
{ Public declarations }
procedure Insert; override;
end;
implementation
{$R *.fmx}
procedure TFrame1.Insert;
begin
//
end;
end.
Unit3.pas
unit Unit3;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation, FMX.Edit,
MyFrameBase;
type
TFrame2 = class(TMyFrameBase)
Edit1: TEdit;
private
{ Private declarations }
public
{ Public declarations }
procedure Insert; override;
end;
implementation
{$R *.fmx}
procedure TFrame2.Insert;
begin
//
end;
end.
declare an interface that has your desired methods, and then have your frame classes implement that interface. You can query a frame object for that interface when needed.
Unit1.pas
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Menus,
FMX.TabControl;
type
TFrameClass = class of TFrame;
TFormMain = class(TForm)
TabControl1: TTabControl;
TabItem1: TTabItem;
TabItem2: TTabItem;
PopupMenu1: TPopupMenu;
MenuItemInsert: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure TabControl1Change(Sender: TObject);
procedure MenuItemInsertClick(Sender: TObject);
public
frame: TFrame;
frames: array[0..1] of TFrameClass;
end;
var
FormMain: TFormMain;
implementation
{$R *.fmx}
uses
MyFrameIntf, Unit2, Unit3;
procedure TFormMain.FormCreate(Sender: TObject);
begin
frames[0] := TFrame1;
frames[1] := TFrame2;
end;
procedure TFormMain.MenuItemInsertClick(Sender: TObject);
var
intf: IMyFrameIntf;
begin
if Supports(frame, IMyFrameIntf, intf) then
intf.Insert;
end;
procedure TFormMain.TabControl1Change(Sender: TObject);
var
frameClass: TFrameClass;
begin
frameClass := frames[TabControl1.TabIndex];
frame := frameClass.Create(TabControl1.ActiveTab);
frame.Parent := TabControl1.ActiveTab;
end;
end.
MyFrameIntf.pas
unit MyFrameIntf;
interface
type
IMyFrameIntf = interface
['{83A4D2BF-C72F-4075-9450-4A1480A674A4}']
procedure Insert;
end;
implementation
end.
Unit2.pas
unit Unit2;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation,
MyFrameIntf;
type
TFrame1 = class(TFrame, IMyFrameIntf)
Label1: TLabel;
private
{ Private declarations }
public
{ Public declarations }
procedure Insert;
end;
implementation
{$R *.fmx}
procedure TFrame1.Insert;
begin
//
end;
end.
Unit3.pas
unit Unit3;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation, FMX.Edit,
MyFrameBase;
type
TFrame2 = class(TFrame, IMyFrameIntf)
Edit1: TEdit;
private
{ Private declarations }
public
{ Public declarations }
procedure Insert;
end;
implementation
{$R *.fmx}
procedure TFrame2.Insert;
begin
//
end;
end.
Create your base class (with the additional functions that you want) from New Items dialog, Delphi Projects|Delphi File|VCL Frame, as now.
But then develop all your other frames from that base one you created by selecting from the New Items dialog Delphi Project|Inheritable Items|Your base dialog (which will have been added to this page).
Note that this is not really any different from Remy's first solution, but it explains how in practice you might do it. You are obviously doing something wrong in trying to follow Remy's instructions.
I have populated a form with required components and pasted the example code into a buttonclick event.
I have added the TStringDynArrayarray and TSearchOption type declarations but I get compilation errors as shown below.
unit dirtest;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, System.Types, Vcl.Controls, Vcl.Forms,
Vcl.Dialogs, IOUtils, Vcl.StdCtrls;
type
TStringDynArray = Array of String;
TSearchOption = (soTopDirectoryOnly, soAllDirectories);
TForm1 = class(TForm)
OpenDialog1: TOpenDialog;
Button1: TButton;
mmResults: TMemo;
cbIncludeDirectories: TCheckBox;
cbIncludeFiles: TCheckBox;
cbDoRecursive: TCheckBox;
edtPath: TEdit;
edtFileMask: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
LList: TStringDynArray;
I: Integer;
LSearchOption: TSearchOption;
begin
{ Select the search option }
if cbDoRecursive.Checked then
LSearchOption := TSearchOption.soAllDirectories
else
LSearchOption := TSearchOption.soTopDirectoryOnly;
try
{ For all entries use GetFileSystemEntries method }
if cbIncludeDirectories.Checked and cbIncludeFiles.Checked then
LList := TDirectory.GetFileSystemEntries(edtPath.Text, LSearchOption, nil;
{ For directories use GetDirectories method }
if cbIncludeDirectories.Checked and not cbIncludeFiles.Checked then
LList := TDirectory.GetDirectories(edtPath.Text, edtFileMask.Text,
LSearchOption);
{ For files use GetFiles method }
if not cbIncludeDirectories.Checked and cbIncludeFiles.Checked then
LList := TDirectory.GetFiles(edtPath.Text, edtFileMask.Text,
LSearchOption);
except
{ Catch the possible exceptions }
MessageDlg('Incorrect path or search mask', mtError, [mbOK], 0);
Exit;
end;
{ Populate the memo with the results }
mmResults.Clear;
for I := 0 to Length(LList) - 1 do
mmResults.Lines.Add(LList[I]);
end;
end.
The errors I get are thus...
[dcc32 Error] dirtest.pas(51): E2250 There is no overloaded version of 'GetFileSystemEntries' that can be called with these arguments
[dcc32 Error] dirtest.pas(56): E2250 There is no overloaded version of 'GetDirectories' that can be called with these arguments
[dcc32 Error] dirtest.pas(61): E2250 There is no overloaded version of 'GetFiles' that can be called with these arguments
Can you see what's going wrong?
Thanks
Your type declaration introduces new types for TStringDynArray and TSearchOption while functions expect types declared in in-built units ( TStringDynArray = TArray<string>; from System.Types and TSearchOption from IOUtils )
So just remove own type description
I have a problem with the MediaLibrary on Delphi.
I make this code below on my main form:
unit uPrincipal;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants,FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics,
FMX.Dialogs,
FMX.Controls.Presentation, FMX.MultiView, FMX.Objects, FMX.Layouts,
FMX.StdCtrls, System.Actions, FMX.ActnList, FMX.StdActns,
FMX.MediaLibrary.Actions, FMX.MediaLibrary, FMX.Platform, System.Messaging;
type
TfmPrincipal = class(TForm)
Layout1: TLayout;
mvMenu: TMultiView;
rctMenuPrincipal: TRectangle;
rctMenuTop: TRectangle;
rctMenuBody: TRectangle;
rctOpHome: TRectangle;
rctBodyPrincipal: TRectangle;
tbPrincipal: TToolBar;
StyleBook1: TStyleBook;
sbMenu: TSpeedButton;
sbPhoto: TSpeedButton;
ActionList1: TActionList;
TakePhotoFromLibraryAction1: TTakePhotoFromLibraryAction;
Image1: TImage;
TakePhotoFromCameraAction1: TTakePhotoFromCameraAction;
procedure TakePhotoFromLibraryAction1DidFinishTaking(Image: TBitmap);
private
{ Private declarations }
public
{ Public declarations }
end;
var
fmPrincipal: TfmPrincipal;
implementation
{$R *.fmx}
{$R *.LgXhdpiPh.fmx ANDROID}
{$R *.NmXhdpiPh.fmx ANDROID}
{$R *.iPhone.fmx IOS}
uses uLogin, uTeste;
procedure TfmPrincipal.TakePhotoFromLibraryAction1DidFinishTaking(
Image: TBitmap);
begin
Image1.Bitmap.Assign(Image);
end;
end.
When I run this on my phone, I click on the SpeedButton, and I receive an "invalid class typecast" error message.
I have added TakePhotoFromLibraryAction1 in the TActionList, and set it as the Action for the SpeedButton.
I don't know why I am getting this error.
It's a bug in your version of Delphi.
One workaround is to use a TButton instead of a TSpeedButton.
Another workaround is to remove the Action assignment from the SpeedButton, and then use the button's OnClick event to call the action's ExecuteTarget()method, passing it a different control as the Target parameter.
I have 3 units This is my main unit....
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
Uses Unit2;
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
begin
Panel1.Visible := oiNone in form2.Settings.OptVars;
Panel2.Visible := oiHint in form2.Settings.OptVars;
Panel3.Visible := oiStat in form2.Settings.OptVars;
end;
end.
This is the second unit - used by the first.
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Unit3;
type
TForm2 = class(TForm)
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
CheckBox3: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
Settings: TSettings;
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.FormCreate(Sender: TObject);
begin
Settings := TSettings.Create;
Checkbox1.Checked := oiNone in Settings.OptVars;
CheckBox2.Checked := oiHint in Settings.OptVars;
CheckBox3.Checked := oiStat in Settings.OptVars;
end;
procedure TForm2.FormDestroy(Sender: TObject);
begin
Settings.Free;
end;
end.
and this is the unit which is used by the second unit and contains the Set type which is used by both other units.
unit Unit3;
interface
Uses
winAPI.windows,system.classes;
Type
TOptions = Set Of (oiNone, oiHint, oiStat);
TSettings = class
private
fMyOption: TOptions;
public
Constructor Create;
Destructor Destroy;
property OptVars: TOptions read fMyOption write fMyOption;
end;
implementation
constructor TSettings.Create;
begin
fMyOption := [oiNone, oiHint, oiStat];
end;
destructor TSettings.Destroy;
begin
end;
end.
In the second unit, the items oiNone,oiHint, oiStat are accessible and within scope.
In the first unit, the items oiNone, oiHint and oiStat are not accessible and within scope although the Settings.OpVars which is a TOption data type is accessible.
I cannot think of a better way to describe my problem. If you put these into a project you should see the issue.
Update
After the latest edit it is clear that my first hunch was correct. You weren't using the unit that declares the type. The names have changed, but with the current edit your problem is that the main unit does not use Unit3.
You need to reference the unit that declares the set type, and its enumerated type. Add UnusedItemsReg to your uses clause in the third unit.
If you have done that then the only other explanation I can imagine is that you compile with scoped enums enabled. But I'm clutching at straws now.
I would definitely recommend that you declare the enumerated type as a named type in its own right. Certainly if you use scoped enums then you'll need to do that. But sooner or later you'll want to have that type available.
type
TOption = (oiNone, oiHint, oiStat);
TOptions = set of TOption;
If you do use scoped enums then you'll refer to then like this:
TOption.oiNone
You need Unit3 in the (a) uses clause of the main unit. Otherwise it cannot see type TOptions. That is a requirement of Delphi for visibility. It does not use the implicit references that you seem to be seeking.
Hello i get error E2197: [DCC Error] proj1.pas(34): E2197 Constant object cannot be passed as var parameter:
unit proj1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, AdvEdit;
type
TForm1 = class(TForm)
AdvEdit1: TAdvEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure SetEditText(const instr: string; out outstr: string);
begin
outstr := instr;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
SetEditText('Pippo', AdvEdit1.Text);
end;
end.
Of course, i can solve writing:
procedure TForm1.Button1Click(Sender: TObject);
var sText: string
begin
SetEditText('Pippo', sText);
AdvEdit1.Text := sText;
end;
But when i have many AdvEdit, then it is hard. Then i ask, is possible solve the problem in some mode giving directly TAdvEdit.Text as parameter in mine procedure?
Thanks very much.
I presume that Text is a property. And you cannot pass a property to a var or out parameter. You can only pass variables to parameters of those kinds.
You'll need to find a different way to write your code. You've come up with one such idea, but it seems needlessly complex to me. I cannot see anything simpler than:
AdvEdit1.Text := 'Pippo';
How could there be any code simpler than this? You need to specify at a bare minimum the following:
The target control.
That we are dealing with the Text property.
The fact that we are assigning.
The new value.
The code above does that and nothing more.