How to instantiate different frame types? - delphi

I'm here again with frames.
I've this main form:
It's only a simple form created in order to understand the use of frames.
With the two buttons on the top of the form I would like to open this two frames:
Frame1
and Frame2
here is the code the simple code of the first frame:
unit AppFrame1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls;
type
TFrame1 = class(TFrame)
lblFrame1: TLabel;
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.
and here is the code of the second frame:
unit AppFrame2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls;
type
TFrame2 = class(TFrame)
lblFrame2: TLabel;
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.
So nothing special in the two frames.
in order to open the frames from the main form I've created an interface like this:
unit FramesManager;
interface
uses
Vcl.Forms, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Controls;
type
TFrameClass = class(TFrame)
end;
IFrameManager = interface
['{A00E0D1B-3438-4DC4-9794-702E8302B567}']
procedure CreateGenericFrame(AParentPanel: TPanel; AFrameClass: TFrameClass);
procedure DestroyGenericFrame();
end;
TFrameManager = class(TInterfacedObject, IFrameManager)
private
FGenericFrame: TFrameClass;
procedure CreateGenericFrame(AParentPanel: TPanel; AFrameClass: TFrameClass);
procedure DestroyGenericFrame();
public
property Frame: TFrameClass read FGenericFrame write FGenericFrame;
end;
implementation
{ TFrameManagement }
procedure TFrameManager.CreateGenericFrame(AParentPanel: TPanel;
AFrameClass: TFrameClass);
begin
FGenericFrame := AFrameClass.Create(AParentPanel);
FGenericFrame.Parent := AParentPanel;
FGenericFrame.Align := alClient;
end;
procedure TFrameManager.DestroyGenericFrame;
begin
FGenericFrame.Free;
end;
end.
At this point my intension is to use the interface for create the two frames but I can't realize how to reach this task.
My main form code is this:
unit Main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.ExtCtrls, FramesManager, Vcl.StdCtrls, AppFrame1, AppFrame2;
type
TfrmMain = class(TForm)
pnlCommands: TPanel;
pnlFrames: TPanel;
btnCrtFrame1: TButton;
btnCrtFrame2: TButton;
procedure FormCreate(Sender: TObject);
procedure btnCrtFrame1Click(Sender: TObject);
procedure btnCrtFrame2Click(Sender: TObject);
private
FFrame: IFrameManager;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FFrame := TFrameManager.Create;
end;
procedure TfrmMain.btnCrtFrame1Click(Sender: TObject);
begin
FFrame.CreateGenericFrame(pnlFrames, TFrame1);
end;
procedure TfrmMain.btnCrtFrame2Click(Sender: TObject);
begin
FFrame.CreateGenericFrame(pnlFrames, TFrame2);
end;
end.
When I try co compile the project I receive this errors:
[dcc32 Error] Main.pas(41): E2010 Incompatible types: 'TFrameClass' and 'class of TFrame1'
[dcc32 Error] Main.pas(46): E2010 Incompatible types: 'TFrameClass' and 'class of TFrame2'
So I would like to understand how to create the two frames from the main.
How can I assign the right object type to the TFrameClass?
I've tought about generics, but I have not idea on how to implement this kind of interface in order to open a "generic" frame that can be created form the main when the use choose to open it.
I hope I have explained clearly my problem, but I know that it may seem complicate to understand.

type
TFrameClass = class(TFrame)
end;
Your TFrameClass declaration is wrong. Right now it is declared as a descendant of TFrame which is just another class.
What you need is a class reference:
type
TFrameClass = class of TFrame;
Because both TFrame1 and TFrame2 descend from TFrame, both can be put into a TFrameClass variable.
But I am pretty sure this TFrameClass already exists somewhere in the VCL in which case you do not have to redeclare this type.
Subsequently, the type of FGenericFrame private field needs to become TFrame instead of TFrameClass.
(Also, this has nothing to do with Generics.)

Related

Put class functions to multiple units in Delphi?

I am using Delphi XE3. Currently there are too many functions in my unit, and one of them is very long. So I want to put that long function to another unit. Is that possible?
I try as follows:
Unit1:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
procedure Test;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
...
Unit2:
unit Unit2;
interface
implementation
uses Unit1;
procedure TForm1.Test;
begin
end;
end.
But compile fails, said "[dcc32 Error] Unit1.pas(16): E2065 Unsatisfied forward or external declaration: 'TForm1.Test'"
You cannot move a class method's implementation to another unit. However, you can refactor its inner code into its own function in another unit, and then call that function from your class method, eg:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
procedure Test;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
Unit2;
procedure TForm1.Button1Click(Sender: TObject);
begin
Test;
end;
procedure TForm1.Test;
begin
Unit2.DoTest;
end;
end.
unit Unit2;
interface
procedure DoTest;
implementation
procedure DoTest;
begin
...
end;
end.
Functions belonging to the same class must be in one unit (your error reason)
your declaration and implementation must be in the same unit
if you want to split it up, you need to make them be in different classes

