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?
Related
I'm trying to have a TEditDescendant that contains a reference to another object (MyString), so that when I edit the TEdit text, MyString gets updated and vice-versa.
This works when MyString is already existing. However, if I need MyEdit to be able to create a MyString, it does not work.
The code below is a minimal example:
btnCreateMyEdit creates the myEdit and gives it some text.
btnCreateMyString creates an instance of myString btnCheck shows the issues:
If btnCheck is created AFTER myString exists (i.e. after clicking btnCreateMyString), no issue.
If btnCheck is created Before myString exists (i.e. before clicking btnCreateMyString), it shows MyString was not created.
I'm using Delphi Tokyo.
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.Edit, FMX.StdCtrls;
type
tMyString= class(TObject)
private
fvalue:string;
property value : string read fvalue write fvalue;
end;
TMyEdit = class(TEdit)
constructor Create(AOwner: TComponent); override;
private
fMyString: tMyString;
fOnChange : TNotifyEvent;
procedure MyOnChange(Sender : TObject);
public
procedure setMyString(prop:tMyString);
property MyProperty: tMyString read fMyString write setMyString;
procedure load;
property OnChange : TNotifyEvent read fOnChange write fOnChange;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
end;
var
Form1: TForm1;
Myedit1:TMyEdit;
Mystring:TMyString;
implementation
{$R *.fmx}
constructor TMyEdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Inherited OnChange := MyOnChange;
end;
procedure TMyEdit.MyOnChange(Sender : TObject);
begin
if (text<>'') and (fMyString=nil) then fMyString:=TMyString.Create;
fMystring.value:=self.text;
end;
procedure TMyEdit.load;
begin
if fMyString<>nil then
text:=fMyString.value;
end;
procedure TMyEdit.setMyString(prop:tMyString);
begin
fMyString:=prop;
load;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Myedit1:=TMyEdit.Create(Form1);
MyEdit1.Parent:=Form1;
Myedit1.MyProperty:=MyString;
MyEdit1.Text:='any text';
Myedit1.MyOnChange(self);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
button2.Text:=Myedit1.myproperty.value;
button2.Text:= Mystring.value;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
mystring:=tmystring.Create;
mystring.value:='123';
end;
end.
When you wish to modify data from an external object that is linked to your object (you have a reference to it stored) I think it might be best to use approach which I named "Property forwarding".
Now basically what you do in such approach is to define a property in your base object that is making use of both getter and setter methods for reading and writing data. But instead of reading data from or writing data to some internal field as these getter and setter methods most commonly doe you actually define then in a way that instead they are reading data from or writing data to some external object either by directly accessing that objects field or use external object own property for accessing its data. I suggest using the latter because this way you make it easier to modify the external object without the need to modify every other object that is reading data from or writing data to this external object.
Here is short and hopefully well commented code example that will show you how such approach works:
type
//Our external object for storing some data
TMyExternalObject = class (TObject)
private
//Field for storing some string value
fStrValue: String;
public
//Property for accessing the value of fStrValue field
property StrValue: String read fStrValue write fStrValue;
end;
//Out base object that will use external object for storing additionall data
TMyBaseObject = class (TObject)
private
//Field for storing reference to our external object
fMyExternalObject: TMyExternalObject;
protected
//Getter method that we will use to forward some data from our external object
function GetMyExternalObjectStr: string;
//Setter method that we will use to store some data into our external object
procedure SetMyExternalObjectStr(AValue: String);
public
//Modified constructor which accepts additional optional parameter so that we can
//set the reference to our external object upon creation of our base class
//If this parameter is omitted when calling constructor the default value of nil will
//be set to AMyExternalObject
constructor Create(AMyExternalObject: TMyExternalObject = nil);
//Property declaration that uses custom made getter and setter methods
property ExternalObjectStr: String read GetMyExternalObjectStr write SetMyExternalObjectStr;
end;
implementation
{ TMyBaseObject }
//Modified constructor which can set fMyExternalObject reference to the object that is passed
//as constructor parameter
constructor TMyBaseObject.Create(AMyExternalObject: TMyExternalObject);
begin
inherited Create;
//Set the reference of external object to the object that was passed as AMyExternalObject parameter
//If parameter was omitted the default value of nil which was defined in constructor will be used
fMyExternalObject := AMyExternalObject;
end;
//Getter method used to forward data from our external object
function TMyBaseObject.GetMyExternalObjectStr: string;
begin
//Always check to se if fMyExternalObject reference is set to point to existing object otherwise you
//will cause AccessVialation by trying to read data from nonexistent object
if fMyExternalObject <> nil then
begin
//Simply assign the returned value from our external object property to the result of the method
result := fMyExternalObject.StrValue;
end
else
begin
//If fmyExternalObject field does not reference to an existing object you will sill want to return
//some predefined result. Not doing so could cause code optimizer to remove this entire method from
//the code before compilation.
//Delphi should warn you about possibility that function might not have a defined result
result := 'No external object attached';
end;
end;
//Setter method used to store some data to external object.
//This method also takes care of creating and linking the external object if one hasn't been linked already
procedure TMyBaseObject.SetMyExternalObjectStr(AValue: String);
begin
//Check to see if fMyExternalObject already references to an existing external object.
//If it does not create external object and set fMyExgternalObject to point to it
if fMyExternalObject = nil then
begin
//Create the external object and set fMyExternalObject field to point to it
fMyExternalObject := TMyExternalObject.Create;
end;
//Write our data to external object
fMyExternalObject.StrValue := AValue;
end;
Note this code example does not have proper error checking (would need several try..except blocks there. I purposely omitted them in order to make code more readable.
Also my code is written to work with classes and not components. So you will have to modify it to work with your derived TEdit component. So you will have to modify the constructor declaration in a way that it won't hide default parameters of TEdit's constructor.
NOTE: While my code example will allow you to have multiple TEdit boxes reading and modifying the same string value that is stored in external object it will not cause all of those TEdit boxes to automatically update their text when the external objects string value is changed. Thre reason for this is that my code example does not have any mechanism for notifying the other TEdit boxes to redraw themselves and thus show the new updated text.
For this you will have to design a special mechanism which will notify all of the TEdit components so that they need to update themselves. Such mechanism would also require your external object to store the reference to all the TEdit components that are linking to it. If you decide to go and implement such system pay special attention because such system would be causing circular referencing and could prevent Automatic Reference Counting to properly free up the objects when there are no longer needed.
Here it might not be bad to go read some more about component notification system and how it works. Why? Because the purpose of component notification system is to provide such functionality that allows you to notify multiple component of some events.
WARNING: Since the above code example is creating these external objects when necessary you will have to make sure there is also proper code for destroying those created external objects otherwise you risk leaking them.
Now if you have just one TEdit box connecting to such external object you can destroy it in TEdit destructor. But if you plan on connection multiple TEdit components to same external object you will have to devise some other mechanism to track the life of these external objects.
I hope my answer will come helpful to you.
Any way I recommend you read more about using of getter and setter methods. They can be pretty powerful when used correctly.
PS: This approach is no novelty. I'm pretty sure it was used many times before. Also the name "Property forwarding" is how I named it. It is quite possible that it has some different name that I don't know off.
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 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)
It may seem a little newbie, but I really have got problem with it. I have a form (not the main one)for getting many different data from the user and I want to pass it to a manager class for creating an object with these. The problem is that I can't have this class to use the other unit (getting circle uses) and also it doesn't have access to the manager class instance (which is in main form).
So, what shall I do? I've already considered using public variable but I have a bad feeling about it (regarding OOD patterns).
My suggestion is to decouple data from the GUI because this is causing your problem.
If you have a form which gathers data from the user then you should distinguish the data from the form(TForm).
For example, let's assume that you have some instance of TForm and a form, which is built from three fields: username, age and location. You want the user to enter those three things, but when the user closes the form, you should pass this inserted data onto some object. Form closes, it is freed, but the object persist. Then you pass this object to your manager object.
Simple example:
This is your record which will hold the data
type
TGatheredData = record
Name: String[40];
Age: Byte;
Location: String[40];
end;
Your TForm1 might have an aditional constructor:
constructor TForm1.Create(AOwner: TComponent; var GatheredData: TGatheredData );
begin
inherited Create(AOwner);
FGatheredData := GatheredData;
//you may want to deserialize GatheredData here and show the data in your form controls
end;
You call it, pass GatheredData and then your are showing your form.
Next, when closing form, you pick upd the data from the form controls.
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Self.ModalResult = mrOk then
begin
//serialize your object
FGatheredData.Name := '';//name taken from control f.e. TEdit
FGatheredData.Age := '';//name taken from control f.e. TSpinButton
FGatheredData.Location := '';//name taken from control f.e. TEdit
end;
end;
Having this record of data, you may now pass it in the same manner to your Manager object.
You decoupled data from GUI in this way, and you may easly plugin in your record to a number of different forms.
Just remember to declare your record type in external unit and use that unit in your manager unit and forms unit.
Hope this helps a little.
The "manager class" shouldn't be in either form's unit, but in its own. By separating GUI code from bussiness logic you avoid problems such like this.
[Edit: I originally put this answer in a comment, but decided to move it out into full answer. TDatamodules are too important and too common in Delphi not to emphasize them and they provide built-in easy-to-use means of seperating gui from logic and data.]
Other people have given good comments about decoupling gui from the logic and data. Surprisingly, I don't think anybody has mentioned that in Delphi TDatamodules are one main means of doing this. You put your data and logic on the Datamodule, then have both forms "use" the Datamodule to get access to its data and methods. Here is brief intro: http://delphi.about.com/od/database/l/aa101601a.htm
Both of your forms (and other forms) can access datasets or other data/datastructures that are located on/in a Datamodule unit. There should be an easy to find sample project out there illustrating the setup, since it is (or at least was) the standard way to construct Delphi apps.
If I understand your question properly then you want the manager to manage the forms (not the forms to access the manger). Right? You can't close the Main Form as if you do you close the application but you CAN Hide it. (unless you create a console app). But it poses a nice little problem :)
So... Splash form (Main Form) is:
.
.
.
uses
ManagerU;
type
TFormSplash = class(TForm)
procedure FormPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
Manager: TManager;
end;
var
FormSplash: TFormSplash;
implementation
{$R *.dfm}
procedure TFormSplash.FormCreate(Sender: TObject);
begin
Manager := TManager.Create;
end;
procedure TFormSplash.FormDestroy(Sender: TObject);
begin
Manager.Free;
end;
procedure TFormSplash.FormPaint(Sender: TObject);
begin
if Visible then
begin
Manager.GetData(Self);
Hide;
Manager.DoDataStuff;
Close;
end;
end;
end.
DaaObject is:
unit DataObjectU;
interface
uses classes;
type TDataObject = class(TObject)
Data: string;
end;
implementation
end.
Manager is:
interface
uses DataObjectU;
type
TManager = Class(Tobject)
MyData: TDataObject;
constructor Create; virtual;
destructor Destroy; override;
procedure GetData(OwnerForm: TForm);
procedure DoDataStuff;
end;
implementation
uses DataFormU;
{ TManager }
constructor TManager.Create;
begin
inherited Create;
MyData := TDataObject.Create;
end;
destructor TManager.Destroy;
begin
MyData.Free;
inherited;
end;
procedure TManager.DoDataStuff;
begin
// do stuff with data here
end;
procedure TManager.GetData(OwnerForm: TForm);
var
MyDataForm: TDataForm;
begin
MyDataForm := TDataForm.Create(OwnerForm);
try
if MyDataForm.ShowModal = mrOK then
begin
MyData.Data := MyDataForm.Data;
end;
finally
MyDataForm.Free;
end;
end;
end.
The Dataform is:
type
TDataForm = class(TForm)
btnOK: TButton;
procedure btnOKClick(Sender: TObject);
private
function GetData: String;
{ Private declarations }
public
{ Public declarations }
MyData: TDataObject;
property Data: String read GetData;
end;
var
DataForm: TDataForm;
implementation
{$R *.dfm}
procedure TDataForm.btnOKClick(Sender: TObject);
begin
MyData := TDataObject.Create;
ModalResult := mrOk;
end;
function TDataForm.GetData: String;
begin
Result := MyData.Data;
end;
You will need to fill in the rest of the unit code and free some stuff but essentially this:
Start Program (Creates Splash)
Splash Creates the manager calls it to get data from the dataform then hides itself
calls manager to manage the data
when manager is done it then closes.
There is no other way to shut it down now except through task manager!)
Tim
To solve circular refrence error, you use that unit in implementation section.
implementation
{$R *.DFM}
Uses <Your Unit>;
end.
Having this 3 units:
FormMain
FormEdit
UnitMyClass
You create your object in FormMain and pass it to the FormEdit in a function like:
class function FormEdit.EditMyObject(AObject: TMyClass): boolean;
this function will showModal the form. The form will do the changes to the object, and finally return True if user pressed OK.
As you can see in UnitMyClass there is no reference to FormMain or FormEdit
FWIW, I did a whole presentation on this topic in a CodeRage 9 video. It can be seen here:
https://youtu.be/qqKx8fQTTfI
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.