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.
Related
I am new to delphi and pascal and was wondering if there was a way to get/access a property of the component that the Sender is referencing within the procedure.
More specifically I would like to make a procedure that changes the caption property of a label, that label being the component that Sender is referencing.
I imagine that procedure looking something like:
procedure TForm1.LabelEdit(Sender: TObject);
begin
Sender.caption := 'Sample Text';
end;
Naturally this wouldn't work but can something like or something similar to this be done?
Although the example in your question doesn't really make sense (it incorrectly suggests that a TLabel has an OnEdit event), it is very much possible to use the Sender parameter to obtain information about the sender object.
Create a new VCL application and drop a number of TLabel controls on the form. Give them different captions (like Dog, Cat, Rabbit, Horse etc.).
Now select them all in the form designer and then use the Object Inspector to create a common OnClick handler for them. You can name it LabelClick (write LabelClick in the edit field next to OnClick and press Enter).
This will create the following empty method:
procedure TForm1.LabelClick(Sender: TObject);
begin
end;
It has a Sender parameter of type TObject. Now, depending on how this method is called, Sender can be any TObject (a button, a form, a bitmap, ...), or nil (no object at all).
But in our case, we expect this method mainly to be called in response to the labels being clicked on, and in these cases, the Sender will be the corresponding TLabel object.
Let's try to display the caption of the clicked label in a message box!
We try
procedure TForm1.LabelClick(Sender: TObject);
begin
ShowMessage(Sender.Caption); // won't compile!
end;
But this doesn't even compile! The problem is that TObject has no public Caption member. But TLabel does, so we can write
procedure TForm1.LabelClick(Sender: TObject);
begin
ShowMessage(TLabel(Sender).Caption);
end;
Here we are telling the compiler that we know Sender will always be a TLabel, and we ask it to assume that it is.
But this will crash or do other bad things if somehow this method is called with a non-TLabel Sender. So it is safer to do
procedure TForm1.LabelClick(Sender: TObject);
begin
ShowMessage((Sender as TLabel).Caption);
end;
This does the same, except that the compiler will now create code that checks at runtime that Sender really is a TLabel object. If not, the code will raise an exception. That's much better than the kind of memory corruption/AV issues you may get with the unsafe cast above.
Arguably even better is
procedure TForm1.LabelClick(Sender: TObject);
begin
if Sender is TLabel then
ShowMessage(TLabel(Sender).Caption);
end;
This will also test the type of Sender at runtime. If it is a label, we display its caption. Otherwise, we choose to do nothing. Notice that there is no point in using a safe (and slightly, slightly, slower) as cast here.
You cast Sender to the type that the event connects.
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(Sender) then
(Sender as TButton).Caption := 'Clicked';
end;
If you're sharing the event among different types of controls, you can test first to see what type it is:
procedure TForm1.ControlClick(Sender: TObject);
begin
if (Sender is TEdit) then
TEdit(Sender).Text := 'Clicked'
else if (Sender is TButton) then
TButton(Sender).Caption := 'Clicked';
end;
end;
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?
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);
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)