How to solve the CYCLIC relation issue between 2 or more Units in Delphi?

I'm using 2 Units in Delphi 2010: Unit1 has a Form with a TEdit and a Button, this Button will call a procedure from Unit2 (Unit2 doesn't have a form). That procedure will change the 'caption' property of the TEdit of Unit1, I tried to put Unit2 in Unit1 "Uses" to access the procedure, and put Unit1 in the "Uses" of Unit2 to to access the TEdit in Unit1, but this relation is cyclic.
I'm not sure what can I do to solve this, any suggestion?
There are many ways to do this, and which one is most appropriate depends crucially on the precise circumstances, so technically this question is too broad for Stack Overflow, I suspect.
Method 1
Still, let me just show you one very simple approach. This might not be a good approach, but it is close to what it seems you are trying to do.
Create a new VCL application.
Drop a TButton and a TEdit control on the main form (TForm1 in Unit1).
Create a new unit, Unit2:
unit Unit2;
interface
procedure SetCaption;
implementation
uses
Unit1;
procedure SetCaption;
begin
Unit1.Form1.Edit1.Text := 'Hello, World!';
end;
end.
On the main form, use this event handler for the button's OnClick event:
procedure TForm1.Button1Click(Sender: TObject);
begin
Unit2.SetCaption;
end;
after you have added Unit2 to the implementation section's uses clause. In full,
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Unit2.SetCaption;
end;
end.
Notice how the units may use each other if referenced only in the implementation sections. Also notice we use the global Form1 variable in Unit1 to refer to the automatically created instance of TForm1. In many (most) cases, you don't want to use automatically created forms, but that is a different story (I could write a 100-page Delphi textbook here!).
Method 2
Also, let me reiterate the fact that there are many other ways to accomplish this result. For instance, this is arguably a better approach:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Unit2.SetCaption(Edit1);
end;
end.
unit Unit2;
interface
uses
StdCtrls;
procedure SetCaption(AEdit: TCustomEdit);
implementation
procedure SetCaption(AEdit: TCustomEdit);
begin
AEdit.Text := 'Hello, World!';
end;
end.
Method 3
Arguably even better:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text := Unit2.GetText;
end;
end.
unit Unit2;
interface
function GetText: string;
implementation
function GetText: string;
begin
Result := 'Hello, World!';
end;
end.

Scoping issue with Set types

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.

Delphi fire frame events from datamodule

