Let's say I have two forms in a delphi project, I want to be able to access form1's variables from form2. Is there anyone to declare, say a 'public' variable in form1 which can be read from all forms?
I have tried putting a variable in the public declaration
{ private declarations }
public
{ public declarations }
test: integer;
end;
and in form 2 i have
unit Unit2;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, unit1;
type
{ TForm2 }
TForm2 = class(TForm)
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.lfm}
{ TForm2 }
procedure TForm2.FormCreate(Sender: TObject);
begin
form1 //<---------- DOES NOT GET RECOGNIZED
end;
end.
I then put 'Unit1' into the uses section on Form2, but it seems I can't do that due to a circular reference. I'd like to refrain from using pointers if possible.
First, it's better to pretend that globals don't exist at all. If you start out programming with the crutch of global variables, you'll just avoid learning the very simple techniques that allow you to do without them. You're worrying about using pointers (which actually wouldn't help your problem at all), but not concerned about using globals which are actually more dangerous.
Globals are dangerous because:
They provide a link between the units that share them (a concept called tight coupling)
This link is hidden in the sense that it's not obvious the exact nature of the link without examining the detail of the code.
Your code will become littered with side effects, which is to say that when you do something in a method, other unexpected things happen as well. This increases the possibility and complexity of subtle bugs.
The cumulative effect of the above points is that even if you break a project down into 20 units, your brain still has to think about all 20 units at the same time - as if they were only1 unit. This defeats the purpose of breaking your project down into smaller manageable units in the first place.
You'll quickly find that even medium sized projects start becoming very difficult to maintain.
Circular References
Please take a look at my answer to a previous question for thoughts on dealing with circular references more generally.
In your case, you simply need to move Unit1 from the interface uses to the implementation uses.
var
Form2: TForm2;
implementation
uses
Unit1;
{$R *.lfm}
{ TForm2 }
procedure TForm2.FormCreate(Sender: TObject);
begin
Form1.test; //Is now accessible, but remember: it will cause a runtime error if Form1 hasn't been created or has already been destroyed.
end;
Sharing data without globals
You'll notice that technically you're still using globals; albeit ones created by Delphi and not your own. Here's a technique you can use to share data in a more controlled fashion.
unit Unit3;
interface
type
TSharedData = class(TObject)
public
Test1: Integer;
Test2: Integer;
end;
Then in Form1 you add the following:
implementation
uses
...
Unit3;
type
TForm1 = class(TForm)
...
public
SharedData: TSharedData;
end;
//Inside either the constructor or OnCreate event add the following line:
SharedData := TSharedData.Create;
//Inside either the destructor or OnDestroyevent add the following line:
SharedData.Free;
Then in Form2 you do something slightly different because you want to use Form1's shared data not its own "shared data".
implementation
uses
...
Unit3;
type
TForm2 = class(TForm)
...
public
Form1SharedData: TSharedData;
end;
//Nothing added to constructor/destructor or OnCreate/OnDestroy events
Finally, after creating both forms, you give Form2 a refernce to Form1's shared data:
procedure RunApplicationWithoutFormGlobals;
var
LForm1: TForm1;
LForm2: TForm2;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, LForm1);
Application.CreateForm(TForm2, LForm2);
LForm2.Form1SharedData := LForm1.SharedData;
Application.Run;
end;
The above illustrates how easily you can do away with even Delphi's global variables.
Disclaimer: Some of the code goes agaisnt generally accepted encapsulation principles, but is for illustrative purposes only.
First, if you must use globals (it's probably better not to use globals, as Craig has wisely pointed out) then you should put the globals you want to share in SharedGlobals.pas:
unit SharedGlobals;
interface
var
{variables here}
Something:Integer;
implementation
{ nothing here?}
Now use that unit, from the two units you want to share access to that variable in. Alternatively, have both reference another object, which is named something sensible, and have that object be designed, as the holder of state (variable values) that those two instances (forms or classes, or whatever) need to share.
Second idea, since your two units that you have already have dependencies on each other, you could also get around your circular dependency by using the unit that would create a circular dependency, from the implementation section instead of the interface:
unit Unit2;
interface
/// stuff
implementation
uses Unit1;
...
unit Unit1;
interface
/// stuff
implementation
uses Unit2;
the example shows Form1(main) and Form2(other) which has better use for organization;
FORM1 interface declaration only;
can be read from all forms;
interface
procedure oncreate(Sender: TObject);
implementation
uses Form2;
procedure TForm1.oncreate(Sender: TObject);
begin
Form2.test1;
//{Form2.test2} are not visible to Form1;
end;
FORM2 interface and implementation declarations;
implementation declaration are local to the unit; cannot be read from all forms;
interface
procedure test1;
implementation
procedure test2; //declared under implementation;
procedure TForm2.test1;
begin
ShowMessage('form2 test1 message');
end;
procedure TForm2.test2;
begin
ShowMessage('form2 test2 message');
end;
any procedure, function, type, variable can be local to the unit under implementation;
it also makes intellisense(Ctrl+Space) clean from declarations used only to the unit;
Related
Is it real?
For example, I have form1 and button1. I can assign to button1.onClick in design time onlyButton26Click.
TForm1 = class(TForm)
procedure Button26Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
I also have unit 2 like :
unit ProcAndFunc;
interface
uses sysutils,forms;
procedure createArrTable(Sender: TObject);
Can I assign createArrTable to button1.onClick in design time?
Thanks.
Ok. Unit1
TForm1 = class(TForm)
myButton1 : TButton;
procedure Button26Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Unit2
TForm2 = class(TForm)
myButton2 : TButton;
procedure ButtonClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Just to know how it works. What should I change in unit2 that I can assign ButtonClick to myButton1.OnClick
How to make a separate unit for event methods, which IDE allows me to assign to component events at design time? It seems that I duplicate question. But that answer does not suit me. Is there the other way?
EDIT3 (08.08.2016):
I created this TDataModule;
unit Unit3;
interface
uses
System.SysUtils, System.Classes,dialogs;
type
TDataModule3 = class(TDataModule)
procedure Click2(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
DataModule3: TDataModule3;
implementation
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TDataModule3.Click2(Sender: TObject);
begin
ShowMessage('hello world');
end;
end.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, unit3;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage('hello world');
end;
end.
And I still cant assign DataModule3.Click2 to button1.onclick in design-time. (screenshot). What do I also need to do?
Can I assign createArrTable to button1.onClick in design time?
Not at design-time, no. createArrTable() is not a member of a class, and thus is not accessible via an object pointer at run-time. So it does not satisfy the signature that is normally required for an OnClick event handler. The IDE will not allow you to assign it at design-time.
However, you can assign createArrTable() at run-time, with some minor tweaking.
The OnClick event (and most RTL/VCL events in general) is implemented using a closure, which consists of 2 pointers - a pointer to the procedure code itself, and a pointer to an object.
When the event is fired at runtime, the compiled code extracts the object pointer from the closure and calls the procedure with that object passed to the procedure's Self parameter. It is the lack of a Self parameter that prevents your current createArrTable() implementation from being used as an OnClick event handler. If you were to somehow assign it as-is, the parameter list will get corrupted at runtime.
However, if you add an explicit Self parameter to createArrTable(), you can avoid that issue, and skip the class object requirement:
procedure createArrTable(Self: Pointer; Sender: TObject);
The button will still try to pass an object pointer from the closure to Self when calling the procedue, but now you have made space for an object pointer to be passed correctly and not corrupt other parameters. The object pointer itself becomes irrelevant and can be set to anything you want it to be, even nil.
However, since createArrTable() is still not a member of a class, you cannot assign it directly to the OnClick event, but you can use a TMethod variable to facilitate the assignment, eg:
var
M: TMethod;
begin
M.Code := #createArrTable;
M.Data := WhateverYouWantSelfToBe;
Button1.OnClick := TNotifyEvent(M);
end;
Now createArrTable() will work as expected when OnClick is fired at run-time.
What should I change in unit2 that I can assign ButtonClick to myButton1.OnClick
Nothing. It is already a suitable event handler as-is. All you would have to do is add unit2 to the uses clause of the interface section in unit1 so the IDE can see ButtonClick() at design-time, then you would be able to assign Form2.ButtonClick to myButton1.OnClick. Just know that you will have to setup your project to create Form2 before Form1 at runtime, otherwise you will likely crash the code when the DFM streaming system tries to access ButtonClick via an invalid Form2 object when assigning the value of Form1.myButton.OnClick.
To avoid having to change the creation order of the Forms, you can place the event handlers into a separate TDataModule instead, and then link its methods to events on your Form. Just make sure the TDataModule is created at runtime before the Form is created:
Access DataModule's event from another Form (delphi design-time)
You cannot ever assign that procedure as an event handler since it is not a method of a class. So you could make the procedure be a method of another class and make sure that there was a global instance of that class visible to the designer.
However that would be a very poor solution in my view. It is best practice to decouple UI logic from application logic where possible. The clean solution is to simply call the procedure from an event handler. That means that your procedure is still available to other parties in its current form and does not need to be shoe-horned into looking like an event handler. You could also likely remove the Sender parameter which I suspect you are not using in any case.
For example, I have a couple of functions written for my form. Now, I need the exact same functions in another form. So, how can I share them between the two forms? Please, provide a simple example if possible.
Don't put them in your form. Separate them and put them in a common unit, and add that unit to the uses clause where you need access to them.
Here's a quick example, but you can see many of the Delphi RTL units (for instance, SysUtils) that do this. (You should learn to use the VCL/RTL source and the demo apps that are included in Delphi; they could answer many of the questions you've posted more quickly than waiting for an answer here.)
SharedFunctions.pas:
unit
SharedFunctions;
interface
uses
SysUtils; // Add other units as needed
function DoSomething: string;
implementation
function DoSomething: string;
begin
Result := 'Something done';
end;
end.
UnitA.pas
unit
YourMainForm;
uses
SysUtils;
interface
type
TMainForm = class(TForm)
procedure FormShow(Sender: TObject);
// other stuff
end;
implementation
uses
SharedFunctions;
procedure TMainForm.FormShow(Sender: TObject);
begin
ShowMessage(DoSomething());
end;
end.
In more recent versions of Delphi than Delphi 7, you can create the functions/methods in a record instead:
unit
SharedFunctions;
interface
uses
SysUtils;
type
TSharedFunctions = record
public
class function DoSomething: string;
end;
implementation
function TSharedFunctions.DoSomething: string;
begin
Result := 'Something done';
end;
end;
UnitB.pas
unit
YourMainForm;
uses
SysUtils;
interface
type
TMainForm = class(TForm)
procedure FormShow(Sender: TObject);
// other stuff
end;
implementation
uses
SharedFunctions;
procedure TMainForm.FormShow(Sender: TObject);
begin
ShowMessage(TSharedFunctions.DoSomething());
end;
end.
If you need forms. You could use inherited forms. Creating a form that inherit the functions of a parent form.
The most interesting. Any changes in the parent form is reflected a change in inherited forms. You even can inherit form controls (tbutton, tlabel, etc...).
In GUI Delphi7. Option "new form", option "inherited from a existing form".
Example:
//MainForm.pas
type
TMainForm = class(TForm)
procedure MiFunction();
.
.
end;
//ChilForm.pas
type
TChildForm = class(TMainForm)
.
.
end;
I want to access a main form variable from a class that is called from the main from.
Something like this:
Unit1:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,Unit2, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
public
end;
var
Form1: TForm1;
Chiled:TChiled;
const
Variable = 'dsadas';
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Chiled.ShowMainFormVariable;
end;
end.
Unit2:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TChiled = class
private
public
procedure ShowMainFormVariable;
end;
var
Form1: TForm1;
implementation
procedure TChiled.ShowMainFormVariable;
begin
ShowMessage(Form1.Variable);
end;
end.
if in Unit2 i add to uses Unit1 an circular errors pops up.
How to make the Unit1 to be GLOBAL?
As other answers tell, you should use one of the units in implementation section.
Suppose you chose in 'unit2' you'd use 'unit1' in implementation. then you need to devise a mechanism to tell the 'TChiled' how to access 'Form1'. That's because since you haven't used 'unit1' in interface section of 'unit2', you cannot declare the 'Form1:TForm1' variable in interface section. Below is just one possible solution:
unit2
type
TChiled = class
private
FForm1: TForm;
public
procedure ShowMainFormVariable;
property Form1: TForm write FForm1;
end;
implementation
uses
unit1;
procedure TChild.ShowMainFormVariable;
begin
ShowMessage((FForm1 as TForm1).Variable);
end;
then in unit1 you can set the Form1 property of TChiled before calling TChiled's method:
procedure TForm1.Button1Click(Sender: TObject);
begin
Chiled.Form1 := Self;
Chiled.ShowMainFormVariable;
end;
the simplest solution is to add Unit1 to a uses clause inside Unit2's implementation section as this gets around the circular reference.
However I'd suggest that this design is flawed. It is hard to see what you are trying to achieve with the sample code so it is difficult to offer any real advice.
Well, the simple naive answer is that you should add Unit1 to the uses clause of the implementation section of Unit2:
unit Unit2;
......
implementation
uses
Unit1;
.....
You can't add it to the uses clause in the interface section of Unit2 since that would create a circular reference at the interface section. In order words, the interface of Unit1 would uses Unit2, and the interface of Unit2 would use Unit1. The language does not allow that. The common solution is to use one of the units at the implementation level.
Having said that, your code is rather confused and fails in many other ways. Your problems run deeper than the circular reference. For example, what do you mean by Form1.Variable? The constant Variable is not a member of TForm1. You declare two global variables named Form1 of type TForm1. Why do you do that?
Also, you have spelled child incorrectly.
I generally create a Data Module (or any type of non-visual container) to share global variables. This way both units can use the variable without a circular reference.
i can't figure out the formatting rules here .. too many lines of code in my example to add 4 spaces to each line, so here is the link to the code i need help with
http://nitemsg.blogspot.com/2011/01/heres-unit-written-in-delphi-7-that-you.html
The problem I have is that I don't know enough about delphi to use this code with a form.
I am a drag and drop programmer only.
An example with a showmessage('friendly name =' + ... ) when a USB device is detected is what I need.
cheers,
If you are only familiar with drag-and-drop programming, and don't know much about objects or other units, then you need to get yourself familiarized with using objects other than auto-created forms and the components you drop in them.
The code at this link is an entire unit. You need to create a new Unit in your project (File > New > Unit). It will look something like this:
unit Unit1;
interface
implementation
end.
Now when you save the unit, the name of the unit will automatically change to the filename (without the extension) like this:
unit MahUSB;
interface
implementation
end.
In this example, you should use the same unit name as that source you're trying to use. Save the unit as 'MahUSB.pas', and should be in the same folder as the rest of your project (or elsewhere, just a suggestion). Copy/Paste all the code from that website and replace everything in this unit now.
Now in order to actually use this, you need to create an instance of this object. ONLY ONE INSTANCE (I say that just because by the looks of this, there's no need for more than one).
Very important: Seeing as you are not familiar with objects, let me quickly explain something. Objects need to be created in order to work. At the same time, anything that's created also needs to be free'd when you're done with it. In this case, we will create this object when your application starts, and free the object when your application closes.
Now on your MAIN FORM (not any other forms) you need to put an event handler for both OnCreate and OnDestroy. You also need to declare a variable to represent this object. In the declaration of your main form, add a variable 'USB' with the type of this object. Make sure that goes under the 'private' or 'public' section, either one is ok. Also make sure you declare the "MahUSB" unit at the top of your main unit in the uses clause.
Declaring the object in your main form:
type
TForm1 = class(TForm)
private
USB: TUsbClass;
public
end;
Creating/freeing object when your app starts/closes:
procedure TForm1.FormCreate(Sender: TObject);
begin
USB:= TUsbClass.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if assigned(USB) then USB.Free;
end;
Now we're not done yet. Now we need to add the event handlers. Notice at the top of this unit you got, there are two types called TOnDevVolumeEvent and TOnUsbChangeEvent. These are event types. The parameters in the event handlers must be identical to the parameters declared in these types. So now in your main form, declare these event handler procedures...
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
USB: TUsbClass;
procedure VolumeEvent(const bInserted : boolean; const sDrive : string);
procedure ChangeEvent(const bInserted : boolean;
const ADevType,ADriverName, AFriendlyName : string);
public
end;
Now just one more thing we have to do before this will work. The USB object needs to know what event handlers to use, therefore, we need to assign these procedures to the events. Upon your form's creation, we need to assign these events...
procedure TForm1.FormCreate(Sender: TObject);
begin
USB:= TUsbClass.Create;
USB.OnUsbChange:= Self.ChangeEvent;
USB.OnDevVolume:= Self.VolumeEvent;
end;
When all is said and done, your main form unit should look something like this:
unit uUSBTest;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, MahUSB;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
USB: TUsbClass;
procedure VolumeEvent(const bInserted : boolean; const sDrive : string);
procedure ChangeEvent(const bInserted : boolean;
const ADevType,ADriverName, AFriendlyName : string);
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ChangeEvent(const bInserted: boolean; const ADevType,
ADriverName, AFriendlyName: string);
begin
ShowMessage('Change event for "'+AFriendlyName+'"');
end;
procedure TForm1.VolumeEvent(const bInserted: boolean;
const sDrive: string);
begin
ShowMessage('Volume event for "'+sDrive+'\"');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
USB:= TUsbClass.Create;
USB.OnUsbChange:= Self.ChangeEvent;
USB.OnDevVolume:= Self.VolumeEvent;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if assigned(USB) then USB.Free;
end;
end.
And there you are! You will have these two event handler procedures where you can further handle either of those two events.
I have the following code which is working, but I don't understand it 100% (please see the comments from code):
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TMyRec=record
a:Integer;
b:String;
end;
TRecArray=array of TMyRec;
PRecArray = ^TRecArray;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
v1:TRecArray;
procedure Test(a:PRecArray);
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
SetLength(v1,3);
v1[0].b:='test1';//set the first value
Test(PRecArray(v1));//call method to change the value assigned before
end;
procedure TForm1.Test(a: PRecArray);
begin
ShowMessage(v1[0].b);//shows test1
try
a^[0].b:='test2' //this is raising an error...
except
end;
PRecArray(#a)^[0].b:='test3';//this is working...
ShowMessage(v1[0].b);//shows test3
end;
end.
I don't understand why 'a^[0].b:='test2' is raising an error.
Thank you!
Your 'Test' procedure expects a 'PRecArray', but you're passing a 'TRecArray' to it. Try calling it like
Test(#v1);//call method to change the value assigned before
Typecasting a 'TRecArray' to a 'PRecArray' will not make it a 'PRecArray'. (Note: your 'test3' will fail then of course.)
I see several things that are suspicious.
1
There is hardly ever a need to take a pointer to a dynamic array, as dynamic array variables are already pointers (well, references).
To pass such an array to a function or procedure, use var parameters:
procedure TForm1.Test(var a: TRecArray);
Now you don't have to use pointer syntax to access the array:
a[0].b := 'test2';
2
You call Test with:
Test(PRecArray(v1));
In your original, Test took a PRecArray, but you are not passing one (you are passing a TRecArray), so you should have done:
Test(#v1); // or Test(Addr(v1));
Applying my change above, where Test has a var parameter, simply use:
Test(v1);
3
Ok, this is probably not suspicous, but I'd like to plug my article Addressing Pointers, about pointers for Delphi programmers. It explains many of the issues you seem to have.
You could replace the
procedure TForm1.Test(a: TPointerArrayRec);
with
procedure TForm1.Test(var a: TArrayRec);
it's simpler and you don't have to use the deprecation dereference operator ^.