I have a DataModule shared by several forms and in that I built a procedure to processing a TFDMemtable passed as parameter. In order to process it I must to disable the events AfterPost and AfterDelete and when conclude processing I have to enable them back. I'm not suceeding in enable them back, because I can't get the actual name of these events in a form "actualnameAfterpost".
I've tried :
pMemTable.AfterPost := #pMemTable.AfterPost ; // Result ==> compile error
pMemTable.AfterPost := addr(pMemTable.AfterPost) ; // Result ==> compile error
pMemTable.AfterPost := MethodAddress(Pmemtable.ClassName +'AfterPost'); //
Result ==> compile error
This is the main code :
procedure UpdateMemtable (var pMemTable : TFDmemtable);
begin
pMemTable.AfterPost := nil;
pMemTable.AfterDelete := nil;
TRY
with pMemTable do
begin
{ code to process pMemtable }
end;
FINALLY
pMemTable.AfterPost := ["actualmemtablenameAfterPost" ??];
pMemTable.AfterDelete := ["actualmemtablenameAfterDelete" ??];
END;
end;
Thanks all !
Both events are of type TDataSetNotifyEvent. Use two local variables of this type to hold the events temporarily.
To disable the events, save them to the temporary variables and then nil them.
After you have done the manipulation restore the actual events from the temporary vars.
procedure UpdateMemtable (var pMemTable : TFDmemtable);
var
tmpAfterPost,
tmpAfterDelete: TDataSetNotifyEvent
begin
tmpAfterPost := pMemTable.AfterPost;
tmpAfterDelete := pMemTable.AfterDelete;
pMemTable.AfterPost := nil;
pMemTable.AfterDelete := nil;
TRY
with pMemTable do
begin
{ code to process pMemtable }
end;
FINALLY
pMemTable.AfterPost := tmpAfterPost;
pMemTable.AfterDelete := tmpAfterDelete;
END;
end;
You need just to write your procedures and then assign them as
...
private
{ Private declarations }
procedure MyAfterPost(ADataSet: TDataSet);
procedure MyAfterDelete(ADataSet: TDataSet);
...
procedure TForm1.MyAfterDelete(ADataSet: TDataSet);
begin
ShowMessage('After Delete Fired');
end;
procedure TForm1.MyAfterPost(ADataSet: TDataSet);
begin
ShowMessage('After Post Fired');
end;
....
pMemTable.AfterPost:= MyAfterPost;
pMemTable.AfterDelete:= MyAfterDelete;
Here is a simple sample to help you understand, just run it and see what's happening
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf,
FireDAC.DApt.Intf, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client,
Vcl.ExtCtrls, Vcl.DBCtrls, Vcl.Grids, Vcl.DBGrids;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
pMemTable: TFDMemTable;
Ds: TDataSource;
Grid: TDBGrid;
Navigator: TDBNavigator;
procedure MyAfterPost(ADataSet: TDataSet);
procedure MyAfterDelete(ADataSet: TDataSet);
procedure GridTitleClick(Column: TColumn);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// Free all
pMemTable.Free;
Ds.Free;
Navigator.Free;
Grid.Free;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// Create the pMemTable
pMemTable:= TFDMemTable.Create(Nil);
with pMemTable do
begin
FieldDefs.Add('Column1', ftInteger);
FieldDefs.Add('Column2', ftInteger);
CreateDataSet;
// Assign the procedures
AfterPost:= MyAfterPost;
AfterDelete:= MyAfterDelete;
end;
// Create DataSource
Ds:= TDataSource.Create(Self);
Ds.DataSet:= pMemTable;
// Create DBNavigator
Navigator:= TDBNavigator.Create(Self);
with Navigator do
begin
Align:= alTop;
Parent:= Self;
DataSource:= Ds;
end;
// Create DBGrid
Grid:= TDBGrid.Create(Self);
with Grid do
begin
Align:= alClient;
Parent:= Self;
DataSource:= Ds;
OnTitleClick:= GridTitleClick;
end;
//
Self.Width:= 250;
Self.Height:= 250;
Self.BorderStyle:= bsDialog;
Self.Position:= poScreenCenter;
end;
procedure TForm1.GridTitleClick(Column: TColumn);
begin
{
The events now is enabled on creation, if you click on "Column1" title
then you disable them, if you click on "Column2" title, you enable them again.
}
if Column.Index = 0 then
begin
pMemTable.AfterPost:= nil;
pMemTable.AfterDelete:= nil;
end
else
begin
pMemTable.AfterPost:= MyAfterPost;
pMemTable.AfterDelete:= MyAfterDelete;
end;
end;
procedure TForm1.MyAfterDelete(ADataSet: TDataSet);
begin
// You will see this message after post
ShowMessage('After Delete Fired');
end;
procedure TForm1.MyAfterPost(ADataSet: TDataSet);
begin
// You will see this message after delete
ShowMessage('After Post Fired');
end;
end.
Finally, I suggest to visit those pages and read them:
Data.DB.TDataSetNotifyEvent
Creating Events - Overview
Related
I'm trying to execute an sql query asynchronously. I've checked the example code from http://docwiki.embarcadero.com/RADStudio/XE5/en/Asynchronous_Execution_(FireDAC)
and also the example project from directory
..Samples\Object Pascal\Database\FireDAC\Samples\Comp Layer\TFDQuery\ExecSQL\Async
and I think I got the logic inside. But there is one problem - the event QueryAfterOpen never executes and my TDataSource remains always Nil (because it gets Nil inside QueryBeforeOpen - this event executes always). This is all the code from my unit :
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf,
FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf,
FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys,
FireDAC.VCLUI.Wait, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt,
Vcl.StdCtrls, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client,
FireDAC.Phys.MySQLDef, FireDAC.Phys.MySQL;
type
TForm1 = class(TForm)
Button1: TButton;
FDPhysMySQLDriverLink1: TFDPhysMySQLDriverLink;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Query1BeforeOpen(DataSet: TDataSet);
procedure Query1AfterOpen(DataSet: TDataSet);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
Connection1: TFDConnection;
Query1: TFDQuery;
DataSource1: TDataSource;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
QueryFinished: Boolean;
begin
with Query1 do begin
SQL.Text := 'SELECT field1 FROM test_table WHERE idpk=1';
AfterOpen := Query1AfterOpen;
BeforeOpen := Query1BeforeOpen;
ResourceOptions.CmdExecMode := amAsync;
QueryFinished := False;
Open;
repeat
Sleep(100);
if Command.State = csPrepared then begin
// A command is prepared. A result set is not accessible.
// TmpInteger := Query1.FieldByName('field1').AsInteger;
end
else if Command.State = csOpen then begin // A command execution
// is finished. A result set is accessible and not yet fully fetched.
if DataSource1.DataSet <> Nil then begin
// this code never executes because Query1AfterOpen never executes and
// DataSource1.DataSet remains always Nil !!!
QueryFinished := True;
end;
end;
until ((QueryFinished) OR (DataSource1.DataSet <> Nil));
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
SQLConnParams: string;
begin
SQLConnParams := ''; // sql connection parameters removed from here from security
// issues, assume they are correct
Connection1 := TFDConnection.Create(Nil);
Connection1.Params.Text := SQLConnParams;
Query1 := TFDQuery.Create(Nil);
Query1.Connection := Connection1;
DataSource1 := TDataSource.Create(Nil);
DataSource1.DataSet := Query1;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
DataSource1.Free;
Query1.Free;
Connection1.Free;
end;
procedure TForm1.Query1AfterOpen(DataSet: TDataSet);
begin
DataSource1.DataSet := Query1;
Query1.AfterOpen := Nil;
//Query1.ResourceOptions.CmdExecMode := amBlocking;
end;
procedure TForm1.Query1BeforeOpen(DataSet: TDataSet);
begin
DataSource1.DataSet := Nil;
end;
end.
Apparently the code inside the repeat .. until .. loop is infinite unless someone terminates the program. What am I missing in order to execute the code inside Query1AfterOpen (or use another event instead) so I can access the result set after the TFDQuery has finished working ?
Those asynchronous events are synchronized with the main thread via the message loop. As long as you stay inside the Button1Click event no new messages can be handled. Thus the AfterOpen event is stuck inside the message loop.
I don't know what you are trying to achieve, but you should consider placing the relevant code in the AfterOpen event. The repeat-until clause somehow counterfeits the purpose of that async execution.
This is a sample code for a stopwatch I have implemented as a separate thread with the OmniThread library.
This is my question: Do I have to terminate and nil the task when the form closes or will it be destroyed automatically when the form closes?
uses
System.SysUtils, System.Classes,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
OtlComm, OtlTask, OtlTaskControl, OtlEventMonitor;
type
TForm1 = class(TForm)
OTLMonitor: TOmniEventMonitor;
btnStartClock: TButton;
btnStopClock: TButton;
procedure btnStartClockClick(Sender: TObject);
procedure btnStopClockClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure OTLMonitorTaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
procedure OTLMonitorTaskTerminated(const task: IOmniTaskControl);
private
{ Private-Deklarationen }
FClockTask: IOmniTaskControl;
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ Place a TOmniEventMonitor component on the form,
name it OTLMonitor,
implement the OnTaskTerminated event-handler: OTLMonitorTaskTerminated
and implement the OnTaskmessage event-handler: OTLMonitorTaskMessage }
var
StopMessage: string;
procedure ShowElapsedSeconds(const ATask: IOmniTask);
var
ElapsedSeconds: Integer;
begin
ElapsedSeconds := 0;
while not ATask.Terminated do
begin
// stop after 10 seconds:
if ElapsedSeconds >= 10 then BREAK;
Inc(ElapsedSeconds);
ATask.Comm.Send(ElapsedSeconds);
Sleep(1000);
end;
end;
procedure TForm1.OTLMonitorTaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
begin
// show elapsed seconds:
Self.Caption := IntToStr(msg.MsgID);
end;
procedure TForm1.OTLMonitorTaskTerminated(const task: IOmniTaskControl);
begin
FClockTask := nil;
Self.Caption := StopMessage;
end;
procedure TForm1.btnStartClockClick(Sender: TObject);
begin
if not Assigned(FClockTask) then // prevent multiple clock-tasks
begin
StopMessage := 'Automatically stopped after 10 seconds';
FClockTask := CreateTask(ShowElapsedSeconds, 'ShowElapsedSeconds').MonitorWith(OTLMonitor).Run;
end
else
begin
MessageDlg('Clock is already running!', mtInformation, [mbOK], 0);
{ Nice: The clock continues to run even while this message dialog is displayed! }
end;
end;
procedure TForm1.btnStopClockClick(Sender: TObject);
begin
if Assigned(FClockTask) then
begin
StopMessage := 'Stopped by the user';
FClockTask.Terminate;
FClockTask := nil;
end
else
MessageDlg('Clock is not running!', mtInformation, [mbOK], 0);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(FClockTask) then
begin
{ Do I need to terminate and nil the clock-task here?
Or will it be destroyed autmatically when the form closes? }
end;
end;
Primož Gabrijelčič, the author of "Parallel Programming with OmniThreadLibrary" writes:
"We should also handle the possibility of user closing the program by
clicking the ‘X’ button while the background scanner is active. We
must catch the OnFormCloseQuery event and tell the task to terminate.
procedure TfrmBackgroundFileSearchDemo.FormCloseQuery(Sender: TObject;
var CanClose: boolean);
begin
if assigned(FScanTask) then
begin
FScanTask.Terminate;
FScanTask := nil;
CanClose := true;
end;
end;"
This book is for sale at http://leanpub.com/omnithreadlibrary
I have seen the below msghandler code in several places now as the solution to not being able to press Enter in a twebbrowser. This solution does work as long as you're only dealing with one twebbrowser. I've provided a complete unit here for discussion. If you take two twebbrowsers and make one of them the "active" browser (see code) and navigate them each to a site for example that has a username, password and button you can enter the data in the "active" browser and press Enter successfully. If you try to use the non "active" browser not only can you not press Enter but use of tab fails as well. Whichever browser you press Enter in first is the one that will continue to work so it seems to have nothing to do with order of creation of the browsers.
How do I make my additional browsers function?
unit Main_Form;
interface
uses
Winapi.Windows, Winapi.Messages, Vcl.Controls, Vcl.Forms,
ActiveX, Vcl.OleCtrls, SHDocVw, System.Classes, Vcl.StdCtrls;
type
TForm1 = class(TForm)
NavigateBrowsers: TButton;
WebBrowser1: TWebBrowser;
WebBrowser2: TWebBrowser;
MakeBrowser1Active: TButton;
MakeBrowser2Active: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormDeactivate(Sender: TObject);
procedure NavigateBrowsersClick(Sender: TObject);
procedure MakeBrowser1ActiveClick(Sender: TObject);
procedure MakeBrowser2ActiveClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure MsgHandler(var Msg: TMsg; var Handled: Boolean);
end;
var
Form1: TForm1;
ActiveBrowser: TWebBrowser;
FOleInPlaceActiveObject: IOleInPlaceActiveObject;
SaveMessageHandler: TMessageEvent;
implementation
{$R *.dfm}
procedure TForm1.MakeBrowser1ActiveClick(Sender: TObject);
begin
ActiveBrowser := WebBrowser1;
end;
procedure TForm1.MakeBrowser2ActiveClick(Sender: TObject);
begin
ActiveBrowser := WebBrowser2;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Application.OnMessage := SaveMessageHandler;
FOleInPlaceActiveObject := nil;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
//Handle messages
SaveMessageHandler := Application.OnMessage;
Application.OnMessage := MsgHandler;
end;
procedure TForm1.FormDeactivate(Sender: TObject);
begin
Application.OnMessage := SaveMessageHandler;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FOleInPlaceActiveObject := nil;
end;
procedure TForm1.NavigateBrowsersClick(Sender: TObject);
begin
WebBrowser1.Navigate(''); //supply own
WebBrowser2.Navigate(''); //supply own
end;
procedure TForm1.MsgHandler(var Msg: TMsg; var Handled: Boolean);
const
StdKeys = [VK_BACK, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT];
var
IOIPAO: IOleInPlaceActiveObject;
Dispatch: IDispatch;
begin
//Exit if webbrowser object is nil
if ActiveBrowser = nil then
begin
Handled := False;
Exit;
end;
Handled:=(IsDialogMessage(ActiveBrowser.Handle, Msg) = True);
if (Handled) and (not ActiveBrowser.Busy) then
begin
if FOleInPlaceActiveObject = nil then
begin
Dispatch := ActiveBrowser.Application;
if Dispatch <>nil then
begin
Dispatch.QueryInterface(IOleInPlaceActiveObject, iOIPAO);
if iOIPAO <>nil then
FOleInPlaceActiveObject := iOIPAO;
end;
end;
if FOleInPlaceActiveObject <>nil then
if ((Msg.message = WM_KEYDOWN) or (Msg.message = WM_KEYUP)) and
(Msg.wParam in StdKeys) then
//nothing - do not pass on StdKeys
else
FOleInPlaceActiveObject.TranslateAccelerator(Msg);
end;
end;
initialization
OleInitialize(nil);
finalization
OleUninitialize;
end.
I faced the same problem as you and I use a similar message handler, FOleInPlaceActiveObject is not really needed:
procedure TForm1.MsgHandler(var Msg: TMsg; var Handled: Boolean);
const
StdKeys = [VK_BACK, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT];
var
IOIPAO: IOleInPlaceActiveObject;
begin
try
if Assigned(ActiveBrowser) then
begin
Handled:=(IsDialogMessage(ActiveBrowser.Handle, Msg) = True);
if Handled then
begin
if ((Msg.message = WM_KEYDOWN) or (Msg.message = WM_KEYUP)) and (Msg.wParam in StdKeys) then
begin
//nothing - do not pass on Backspace, Left, Right, Up, Down arrows
end
else
begin
IOIPAO := (ActiveBrowser.Application as IOleInPlaceActiveObject);
if Assigned(IOIPAO)then
IOIPAO.TranslateAccelerator(Msg)
end;
end;
end;
except
Handled := False;
end;
end;
After days of searching for an answer it appears I have found something that works the same day I posted the question here. Go figure! For everyone's benefit, here is what worked.
All I had to do was assign the browser as the active control when either the user changed tabs or at the time of new tab creation. The reason for the count check in the pagecontrolchange procedure is to keep from getting a list index out of bounds on initial tab creation at startup. I do realize I probably need to change my ObjectLists over to Generics, ;)
procedure TForm1.PageControl1Change(Sender: TObject);
begin
if PageControl1.PageCount = MyBrowsersObjectList.Count then // Not adding a page
begin
ActiveBrowser := MyBrowsersObjectList[PageControl1.ActivePageIndex] as TWebBrowser;
ActiveControl := ActiveBrowser;
end;
end;
procedure TForm1.CreateBrowserTab(APage: TAdvOfficePage; NavigateTo: String);
begin
APage.Caption := 'Loading...';
ActiveBrowser := TWebBrowser.Create(nil);
MyBrowsersObjectList.Add(ActiveBrowser);
TControl(ActiveBrowser).Parent := APage;
ActiveBrowser.Align := alClient;
ActiveBrowser.RegisterAsBrowser := True;
ActiveBrowser.Tag := BrowserTabs.ActivePageIndex;
ActiveBrowser.Navigate(NavigateTo);
ActiveControl := ActiveBrowser;
end;
Problem:
How can I load frame in Form1 or sample container in form ?
FindClass or GetClass is only locality for main form appl-n
I need (maybe) string globaly elemental for TFrameClass, next code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, Vcl.ExtCtrls;
type
TFrameClass = class of TFrame;
type
TForm1 = class(TForm)
Panel1: TPanel;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FFrame: TFrame;
function StrShowFrame(FrameClassName: string;
ParentPanel: TWinControl): Boolean;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses Base1Frame, Base2Frame, Base3Frame;
function TForm1.StrShowFrame(FrameClassName: string;
ParentPanel: TWinControl): Boolean;
var
FrameClass: TClass;
// Current Frame (FrameName)
FrameName: string;
begin
Result := False;
??? GetClass is only locality for main form in appl-n
FrameClass := GetClass(FrameClassName);
if FrameClass = nil then
begin
ShowMessageFmt('Class %s not registered', [FrameClassName]);
Result := False;
Exit;
end;
try
begin
LockWindowUpdate(ParentPanel.Handle);
if Assigned(FFrame) then
if FFrame.ClassType = FrameClass then
begin
Result := True;
Exit;
end
else
FFrame.Destroy; // del previus FrameClass
try
FFrame := TFrameClass(FrameClass).Create(nil);
except
on E:Exception do
begin
Result := True;
E.Create(E.Message);
FFrame := nil;
Exit;
end;
end;
FrameName:= FrameClassName;
Delete(FrameName, 1, 1); // T-...
FFrame.Name := Concat(FrameName, '1');
FFrame.Parent := ParentPanel;
FFrame.Align := alClient;
end;
finally
LockWindowUpdate(0);
end;
Result := True;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
StrShowFrame('TFr_Base1', Panel1);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
try
if FFrame <> nil then
FFrame.Free
else
ShowMessage('Class not activ');
except
end;
end;
end.
How can I load frame in Form1 or sample container in form ?
FindClass or GetClass is only locality for main form appl-n
I need (maybe) string globaly elemental for TFrameClass.
GetClass() and FindClass() are not local to the MainForm, they are global to the entire RTL as a whole. Any unit can call RegisterClass() and have that class be accessible to any other unit that shares the same instance of the RTL. That last part is important. A DLL cannot register a class that the EXE uses (and vice versa), unless both projects are compiled with Runtime Packages enabled so they share a single RTL instance.
I have a TMachine class, that is a TShape class
unit MachineShape;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, extctrls,myDataModule,Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TMachine = class(TShape)
private
{ Private declarations }
public
{ Public declarations }
procedure PlaceShape(sizeW,sizeH :integer; name, order,asset : string);
end;
implementation
Procedure TMachine.PlaceShape(sizeW,sizeH :integer; name, order,asset : string);
begin
self.width := sizeW;
self.height := sizeH;
self.top := 136;
self.left := MyDataModule.fDB.LastX +2;//set left
MyDataModule.fDB.lastx := left + sizeW;
end;
end.
How would i add onmouseenter code for this? So when the shape is added during run time it will have its own on mouse enter code. Something like this, I know this wont work.. but maybe it will show you what i am looking to do? So when i create a TMachine, i would pass the name and number to this procedure and it would make the onmouseenter procedure update with the name/number i sent it.
Procedure TMachine.EditMouseEnter(name,number :string);
begin
....onmouseenter(Label2.Caption := name AND label3.caption := Number)...
end
Add an OnMouseEnter event:
type
TMachineEvent = procedure(Sender: TMachine) of object;
TMachine = class(TShape)
private
FOnMouseEnter: TMachineEvent;
...
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
protected
procedure DoMouseEnter; virtual;
published
property OnMouseEnter: TMachineEvent read FOnMouseEnter write FOnMouseEnter;
...
end;
implementation
{ TMachine }
procedure TMachine.CMMouseenter(var Message: TMessage);
begin
DoMouseEnter;
inherited;
end;
procedure TMachine.DoMouseEnter;
begin
if Assigned(FOnMouseEnter) then
FOnMouseEnter(Self);
end;
And assign that event at runtime:
procedure TForm1.CreateMachine;
var
Machine: TMachine;
begin
Machine := TMachine.Create(Self);
Machine.SetBounds(...);
Machine.OnMouseEnter := MachineMouseEnter;
Machine.Parent := Self;
end;
procedure TForm1.MachineMouseEnter(Sender: TMachine);
begin
Label2.Caption := Sender.Name;
Label3.Caption := Sender.Number;
end;