I've an application made by a main form, a frame and a datamodule.
When the main form is created I create also the frame wich contains the fields present in a table that are in the datamodule.
So the main form code is this:
unit main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
FraDtl;
type
TfrmMain = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FFraDtl: TfraDetail;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FFraDtl := TfraDetail.Create(Self);
FFraDtl.Parent := Self;
end;
end.
The code of the frame is this:
unit FraDtl;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
database, cxGraphics, cxControls, cxLookAndFeels, cxLookAndFeelPainters,
cxContainer, cxEdit, cxTextEdit, cxDBEdit, Vcl.StdCtrls, dxSkinsCore,
cxNavigator, cxDBNavigator;
type
TfraDetail = class(TFrame)
lblCognome: TLabel;
edtCognome: TcxDBTextEdit;
lblNome: TLabel;
edtNome: TcxDBTextEdit;
cxDBNavigator1: TcxDBNavigator;
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.
And the code of the datamodule is this:
unit database;
interface
uses
System.SysUtils, System.Classes, Data.DB, DBAccess, MSAccess, MemDS,
dxSkinsCore, cxClasses, cxLookAndFeels, dxSkinsForm;
type
TdmData = class(TDataModule)
Connection: TMSConnection;
tblAutori: TMSTable;
dsAutori: TMSDataSource;
fAut_IdAutore: TIntegerField;
fAut_Cognome: TStringField;
fAut_Nome: TStringField;
fAut_Nominativo: TStringField;
SkinController: TdxSkinController;
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
procedure dsAutoriStateChange(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
dmData: TdmData;
implementation
{$R *.dfm}
procedure TdmData.DataModuleCreate(Sender: TObject);
begin
tblAutori.Open;
end;
procedure TdmData.DataModuleDestroy(Sender: TObject);
begin
tblAutori.Close;
end;
procedure TdmData.dsAutoriStateChange(Sender: TObject);
begin
// Some stuff
end;
end.
I would like to call a procedure present in the frame in the dsAutoriStateChange event is fired. There is a way to do this?
I would like to call a procedure present in the frame in the dsAutoriStateChange event is fired.
You can do this using code like this:
unit database;
....
implementation
uses FraDtl;
procedure TdmData.dsAutoriStateChange(Sender: TObject);
begin
FraDtl.SomeProcedure;
end;
Or if you want to access the actual instantiated frame you access the main unit.
unit database;
....
implementation
uses main; //circular reference allowed in implementation
procedure TdmData.dsAutoriStateChange(Sender: TObject);
begin
frmMain.FrameDetail.DoSomething(Sender);
end;
In that case you need to add an public property to frmMain exposing the frame.
TfrmMain = class(TForm)
...
public
property FrameDetail: TFraDetail read FFraDtl;
Warning
These kinds of hard coupling between data module and form are considered bad programming practice.
Loose coupling is good
Hard coupling is bad
It would be better to use an event handler for a datasource inside the mainform, or if that's not possible to add additional properties to TdmData that get updated by the main form as needed.
The event handler dsAutoriStateChange can then access these properties instead of having to poke around in the private parts of the frame.

Delphi generic frame

I'm still here with a questione about Delphi frames.
I would like to create an application that use various type of frames in order to manage different database tables, so trying to understand how to do this kind of task I've create a simple Delphi Form:
unit main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, Vcl.ExtCtrls, FramesManagement;
type
TfrmMain = class(TForm)
pnlCommands: TPanel;
pnlFrames: TPanel;
btnFrame1: TButton;
btnFrame2: TButton;
procedure FormCreate(Sender: TObject);
procedure btnFrame1Click(Sender: TObject);
procedure btnFrame2Click(Sender: TObject);
private
FFrame: IFrameManagement;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
uses Frame1, Frame2;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FFrame := TFramemanagement.Create;
end;
procedure TfrmMain.btnFrame1Click(Sender: TObject);
begin
FFrame.CreateGenericFrame(pnlFrames, TFrame(Frame1.TFra1));
end;
procedure TfrmMain.btnFrame2Click(Sender: TObject);
begin
FFrame.CreateGenericFrame(pnlFrames, TFrame(Frame2.TFra2));
end;
end.
This form make use of an interface declared as following:
unit FramesManagement;
interface
uses
Vcl.Forms, Vcl.StdCtrls, Vcl.ExtCtrls, Frame1, Frame2;
type
IFrameManagement = interface
['{A00E0D1B-3438-4DC4-9794-702E8302B567}']
procedure CreateGenericFrame(ParentPanel: TPanel; FrameName: TFrame);
end;
TFrameManagement = class(TInterfacedObject, IFrameManagement)
private
genericFrame: TFrame;
procedure CreateGenericFrame(ParentPanel: TPanel; FrameName: TFrame);
end;
implementation
procedure TFrameManagement.CreateGenericFrame(ParentPanel: TPanel;
FrameName: TFrame);
begin
genericFrame := FrameName.Create(ParentPanel);
genericFrame.Parent := ParentPanel;
end;
And here there are the two frames.
Frame 1:
unit Frame1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls;
type
TFra1 = class(TFrame)
txtFrame1: TStaticText;
txtFrameType: TStaticText;
lblFrameType: TLabel;
private
public
end;
implementation
{$R *.dfm}
end.
and Frame 2:
unit Frame2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls;
type
TFra2 = class(TFrame)
txtFrame2: TStaticText;
txtFrameType: TStaticText;
lblFrameType: TLabel;
private
public
end;
implementation
{$R *.dfm}
end.
This is all the code, but When I run the application and I try to create the first or the second frame i receive an error like this:
I've thought the solution may be the use of generics but I don't know how to use them. Is my thought right or there is another way to reache this gol?
Can Anyone help me?
procedure TFrameManagement.CreateGenericFrame(ParentPanel: TPanel; FrameName: TFrame);
begin
genericFrame := FrameName.Create(ParentPanel);
genericFrame.Parent := ParentPanel;
end;
Here FrameName is an instance and you are calling the constructor of that instance. You are not creating a new instance as you intend to do.
You need to use meta classes.
type
TFrameClass = class of TFrame;
procedure TFrameManagement.CreateGenericFrame(ParentPanel: TPanel; FrameClass: TFrameClass);
begin
genericFrame := FrameClass.Create(ParentPanel);
genericFrame.Parent := ParentPanel;
end;
You can call this like so:
FFrame.CreateGenericFrame(pnlFrames, Frame2.TFra2);

Resources