I need to save and load some properties to a database and I am stuck with this.
I have a form with several methods and a button. button.onclick event is assigned to one of the form's methods.
I need to get the name of the assigned method as string (just like Object inspector "form1.proc1") and save it to the database. Later I need to get the method name from the database and assign button.onclick to the corresponding form's method.
Is this possible at all?
Form1 = class(TForm)
...
procedure proc1(Sender: TObject);
procedure proc2(Sender: TObject);
procedure proc3(Sender: TObject);
Button1.OnClick = readMethodNameFromDatabase;
...
saveMethodToDatabase(Button1.OnClick);
You can obtain a method, given its name, like this:
function TForm1.MethodFromName(const Name: string): TNotifyEvent;
begin
TMethod(Result).Data := Self;
TMethod(Result).Code := MethodAddress(Name);
if TMethod(Result).Code=nil then
raise Exception.CreateFmt('Count not find method named %s', [Name]);
end;
This is the mechanism that the RTL uses when reading your .dfm files. It relies on the method being published.
You can call it like this:
Button1.OnClick := TNotifyEvent(MethodFromName('Button1Click'));
Naturally you'd substitute a database read in the final code.
As for the second part of your question, you can get the name of an event handler with this code:
MethodName(#Button1.OnClick);
Related
i'm just trying to make a new procedure so i don't need to rewrite it again.
i'm very sorry cause i'm new in delphi. i just wanna make a simple code on writing dnname.text but i don't wanna write it in every event.
like this
procedure link ;
var
dbnam : string;
form : Tform1;
begin
dbnam := 'squire';
form.dnname.text := dbnam;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
link;
end;
procedure Tmain.activate(Sender: TObject);
begin
link;
end;
procedure Tmain.datachange(Sender: TObject; Field: TField);
begin
link;
end;
but i've got error on 0074A43C6 from Form1.
The variable declaration
var
form : TForm1
only tells the compiler which type (TForm1) the variable has, but not which instance of this form should be used (there could be many forms of type TForm1 instantiated). In Delphi, you have to always initialize local variables before you use them.
You have a couple of options:
Do not use a local variable, but use the global form instance instead (it should be called Form1 in your case). But in this case your utility function is bound to a specific form instance, which is usually not a good idea.
Implement your utility function as a method of class TForm1. In this case you can reference the form instance implicitly or using Self.
Pass the form instance as a parameter to your utility function.
The default, Delphi generated, code for a new form will look something like this:
type
TForm1 = class(TForm)
{ lots of stuff }
end
This describes what "TForm1" looks like but does not create an "instance" of one.
(think of an "instance" as a UFO: just because you told me what the UFO looked like doesn't mean that there is a UFO. You have to actually create one for me to think you're not crazy)
You will also see something like the following in the same file:
var
Form1: TForm1;
This allocates some memroy to store an instanance of TForm1, but, also, does not create one.
In your .dpr file you may see something like this:
Application.CreateForm(TForm1, Form1);
That creates and instance of TForm1 and stores it in the variable Form1.
You can also do a similar thing manually:
Form1 := TForm1.Create(nil)
That also creates an instance of TForm1 and stores it in the variable Form1.
Your (slightly simplified) supplied code, looks like this:
procedure link;
var
form1: TForm1:
begin
form1.dnname.text := 'squire';
end;
In the context of procedure "link", "form1" has not been assigned a value, therefore it will contain a random value
(this is not entirely true, but will surfice for now)
You will either need to assign "form1" a (valid) value or ensure that "form1" already references a valid value.
The following may work for you:
procedure link;
begin
form1.dnname.text := 'squire';
end;
But please, please, take the time to understand what is happening.
There will be a lightbuld moment.
And I can assure you that every single super-high-karma stack overflow poster has had that lightbulb moment for themselfs...
Local variable form is not initialized here, so Access Violation occurs.
Would you transform this procedure to TForm method - to freely using form components?
Edit the body for more details.
I have a Form called ENP, defined in the EnpView unit. The ENP form, is created and shown from the handler event of a click a toolbar item in the Main Form (TPrincipal).
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(self).Show();
end;
Enp form knows (in public declaration) clearGrid() message.
ENP Form have a TStringGrid called StringGrid. And a button called "Add". When click "Add" button other Form is created and shown: AddEnp form, defined in EnpViewAdd unit.
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(self).Show();
end;
The AddEnp form, have any TEdits. The values of the inputs must be adding in the EnpView.StringGrid.
I try this:
implementation
uses
EnpView, Main;
procedure TAddEnp.AgregarClick(Sender: TObject);
begin
{ Agrego el nodo xml }
Globals.Xml.agregarMuestra(muestra.Text);
Globals.Xml.insertEnp(muestra.Text,golpes.Text,metros.Text);
{ Send messages to EnpView Form }
ENP.clearGrid();
ENP.populateGrid();
end;
ClearGrid messages fails in line 1, with access violation:
procedure TENP.clearGrid();
begin
Self.StringGrid.RowCount := 2;
Self.StringGrid.Rows[1].Clear();
end;
The clearGrid methods works if is send within the class. Any ideas ?.
Create a property named, for instance, ENPForm in TAddENP and assign the ENP form just after creating it. Declare it as follows:
TAddENP = class(TForm)
private
FENPForm: TENP;
// all of your already existing declarations
public
property ENPForm: TENP read FENPForm write FENPForm;
end;
Now that you have a possible reference to ENP form, you can use it as you like.
During the creation of TAddENP form, do as follows:
procedure TENP.opAgregarClick(Sender: TObject);
var
addForm: TAddENP;
begin
addForm := TAddEnp.Create(Self);
addForm.EnpForm := Self;
addForm.Show;
end;
Now you created the second form and gave it a secure reference to the first one. They can now talk to each other safely.
I advise you to avoid having one form operating other oneĀ“s components, because this increses the dependency between them. Instead, declare public methods to do that, so the forms will depend on their interfaces, not their implementations.
I hope this helps.
From your question (I added code-style to make it more clear):
I have a Form called ENP, defined in the EnpView unit. The ENP form,
is created and shown from the handler event of a click a toolbar item
in the Main Form (TPrincipal).
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(self).Show();
end;
This does nothing with your ENP form variable.
You create an instance of the TENP form class and show it using Show, but the ENP variable is not assigned.
You cannot assign the instance to the ENP variable, as each button click creates a new instance (so you have multiple instances of TENP) around.
Then you create a convoluted depedency of a TAddEnp instance and the (never assinged ENP variable).
You do this by creating a TAddEnp instance (why TAddEnp here, and not TAddENP?) show it using Show (giving the users the opportunity to go back to the TENP instance and again click on the opAgregar button to create more instances of TAddEnp):
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(self).Show();
end;
Followed by having the TAddEnp depend on the ENP variable:
procedure TAddEnp.AgregarClick(Sender: TObject);
begin
//...
ENP.clearGrid();
ENP.populateGrid();
end;
This will indeed fail:
ClearGrid messages fails in line 1, with access violation:
procedure TENP.clearGrid();
begin
Self.StringGrid.RowCount := 2;
Self.StringGrid.Rows[1].Clear();
end;
The reason is that ENP is not assigned (by default it will be nil), so inside clearGrid, the Self will also be nil.
Solutions you could implement
Keep a single instance of TENP and TAddEnp around, using ShowModal to force modality to prevent the user from clicking on the same buttons multiple times.
Keep your existing Show behaviour, but binding each TAddEnp instance to the TENP instance it was created from.
For the first solution, your code will become this:
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
ENP := TENP.Create(Application);
ENP.ShowModal();
ENP.Release();
ENP := nil;
end;
and
procedure TENP.opAgregarClick(Sender: TObject);
begin
AddEnp := TAddEnp.Create(Application);
AddEnp.ShowModal();
AddEnp.Release();
AddEnp := nil;
end;
The second will take more effort, as you need to prevent the use of the existing variables.
Delete the ENP variable.
Delete the AddENP variable.
Fix the self -> Self in the methods
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(Self).Show();
end;
and
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(Self).Show();
end;
Here add the dependency:
procedure TAddEnp.AgregarClick(Sender: TObject);
var
ENP: TENP;
begin
//...
ENP := Owner as TENP; // Owner will be of type TENP because of the Create(Self) in the ENP1Click method
ENP.clearGrid();
ENP.populateGrid();
end;
I have a procedure named XYZ(sender:TObject) in delphi. There is one button on my form.
Button.onclick:= xyz;
Button.OnExit:= xyz;
Both the events calls the same procedure. I want to determine in procedure XYZ, which event calls this(onclick or onexit) and according to that proceed with coding.
How to determine which event gets fired? thanks
You can't get hold of that information by fair means. The solution is to use two separate top-level event handlers which in turn can call another method passing a parameter identifying which event is being handled.
type
TButtonEventType = (beOnClick, beOnExit);
procedure TMyForm.ButtonClick(Sender: TObject);
begin
HandleButtenEvent(beOnClick);
end;
procedure TMyForm.ButtonExit(Sender: TObject);
begin
HandleButtenEvent(beOnExit);
end;
procedure TMyForm.HandleButtonEvent(EventType: TButtonEventType);
begin
//use EventType to decide how to handle this
end;
I am new to delphi development. I have to create an event and pass some properties as parameters. Could someone share some demo program that shows how to do this from scratch. I googled nearly every site, they all gave a piece of code, but what I need is a full fledged program that is simple and understandable.
Here's a short-but-complete console application that shows how to create your own event in Delphi. Includes everything from type declaration to calling the event. Read the comments in the code to understand what's going on.
program Project23;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
// Declare an event type. It looks allot like a normal method declaration except
// it suffixed by "of object". That "of object" tells Delphi the variable of this
// type needs to be assigned a method of an object, not just any global function
// with the correct signature.
TMyEventTakingAStringParameter = procedure(const aStrParam:string) of object;
// A class that uses the actual event
TMyDummyLoggingClass = class
public
OnLogMsg: TMyEventTakingAStringParameter; // This will hold the "closure", a pointer to
// the method function itself + a pointer to the
// object instance it's supposed to work on.
procedure LogMsg(const msg:string);
end;
// A class that provides the required string method to be used as a parameter
TMyClassImplementingTheStringMethod = class
public
procedure WriteLine(const Something:string); // Intentionally using different names for
// method and params; Names don't matter, only the
// signature matters.
end;
procedure TMyDummyLoggingClass.LogMsg(const msg: string);
begin
if Assigned(OnLogMsg) then // tests if the event is assigned
OnLogMsg(msg); // calls the event.
end;
procedure TMyClassImplementingTheStringMethod.WriteLine(const Something: string);
begin
// Simple implementation, writing the string to console
Writeln(Something);
end;
var Logging: TMyDummyLoggingClass; // This has the OnLogMsg variable
LoggingProvider: TMyClassImplementingTheStringMethod; // This provides the method we'll assign to OnLogMsg
begin
try
Logging := TMyDummyLoggingClass.Create;
try
// This does nothing, because there's no OnLogMsg assigned.
Logging.LogMsg('Test 1');
LoggingProvider := TMyClassImplementingTheStringMethod.Create;
try
Logging.OnLogMsg := LoggingProvider.WriteLine; // Assign the event
try
// This will indirectly call LoggingProvider.WriteLine, because that's what's
// assigned to Logging.OnLogMsg
Logging.LogMsg('Test 2');
finally Logging.OnLogMsg := nil; // Since the assigned event includes a pointer to both
// the method itself and to the instance of LoggingProvider,
// need to make sure the event doesn't out-live the LoggingProvider
end;
finally LoggingProvider.Free;
end;
finally Logging.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
The complete project answer is good. But this is an alternate answer showing how to do what you want, in a form you already have.
Go into your form, and go to the interface section, in the types area, outside your form's class definition and add a type:
interface
type
TMyEvent = procedure(Sender:TObject;Param1,Param2,Param3:Integer) of object;
TMyForm = class(TForm)
....
It is traditional, but not required, that the first item in your event be the object sending it, but to use base class TObject instead of your form's actual class type.
The other parameters above are not required at all, but are showing you how you would declare your own additional data. if you don't need them, then just use Sender:TObject.
And in that case, you don't have to define TMyEvent at all, just use the TNotifyEvent type.
Now declare a field that uses that type, in your form:
TMyForm = class(TForm)
private
FMyEvent : TMyEvent;
...
Now declare a property that accesses that field, in your form's properties section:
// this goes inside the class definition just before the final closing end
property MyEvent:TMyEvent read FMyEvent write FMyEvent
Now go to where you want that event to 'fire' (get called if it is set) and write this:
// this goes inside a procedure or function, where you need to "fire" the event.
procedure TMyForm.DoSomething;
begin
...
if Assigned(FMyEvent) then FMyEvent(Self,Param1,Param2,Param3);
end;
You use an event handler to react when something else happens (for example AfterCreation and before closing).
In order to use events for your own class, you need to define the event type. Change the type and number of parameters needed.
type
TMyProcEvent = procedure(const AIdent: string; const AValue: Integer) of object;
TMyFuncEvent = function(const ANumber: Integer): Integer of object;
In the class, you can add a DoEvent (rename for the proper event). SO you can call the DoEvent internally. The DoEvent handles the possibility that an event is not assigned.
type
TMyClass = class
private
FMyProcEvent : TMyProcEvent;
FMyFuncEvent : TMyFuncEvent;
protected
procedure DoMyProcEvent(const AIdent: string; const AValue: Integer);
function DoMyFuncEvent(const ANumber: Integer): Integer;
public
property MyProcEvent: TMyProcEvent read FMyProcEvent write FMyProcEvent;
property MyFuncEvent: TMyFuncEvent read FMyFuncEvent write FMyFuncEvent;
end;
procedure TMyClass.DoMyProcEvent(const AIdent: string; const AValue: Integer);
begin
if Assigned(FMyProcEvent) then
FMyProcEvent(AIdent, AValue);
// Possibly add more general or default code.
end;
function TMyClass.DoMyFuncEvent(const ANumber: Integer): Integer;
begin
if Assigned(FMyFuncEvent) then
Result := FMyFuncEvent(ANumber)
else
Result := cNotAssignedValue;
end;
in the context of placing "events" into a DLL I described a concept using interfaces, step by step... maybe this helps in a different way: Using event listeners in a non-gui environment (DLL) (Delphi)
In Delphi 2009 I have a Form with a procedure MyProcedure that writes to a label on the Form. The form uses a DataModule with a ClientDataSet. When the AfterScroll event of the ClientDataSet is fired MyProcedure should be executed.
To avoid circular references and more important, as I want the DataModule to be reusable,
the DataModule should not reference to this specific Form.
In short, I hope that I can access the AfterScroll event from my Form. Can I hook up the Afterscroll event on the DataModule from my Form? I thought it should be possible, but I cannot remember how to do it. Thanks in advance.
You put an event property in your DataModule:
private
FOnAfterScroll : TNotifyEvent;
public
property OnAfterScroll : TNotifyEvent read FOnAfterScroll write FOnAfterScroll;
You then call that event in the AfterScroll procedure in the DataModule:
If Assigned(FOnAfterScroll) then FOnAfterScroll(Self);
In Form:
declare event handler
procedure HandleAfterScroll(Sender : TObject);
Then you assign a procedure to DataModule's OnAfterScroll
Datamodule1.OnAfterScroll :=
MyHandleAfterScroll;
Another way would be to send a custom windows message from DataModule and to respond to that message in the Form.
Should be something like:
procedure TForm1.FormCreate(Sender: TObject);
begin
DataModule1.MyCDS.AfterScroll := MyAfterScrollHandler;
end;
If all you want is to declare the event handler in a different unit, like the form, go with Ulrich's suggestion. If you want to be able to put a default event handler in your data module but then be able to extend its behavior, it takes a bit more work. You can do this by adding an event to the data module.
Define a method pointer with the appropriate signature and add one to the data module at public scope, like so:
type
TMyEvent = procedure({arg list here}) of object;
TMyDataModule = class(TDataModule)
//definition goes here
procedure MyTableAfterScroll({arg list here});
private
FExternalEvent: TMyEvent;
public
property ExternalEvent: TMyEvent read FMyEvent write FMyEvent
end;
implementation
procedure TMyDataModule.MyTableAfterScroll({arg list here});
begin
//do whatever
if assigned(FExternalEvent) then
FExternalEvent({whatever arguments});
//do more stuff, if you'd like
end;
To hook it up, in your form's OnCreate, just assign your procedure to MyDataModule.ExternalEvent and you'll be good to go.