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.
Related
Need to access some components right after program start but I have found that doing so from form's onCreate event is bad because at the moment they may still be unavailable (access violation occurs). Can not find onCreate event in any component. Am I missing something?
Here is code. Empty form with ValueListEditor.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
ValueListEditor1: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure: Load;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Load;
end;
procedure Load;
begin
(Application.MainForm.FindComponent('ValueListEditor1') as TValueListEditor)
.Strings.LoadFromFile('c:\li');
end;
end.
The problem is not because your component hasn't been created yet, because it has been. The real problem is because the Application.MainForm property hasn't been assigned yet when your main Form's OnCreate event is fired, so you are calling FindComponent() on a nil Form pointer.
Since Load() is merely accessing a member of TForm1 then Load() should also be a member of TForm1 as well, and then you can call it, and thus access your component, via the implicit Self pointer, which is valid during the Form's OnCreate event, eg:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
ValueListEditor1: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure LoadValues;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadValues;
end;
procedure TForm1.LoadValues;
begin
ValueListEditor1.Strings.LoadFromFile('c:\li');
end;
end.
If, for whatever reason, Load() must be a standalone procedure, then at least have it use your global Form1 variable, which the call to Application.CreateForm() will assign before the main Form's OnCreate event is fired, eg:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
ValueListEditor1: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure LoadValues;
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure Load;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadValues;
end;
procedure TForm1.LoadValues;
begin
ValueListEditor1.Strings.LoadFromFile('c:\li');
end;
procedure Load;
begin
if Form1 <> nil then
Form1.LoadValues;
end;
end.
Alternatively, you can fallback to looking for the Form1 object in the Screen.Forms[] array, eg:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
ValueListEditor1: TValueListEditor;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure LoadValues;
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure Load;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadValues;
end;
procedure TForm1.LoadValues;
begin
ValueListEditor1.Strings.LoadFromFile('c:\li');
end;
procedure Load;
var
I: Integer;
Frm: TForm;
begin
for I := 0 to Screen.FormCount-1 do
begin
Frm := Screen.Forms[I];
if Frm is TForm1 then
begin
TForm1(Frm).LoadValues;
Exit;
end;
end;
end;
end.
Use the form's onShow() -event.
But be aware, that the onShow() -event is called every time the form is shown, not only the first time.
In your form, you should override the DoShow method and insert your code there. Since DoShow is called everytime the form changes from invisible to visible, you should also add a boolean variable in your form and check in the DoShow override, if it is false. In that case, it is the first time DoShow is called. Then set the variable to true and do whatever you need to do the first time.
Please note that within DoShow the form is not visible yet. If you need to do something once the form is made visible, you can post a custom message from DoShow and put your code in the corresponding message handler. At the time it is executed, the form just became visible.
const
WM_APP_STARTUP = WM_USER + 1;
type
TForm1 = class(TForm)
protected
FInitialized : Boolean;
procedure DoShow; override;
procedure WMAppStartup(var Msg: TMessage); message WM_APP_STARTUP;
end;
implementation
procedure TForm1.DoShow;
begin
// Form is NOT visible but will soon be
if not FInitialized then begin
FInitialized := TRUE;
// Insert your code here
PostMessage(Handle, WM_APP_STARTUP, 0, 0);
end;
inherited DoShow;
end;
procedure TForm1.WMAppStartup(var Msg: TMessage);
begin
// The form is now visible
// Insert your code here
end;
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.
I have two units in my project as follow:
1 - Connexion unit:
unit Connexion;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TFConn = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
FConn: TFConn;
implementation
{$R *.dfm}
uses MainForm;
procedure TFConn.Button1Click(Sender: TObject);
begin
if not Assigned(FMain) then
begin
FMain := TFMain.CreateNew(Application);
FMain.OnClose := FMain.FormClose;
FMain.ShowModal;
end;
end;
end.
2 - MainForm unit :
unit MainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TFMain = class(TForm)
Constructor FormCreate(Sender: TObject);overload;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
end;
Var
FMain : TFMain;
implementation
Constructor TFMain.FormCreate(Sender: TObject);
var B : TButton;
begin
inherited;
B := TButton.Create(Self);
B.Parent := Self;
B.Caption := 'Button2';
end;
procedure tfmain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FMain := Nil;
end;
end.
The problem is that the procedure FormCreate in MainForm unit not working , I know that I miss something in the declaration , because the procedure should fire while the creation of the FMain form .
The application run without any error , but it should create B button on the FMain form.
How can I do that?
Constructor FormCreate(Sender: TObject);overload;
is wrong. It should be:
procedure FormCreate(Sender: TObject);
The other problem is that you must set the OnCreate event to refer to FormCreate. Do this in the events page of the object inspector.
That will also need you to have a dfm file for this form which you appear not to have. Once you restore the dfm file you can set the OnClose event handler in the same way. You'd need to switch to Create rather than CreateNew.
You can't set the OnCreate event handler in code because the form has already been created.
If there is a good reason not to have a dfm file and do everything in code then you could need to add a constructor. Do that by overriding the virtual constructor:
constructor Create(AOwner: TComponent); override;
Finally, very much of the code in the question looks dubious, shall I saw. Strange use of global variables. Odd naming. Setting important events like OnClose from outside the class. Testing against nil that suggests weak design. I think there are problems ahead.
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.
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.)