How to trigger the event together on each two deferent class - delphi

I have two object class on a single unit, is it posible to trigger the two events? let say the FIRSTCLASS event is fired, The SECONDCLASS also will fired?
Assuming......
//{Class 1}-------------------------------------------------------------
type
TOnEventTrigger = procedure(Sender : Tobject; Value :integer);
TMyFirstClass = Class(Tcomponent)
private
....
public
....
propert OnEventTrigger : TOnEventTrigger read Fevent write Fevent;
end;
procedure TMyFirstClass.FEvnt(Sender : Tobject; Value :integer);
begin
// here is normaly triggers the event //
if Assigned(OnEventTrigger) then
OnEventTrigger(Self,FSomevalue);
// POSTMessage(GetForegroundWindow,WM_USER+3,0,0);
// this is what i did here to get the result of FSomevalue
// but this is not ideal. It work only on focus window.
end;
//{Class 2}-------------------------------------------------------------
type
TOnEventTrigger = procedure(Sender : Tobject; Value :integer);
TMySecondClass = Class(Tobject)
private
....
public
....
property OnEventTrigger : TOnEventTrigger; read Fevent write Fevent;
end;
procedure TMySecondClass.FEvnt(Sender : Tobject; Value :integer);
begin
// I wanted here to trigger, whenenver the above is fired //
if Assigned(OnEventTrigger) then
OnEventTrigger(Self,FSomevalue);
end;

You can assign the same Event handler to both classes when they are created (Pseudo-code):
Form1 = class()
private
{ ... }
protected
A : TMyFirstClass;
B : TMySecondClass;
procedure MyHandler(Sender: TObject; Value: Integer);
end;
procedure Form1.CreateForm();
Begin
{ Create A & B }
A := TMyFirstClass.Create(Self);
B := TMySecondClass.Create(Self);
{ Assign Event Handler to both classes }
A.OnEventTrigger := MyHandler;
B.OnEventTrigger := MyHandler;
End;
procedure Form1.MyHandler(Sender: TObject; Value: Integer);
Begin
ShowMessage('Event from '+Sender.ClassName+'. Value='+IntToStr(Value));
End;
This way when the event fires from either class, it'll end up in the MyHandler() code.

Related

Delphi OTAPI AddMenuCreatorNotifier deprecated, what is the replacement?

I am following the CodeCentral article on how to extend the project menu in Delphi IDE, using IOTAProjectManager.
The sample wizard code on code-central does this:
procedure Register;
begin
FNotifierIndex := (BorlandIDEServices as IOTAProjectManager).AddMenuCreatorNotifier(TMyContextMenu.Create); // deprecated.
end;
What is the new technique to register a context menu such as the project menu one? Note that this was deprecated without even making it onto the docwiki.
Screenshot of desired result:
Update: I could not find any up to date tutorials including code. There is a PDF whitepaper on Embarcadero's website but the code samples from that whitepaper by Bruno Fierens are not anywhere on the web. I made an answer below with a working example, which is on bitbucket, you can download the zip below.
If you look at the source code in $(BDS)\Source\ToolsAPI\ToolsAPI.pas, the declaration of IOTAProjectManager.AddMenuCreatorNotifier() says:
This function is deprecated -- use AddMenuItemCreatorNotifier instead
And also, the declaration of INTAProjectMenuCreatorNotifier says:
This notifier is deprecated. Use IOTAProjectMenuItemCreatorNotifier instead. It supports adding menu items for multi-selected items in the Project Manager.
Here are the relevant declarations and descriptions. Note the comments:
type
...
{ This notifier is deprecated. Use IOTAProjectMenuItemCreatorNotifier instead.
It supports adding menu items for multi-selected items in the Project Manager. }
INTAProjectMenuCreatorNotifier = interface(IOTANotifier)
['{8209348C-2114-439C-AD4E-BFB7049A636A}']
{ The result will be inserted into the project manager local menu. Menu
may have child menus. }
function AddMenu(const Ident: string): TMenuItem;
{ Return True if you wish to install a project manager menu item for this
ident. In cases where the project manager node is a file Ident will be
a fully qualified file name. }
function CanHandle(const Ident: string): Boolean;
end;
IOTAProjectMenuItemCreatorNotifier = interface(IOTANotifier)
['{CFEE5A57-2B04-4CD6-968E-1CBF8BF96522}']
{ For each menu item you wish to add to the project manager for the given
list of idents, add an IOTAProjectManagerMenu to the ProjectManagerMenuList.
An example of a value for IdentList is sFileContainer and the name of the
file, look above in this file for other constants. }
procedure AddMenu(const Project: IOTAProject; const IdentList: TStrings;
const ProjectManagerMenuList: IInterfaceList; IsMultiSelect: Boolean);
end;
IOTAProjectManager = interface(IInterface)
['{B142EF92-0A91-4614-A72A-CE46F9C88B7B}']
{ This function is deprecated -- use AddMenuItemCreatorNotifier instead }
function AddMenuCreatorNotifier(const Notifier: INTAProjectMenuCreatorNotifier): Integer; deprecated;
{ Adds a menu notifier, which allows you to customize the local menu of the
project manager }
function AddMenuItemCreatorNotifier(const Notifier: IOTAProjectMenuItemCreatorNotifier): Integer;
...
{ This function is deprecated -- use RemoveMenuItemCreatorNotifier instead }
procedure RemoveMenuCreatorNotifier(Index: Integer); deprecated;
{ Removes a previously added menu notifier }
procedure RemoveMenuItemCreatorNotifier(Index: Integer);
end;
...
{ This is meant to be an abstract interface that describes a menu context that
can be passed to an IOTALocalMenu-descendant's Execute method. }
IOTAMenuContext = interface(IInterface)
['{378F0D38-ED5F-4128-B7D6-9D423FC1502F}']
{ Returns the identifier for this context }
function GetIdent: string;
{ Returns the verb for this context }
function GetVerb: string;
property Ident: string read GetIdent;
property Verb: string read GetVerb;
end;
{ This is meant to be an abstract interface that describes a local menu item
in an IDE view. Specific views that can have their local menus customized
will provide a descendant interface to be used for that view }
IOTALocalMenu = interface(IOTANotifier)
['{83ECCBDF-939D-4F8D-B96D-A0C67ACC86EA}']
{ Returns the Caption to be used for this menu item }
function GetCaption: string;
{ Returns the Checked state to be used for this menu item }
function GetChecked: Boolean;
{ Returns the Enabled state to be used for this menu item }
function GetEnabled: Boolean;
{ Returns the help context to be used for this menu item }
function GetHelpContext: Integer;
{ Returns the Name for this menu item. If blank, a name will be generated }
function GetName: string;
{ Returns the parent menu for this menu item }
function GetParent: string;
{ Returns the position of this menu item within the menu }
function GetPosition: Integer;
{ Returns the verb associated with this menu item }
function GetVerb: string;
{ Sets the Caption of the menu item to the specified value }
procedure SetCaption(const Value: string);
{ Sets the Checked state of the menu item to the specified value }
procedure SetChecked(Value: Boolean);
{ Sets the Enabled state of the menu item to the specified value }
procedure SetEnabled(Value: Boolean);
{ Sets the help context of the menu item to the specified value }
procedure SetHelpContext(Value: Integer);
{ Sets the Name of the menu item to the specified value }
procedure SetName(const Value: string);
{ Sets the Parent of the menu item to the specified value }
procedure SetParent(const Value: string);
{ Sets the position of the menu item to the specified value }
procedure SetPosition(Value: Integer);
{ Sets the verb associated with the menu item to the specified value }
procedure SetVerb(const Value: string);
property Caption: string read GetCaption write SetCaption;
property Checked: Boolean read GetChecked write SetChecked;
property Enabled: Boolean read GetEnabled write SetEnabled;
property HelpContext: Integer read GetHelpContext write SetHelpContext;
property Name: string read GetName write SetName;
property Parent: string read GetParent write SetParent;
property Position: Integer read GetPosition write SetPosition;
property Verb: string read GetVerb write SetVerb;
end;
{ This is the context used for Project Manager local menu items. The list
passed to IOTAProjectManagerMenu.Execute will be a list of these interfaces }
IOTAProjectMenuContext = interface(IOTAMenuContext)
['{ECEC33FD-837A-46DC-A0AD-1FFEBEEA23AF}']
{ Returns the project associated with the menu item }
function GetProject: IOTAProject;
property Project: IOTAProject read GetProject;
end;
{ This is a Project Manager specific local menu item }
IOTAProjectManagerMenu = interface(IOTALocalMenu)
['{5E3B2F18-306E-4922-9067-3F71843C51FA}']
{ Indicates whether or not this menu item supports multi-selected items }
function GetIsMultiSelectable: Boolean;
{ Sets this menu item's multi-selected state }
procedure SetIsMultiSelectable(Value: Boolean);
{ Execute is called when the menu item is selected. MenuContextList is a
list of IOTAProjectMenuContext. Each item in the list represents an item
in the project manager that is selected }
procedure Execute(const MenuContextList: IInterfaceList); overload;
{ PreExecute is called before the Execute method. MenuContextList is a list
of IOTAProjectMenuContext. Each item in the list represents an item in
the project manager that is selected }
function PreExecute(const MenuContextList: IInterfaceList): Boolean;
{ PostExecute is called after the Execute method. MenuContextList is a list
of IOTAProjectMenuContext. Each item in the list represents an item in
the project manager that is selected }
function PostExecute(const MenuContextList: IInterfaceList): Boolean;
property IsMultiSelectable: Boolean read GetIsMultiSelectable write SetIsMultiSelectable;
end;
Remy's answer is correct, but I'm providing this answer because I have written a little unit to do Project Menu (context menu) integration, and as well, as a bonus, this demo also shows main menu and IDE insight.
The code snippet in my answer covers how to actually write the code which is in several layers of classes, one of which must implement IOTAProjectMenuItemCreatorNotifier interface.
The demo on bitbucket actually does several things that are useful:
As this question asks, it puts a custom item in the project right click context menu.
It also registers a global keyboard shortcut (hotkey).
It also makes the same action visible in the IDE insight search.
It also adds a menu to the main menu.
Handling the interfaces that Remy's answer discusses is non-trivial, so I have made a working example.
unit HelloExpertContextMenu;
// Example of a Project Right Click (Context) menu for Delphi 10 Seattle
// using OTAPI. Must be provided an action list full of actions with valid
// unique names.
//
// Register menu:
//
// Similar code would work in RAD Studio 2010 and newer, but not in older
// Delphi versions.
interface
uses Classes,
SysUtils,
Generics.Collections,
Vcl.ActnList,
ToolsAPI,
Menus,
Windows,
Messages;
type
TProjectManagerMenu = class(TNotifierObject, IOTANotifier, IOTAProjectMenuItemCreatorNotifier)
private
FActionList: TActionList; // reference only.
FProject: IOTAProject; // Reference valid ONLY during MenuExecute
FNotifierIndex: Integer;
FFault:Boolean; // nicer than raising inside the IDE.
{ IOTAProjectMenuItemCreatorNotifier }
procedure AddMenu(const Project: IOTAProject; const Ident: TStrings;
const ProjectManagerMenuList: IInterfaceList; IsMultiSelect: Boolean);
protected
procedure ExecuteVerb(const Verb:string);
public
procedure InstallMenu;
constructor Create(ActionList:TActionList);
procedure MenuExecute(const MenuContextList: IInterfaceList);
property Project: IOTAProject read FProject; // Reference valid ONLY during MenuExecute
property Fault: Boolean read FFault; // InstallMenu fail.
end;
TOTAActionMenu = class(TInterfacedObject, IOTANotifier, IOTALocalMenu)
private
FAction:TAction;
FParent: string;
FPosition: Integer;
public
{ IOTANotifier }
procedure AfterSave;
procedure BeforeSave;
procedure Destroyed;
procedure Modified;
public
{ IOTALocalMenu }
function GetCaption: string;
function GetChecked: Boolean;
function GetEnabled: Boolean;
function GetHelpContext: Integer;
function GetName: string;
function GetParent: string;
function GetPosition: Integer;
function GetVerb: string;
procedure SetChecked(Value: Boolean);
procedure SetEnabled(Value: Boolean);
procedure SetHelpContext(Value: Integer);
procedure SetName(const Value: string);
procedure SetParent(const Value: string);
procedure SetPosition(Value: Integer);
procedure SetVerb(const Value: string);
procedure SetCaption(const Value: string);
property Action: TAction read FAction write FAction; // MUST NOT BE NIL!
property Caption: string read GetCaption write SetCaption;
property Checked: Boolean read GetChecked write SetChecked;
property Enabled: Boolean read GetEnabled write SetEnabled;
property HelpContext: Integer read GetHelpContext write SetHelpContext;
property Name: string read GetName write SetName;
property Parent: string read GetParent write SetParent;
property Position: Integer read GetPosition write SetPosition;
property Verb: string read GetVerb write SetVerb;
end;
TProjectManagerMenuExecuteEvent = procedure (const MenuContextList: IInterfaceList) of object;
TOTAProjectManagerActionMenu = class(TOTAActionMenu, IOTANotifier, IOTALocalMenu, IOTAProjectManagerMenu)
private
FIsMultiSelectable: Boolean;
public
{ IOTAProjectManagerMenu }
function GetIsMultiSelectable: Boolean;
procedure SetIsMultiSelectable(Value: Boolean);
procedure Execute(const MenuContextList: IInterfaceList); overload;
function PreExecute(const MenuContextList: IInterfaceList): Boolean;
function PostExecute(const MenuContextList: IInterfaceList): Boolean;
property IsMultiSelectable: Boolean read GetIsMultiSelectable write SetIsMultiSelectable;
end;
implementation
constructor TProjectManagerMenu.Create(ActionList:TActionList);
begin
inherited Create;
FActionList := ActionList;
end;
procedure TProjectManagerMenu.ExecuteVerb(const Verb: string);
var
AnAction: TAction;
begin
if Assigned(FActionList) then
begin
AnAction := FActionList.FindComponent(Verb) as TAction;
if Assigned(AnAction) then
AnAction.Execute();
end;
end;
procedure TProjectManagerMenu.InstallMenu;
var
OTAProjectManager: IOTAProjectManager;
begin
if Supports(BorlandIDEServices, IOTAProjectManager, OTAProjectManager) then
FNotifierIndex := OTAProjectManager.AddMenuItemCreatorNotifier(Self)
else
FFault := True;
end;
procedure TProjectManagerMenu.AddMenu(const Project: IOTAProject; const Ident: TStrings;
const ProjectManagerMenuList: IInterfaceList; IsMultiSelect: Boolean);
var
AMenu: TOTAProjectManagerActionMenu;
Action:TAction;
n:Integer;
begin
if (not IsMultiSelect) and Assigned(Project) and (Ident.IndexOf(sProjectContainer) <> -1) then
begin
for n := 0 to FActionList.ActionCount-1 do
begin
Action := FActionList.Actions[n] as TAction;
if Action.Name ='' then
Action.Name := 'HelloExpertContextMenuAction'+IntToStr(n+1);
AMenu := TOTAProjectManagerActionMenu.Create;
AMenu.Action := Action;
if AMenu.Caption='' then
AMenu.Caption := 'Menu Item Text Missing'+IntToStr(n);
AMenu.IsMultiSelectable := True;
AMenu.Position := pmmpUserBuild;
ProjectManagerMenuList.Add(AMenu);
end;
end;
end;
procedure TProjectManagerMenu.MenuExecute(const MenuContextList: IInterfaceList);
var
Index: Integer;
MenuContext: IOTAProjectMenuContext;
Verb: string;
begin
try
for Index := 0 to MenuContextList.Count - 1 do
begin
MenuContext := MenuContextList.Items[Index] as IOTAProjectMenuContext;
FProject := MenuContext.Project;
try
Verb := MenuContext.Verb;
ExecuteVerb(Verb);
finally
FProject := nil;
end;
end;
except
on E:Exception do
begin
OutputDebugString(PChar(E.Message));
end;
end;
end;
procedure TOTAActionMenu.AfterSave;
begin
end;
procedure TOTAActionMenu.BeforeSave;
begin
end;
procedure TOTAActionMenu.Destroyed;
begin
end;
procedure TOTAActionMenu.Modified;
begin
end;
function TOTAActionMenu.GetCaption: string;
begin
Result := FAction.Caption;
end;
function TOTAActionMenu.GetChecked: Boolean;
begin
Result := FAction.Checked;
end;
function TOTAActionMenu.GetEnabled: Boolean;
begin
Result := FAction.Enabled;
end;
function TOTAActionMenu.GetHelpContext: Integer;
begin
Result := FAction.HelpContext;
end;
function TOTAActionMenu.GetName: string;
begin
Result := FAction.Name;
end;
function TOTAActionMenu.GetParent: string;
begin
Result := FParent;
end;
function TOTAActionMenu.GetPosition: Integer;
begin
Result := FPosition;
end;
function TOTAActionMenu.GetVerb: string;
begin
Result := FAction.Name; // Name is also Verb
end;
procedure TOTAActionMenu.SetCaption(const Value: string);
begin
FAction.Caption := Value;
end;
procedure TOTAActionMenu.SetChecked(Value: Boolean);
begin
FAction.Checked := Value;
end;
procedure TOTAActionMenu.SetEnabled(Value: Boolean);
begin
FAction.Enabled := Value;
end;
procedure TOTAActionMenu.SetHelpContext(Value: Integer);
begin
FAction.HelpContext := Value;
end;
procedure TOTAActionMenu.SetName(const Value: string);
begin
FAction.Name := Value;
end;
procedure TOTAActionMenu.SetParent(const Value: string);
begin
FParent := Value;
end;
procedure TOTAActionMenu.SetPosition(Value: Integer);
begin
FPosition := Value;
end;
procedure TOTAActionMenu.SetVerb(const Value: string);
begin
FAction.Name := Value; // NAME == VERB!
end;
//=== { TOTAProjectManagerActionMenu } ==========================================
function TOTAProjectManagerActionMenu.GetIsMultiSelectable: Boolean;
begin
Result := FIsMultiSelectable;
end;
procedure TOTAProjectManagerActionMenu.SetIsMultiSelectable(Value: Boolean);
begin
FIsMultiSelectable := Value;
end;
procedure TOTAProjectManagerActionMenu.Execute(const MenuContextList: IInterfaceList);
begin
if Assigned(FAction) then
begin
FAction.Execute;
end;
end;
function TOTAProjectManagerActionMenu.PreExecute(const MenuContextList: IInterfaceList): Boolean;
begin
Result := True;
end;
function TOTAProjectManagerActionMenu.PostExecute(const MenuContextList: IInterfaceList): Boolean;
begin
Result := True;
end;
end.
complete working example on bitbucket at https://bitbucket.org/wpostma/helloworldexpert

I want to create or change an event on a form, dynamically, how can i do that?

i'm trying to write a taskbar for my program, and i need to add one line of code in 2 events, OnClose and OnActivate, but my program has over 100 forms, so i'd like to do it dynamically. Is there an way to do it?
The language is Delphi 7.
As TLama and David Hefferman say, inheritance is right way to do it, but sometimes pragmatism wins out over the right way. So here is a horrible Kludge that will do the job.
unit Unit1;
interface
uses
VCL.Forms,
System.Classes,
System.Generics.Collections;
type
TKludge = class
private
fForm: TForm;
fOnClose: TCloseEvent;
fOnActivate: TNotifyEvent;
procedure fNewOnActivate( Sender : TObject );
procedure fNewOnClose( Sender : TObject; var Action : TCloseAction );
public
property Form : TForm
read fForm;
property OnClose : TCloseEvent
read fOnClose;
property OnActivate : TNotifyEvent
read fOnActivate;
constructor Create( pForm : TForm );
end;
TKludges = class( TObjectList<TKludge> )
private
fApplication: TApplication;
procedure SetApplication(const Value: TApplication);
public
property Application : TApplication
read fApplication
write SetApplication;
end;
implementation
{ TKludge }
constructor TKludge.Create(pForm: TForm);
begin
fForm := pForm;
fOnClose := pForm.OnClose;
pForm.OnClose := fNewOnClose;
fOnActivate := pForm.OnActivate;
pForm.OnActivate := fOnActivate;
end;
procedure TKludge.fNewOnActivate(Sender: TObject);
begin
if assigned( fOnActivate ) then fOnActivate( Sender );
// my extra line
end;
procedure TKludge.fNewOnClose(Sender: TObject; var Action: TCloseAction);
begin
if assigned fOnClose then fOnClose( Sender, Action );
// my extra line
end;
{ TKludges }
procedure TKludges.SetApplication(const Value: TApplication);
var
i: Integer;
begin
fApplication := Value;
for i := 0 to fApplication.ComponentCount do
begin
if fApplication.Components[ i ] is TForm then
begin
Add( TKludge.Create( fApplication.Components[ i ] as TForm ));
end;
end;
end;
end.
Create an instance of the TKludges class and pass the Application to it. For every form that it finds it will replace the events with new ones that call the original if it exists and put your extra lines in (just comments at the minute).
What makes it particularly horrible is the EVERY form will be affected, including ones you might not expect. But if you are sure...
Use at your own risk!

Setting up an eventHandler with return value

I need to call an event handler to tell a user to select a TCard or TLabel
and then return this value as a parameter.
I have two units GAME , SS_SPELL
This is how the code in SS_SPEll works.
// TSPELL
// ======
// The chronology is:
// 1. At appropriate points in the game (such as before turn, after cast etc aka trigger points)
// the game calls the SpellMeister's RunSpells method.
// 2. RunSpells checks the database for spells matching the card that
// initiated the spell action, and the trigger point at which it did so.
// For each one that it finds it will create an appropriate object, which
// could be a TSpell descendent or a TSpellAdjuster descendent.
// For each TSpell it finds it fires off an onFindSpell event.
// See below for details of how TSpellAdjusters are handled.
// 3. The handler for the onFindSpell events can (should) call the spells'
// AimAt method for each potential target. A potential target is a card
// or a player.
// 4. A spell's AimAt method checks if the potential target is a legitimate
// target for that spell and if so it calls its ApplySpellTo method to
// actually do the dirty deed.
So what i need is once the RunSpells gets the db info it will check if needs2ndtarget := 1 , if so then i know i need a second target for this spell.
Here is TSpellBase it is the class TSpell is created from. In an attampt to create this event i have added FOnSeek2ndTarget
in the privite section and FNeed2Target in the Protected and the public property OnSeek2ndTarget. You will also see
the TTargetEvt, currently its setup to recive TCARD but i need it to recive TCard or TLAbel no idea how to do this.
TTargetEvt = procedure (Card : TCard) of Object;
TSpellBase = class
private
FOnManaChange: TManaEvt;
FOnSeek2ndTarget: TTargetEvt;
protected
FCardType : TCardType;
FOriginator : TCard;
FNeed2Target : integer;
function LegitimateTarget (Candidate : TObject) : boolean; virtual;
public
constructor Create; virtual;
property CardType : TCardType read FCardType write FCardType; // ctLava, ctNature, ctWizard, etc etc
property Originator : TCard read FOriginator write FOriginator;
property Need2Target : integer read FNeed2Target write FNeed2Target;
property OnManaChange : TManaEvt read FOnManaChange write FOnManaChange;
property OnSeek2ndTarget : TTargetEvt read FonSeek2ndTarget write FOnSeek2ndTarget;
end;
Now in TSpell , i dont think i need anything chnaged here but its needed for the spellmiester.runspell procedure
TSpell = class(TSpellBase)
private
protected
FCategory : TCategory;
FLifeToAdd : Byte;
FMaxRandom : Byte;
FReplaceDmg : Byte;
FReplacement : string;
FStatTarget : Byte;
FTrigger : TTrigger;
procedure ApplySpellTo(Target : TObject); virtual; abstract; // Apply the spell to the target
public
procedure AimAt(Candidate: TObject); virtual;
property Category : TCategory read FCategory write FCategory;
property LifeToAdd : Byte read FLifeToAdd write FLifeToAdd;
property MaxRandom : Byte read FMaxRandom write FMaxRandom;
property ReplaceDmg : Byte read FReplaceDmg write FReplaceDmg;
property Replacement : string read FReplacement write FReplacement;
property StatTarget : Byte read FStatTarget write FStatTarget;
property Trigger : TTrigger read FTrigger write FTrigger;
end;
Here i added the FOnSeek2ndTarget in private section. and the property So now when a spell is casted it will
get to here and now calls runsspells.
TSpellMeister = class
private
FonFindSpell : TRcvSpell;
FOnManaChange : TManaEvt;
FOnSeek2ndTarget : TTargetEvt;
// FonNewAdjuster : TRcvSpell;
protected
FAdjusters : TAdjusters;
FQuery : TADOQuery;
public
constructor Create(DBCon: TADOConnection);
destructor Destroy; override;
function IfNull( const Value, Default : OleVariant ) : OleVariant;
procedure Adjust(Attacker : TCard; Victim : TObject; var TheDamage : integer); overload;
procedure Adjust(Attacker : TCard; var TheCost : integer); overload;
procedure RunSpells(Card : TCard; Trigger : TTrigger);
property onFindSpell : TRcvSpell read FonFindSpell write FonFindSpell;
property OnManaChange : TManaEvt read FOnManaChange write FOnManaChange;
property OnSeek2ndTarget: TTargetEvt read FOnSeek2ndTarget write FOnSeek2ndTarget;
// property onNewAdjuster : TRcvSpell read FonNewAdjuster write FonNewAdjuster;
end;
This is where the issue is, i added foundspell.Need2ndTarget this gets data from database if it is a 1 then it needs the user to select another target for the spell. Currently i added
if FoundSpell.Need2Target = 1 then
FOnSeek2ndTarget(Card);
but i am sure that is not correct...
//**************************************************************************
procedure TSpellMeister.RunSpells(Card: TCard; Trigger: TTrigger);
//**************************************************************************
var
OneSpell : TSpellBase;
FoundSpell : TSpell; // Just so only have to cast once
begin
assert(assigned(FonFindSpell),'TSpellMeister.RunSpells : No onFindSpell event handler!');
// Search the database
FQuery.Active := FALSE;
FQuery.Parameters.ParamByName(SQL_PARAM_SPELL_ORIGINATOR).Value := Card.CName;
FQuery.Parameters.ParamByName(SQL_PARAM_SPELL_TRIGGER ).Value := Trigger;
FQuery.Active := TRUE;
// Iterate through the spell records. For each one, create a category-specific
// TSpell descendant and fire off an onFindSpell event.
if FQuery.RecordCount > 0 then
begin
FQuery.Recordset.MoveFirst;
while not FQuery.Recordset.EOF do
begin
case TCategory(FQuery.Recordset.Fields[DB_FLD_CATEGORY].Value) of
caAboveLife : OneSpell := TSpellAboveLife.Create;
caDamage : OneSpell := TSpellDamage.Create;
caDamagePlus : OneSpell := TSpellDamagePlus.Create;
caDamagePlusPercent : OneSpell := TSpellDamagePlusPercent.Create;
caDamagePercentIncrease : OneSpell := TSpellDamagePercentIncrease.Create;
caDamagePercentDecrease : OneSpell := TSpellDamagePercentDecrease.Create;
caDamageSpells : OneSpell := TSpellDamageSpells.Create;
caDestroy : OneSpell := TSpellDestroy.Create;
.....
else raise ERangeError.CreateFmt(ERROR_INVALID_DB_NUMBER,[DB_FLD_CATEGORY,FQuery.Recordset.Fields[DB_FLD_CATEGORY].Value]);
end;
try
if assigned(OneSpell) then
begin
OneSpell.CardType := TCardType (IfNull( FQuery.Recordset.Fields[ DB_FLD_CARD_TYPE ].Value,0) );
OneSpell.Originator := Card;
OneSpell.OnManaChange := Self.OnManaChange;
OneSpell.OnSeek2ndTarget := self.OnSeek2ndTarget;
assert(OneSpell.Originator.COwner is TPlayer,'TSpellMeister.RunSpells : OneSpell.Originator.COwner not a player: ' + OneSpell.Originator.COwner.ClassName);
try
FoundSpell := TSpell(OneSpell);
FoundSpell.Originator := Card;
FoundSpell.Trigger := Trigger;
FoundSpell.CardType := TCardType ( FQuery.Recordset.Fields[ DB_FLD_CARD_TYPE ].Value );
FoundSpell.Category := TCategory ( FQuery.Recordset.Fields[ DB_FLD_CATEGORY ].Value );
FoundSpell.LifeToAdd := IfNull( FQuery.Recordset.Fields[ DB_FLD_LIFE_TO_ADD ].Value,0);
FoundSpell.MaxRandom := IfNull( FQuery.Recordset.Fields[ DB_FLD_MAX_RANDOM ].Value,0);
FoundSpell.PerCent := IfNull( FQuery.Recordset.Fields[ DB_FLD_PER_CENT ].Value,0);
FoundSpell.Plus := IfNull( FQuery.Recordset.Fields[ DB_FLD_PLUS ].Value,0);
FoundSpell.ReplaceDmg := IfNull( FQuery.Recordset.Fields[ DB_FLD_REPLACE_DMG ].Value,0);
FoundSpell.Replacement := IfNull( FQuery.Recordset.Fields[ DB_FLD_REPLACEMENT ].Value,0);
FoundSpell.StatTarget := IfNull( FQuery.Recordset.Fields[ DB_FLD_STAT_TARGET ].Value,0);
FoundSpell.Target := TTargetType( IfNull(FQuery.Recordset.Fields[ DB_FLD_TARGET ].Value,0) );
FoundSpell.Need2Target := IfNull( FQuery.Recordset.Fields[ DB_FLD_NEED2TARGET ].Value,0);
assert(FoundSpell.Originator.COwner is TPlayer,'TSpellMeister.RunSpells : FoundSpell.Originator.COwner not a player: ' + OneSpell.Originator.COwner.ClassName);
if FoundSpell.Need2Target = 1 then
FOnSeek2ndTarget(Card);
FonFindSpell(FoundSpell);
finally
FreeAndNil(OneSpell);
end;
end;
except // I think this is OK but is there a possible bug if
FreeAndNil(OneSpell); // spell adjuster added to list then destroyed?
end; // List item would then be invalid.
FQuery.Recordset.MoveNext;
end;
end;
end;
So all that is the ss_spells unit, now the game unit which uses ss_spells unit
in the forum.create i have
FSpellMeister.OnSeek2ndTarget := self.Handle2ndTarget;
with no idea what to put in Handle2ndTarget currently its just
//****************************************************************************
procedure TFGame.Handle2ndTarget(Card : TCard);
begin
showmessage('Select a target HANDLE2ndTARGET');
end;
just to see if i could at least get here..
So with this my question if you cant make it out, How do i set a var in ss_Spells to an TObject (tcard or tlabel) when the foundspell.Need2ndTarget := 1 using the event FOnSeek2ndTarget();
Simply change the signature of your TTargetEvt type, eg:
TTargetEvt = procedure (Card : TCard; var Target: TObject) of Object;
Then update RunSpells() accordingly:
var
Target: TObject;
...
if FoundSpell.Need2Target = 1 then
begin
Target := nil;
if Assigned(FOnSeek2ndTarget) then FOnSeek2ndTarget(Card, Target);
// use Target as needed...
end;
...
Then update your handler accordingly:
procedure TFGame.Handle2ndTarget(Card : TCard; var Target: TObject);
begin
Target := ...;
end;
I need to know how to use an event to return a var parameter.
Define your event like this:
type
TMyEvent = procedure(var ReturnValue: Integer) of object;
And then you add the event property in the usual way:
....
private
FOnMyEvent: TMyEvent;
....
published
property OnMyEvent: TMyEvent read FOnMyEvent write FOnMyEvent;
....
The nuance comes in how you surface the event. Generally, if you are writing a component that offers events, you must cater for the eventuality that there will be no handler attached to the event. And if the event is meant to return a value, how can you have no hander and also a returned value? The trick is to assign the parameter to the default, before you surface the event. For example:
procedure TMyComponent.DoMyEvent(out ReturnValue: Integer);
begin
Result := DefaultValueForMyEventHandler;// you supply something meaningful here
if Assigned(FOnMyEvent) then
FOnMyEvent(Result);
end;
So, if the consumer of the component has not supplied a handler for the event, then the method still yields a reasonable value.
If you read this and think that it makes no sense for FOnMyEvent to be nil, then your design is wrong. If you want to force the consumer to supply behaviour, and not be allowed to rely on a default, then an event is the wrong mechanism. In that case ask the consumer to supply the behaviour via a parameter, perhaps enforced by the signature of the component's constructor. Or maybe some other way.
I've just given you a basic example un-related to your code. I've tried to get across the concepts. Hopefully you can adapt this to your specific needs.

Call a procedure(by an event) whose name is set by another procedure

I want a procedure to be executed when an event is happened. But that procedure is set by another procedure(SetNotifierProc).
Firstly I run this:
SetNotifierProc(Proc1);
And then Proc1 is executed whenever event triggered.
How could I code SetNotifierProc to get a procedure as an argument and how to inform event handler to execute that procedure?
Problem: I have a TCPServerExecute and want to run a procedure to show received data. But because I have multiple forms I want to set a procedure that handle received data.
Thanks
If your procedure is an ordinary procedure without arguments:
Type
TForm1 = Class(TForm)
..
private
FMyProc : TProcedure;
public
procedure SetEventProc(aProc : TProcedure);
procedure TheEvent( Sender : TObject);
end;
procedure Test;
begin
// Do something
end;
procedure TForm1.SetEventProc(aProc: TProcedure);
begin
Self.FMyProc := aProc;
end;
procedure TForm1.TheEvent(Sender: TObject);
begin
if Assigned(FMyProc) then
FMyProc;
end;
// to set the callback to procedure "Test"
Form1.SetEventProc(Test);
If your procedure has arguments, declare a procedure type:
Type
MyProcedure = procedure( aString : String);
And if your procedure is a method :
Type
MyMethod = procedure( aString : String) of Object;
See also documentation about Procedural types.
This should do the trick :-
Type
TTCPNotifyProc = Procedure(pData : String) Of Object;
TMyTCPServer = Class
Private
FNotifyProc : TTCPNotifyProc;
..
Public
Procedure SetNotifier(pProc : TTCPNotifyProc);
End;
Procedure TMyTCPServer.SetNotifier(pProc : TTCPNotifyProc);
Begin
FNotifyProc := pProc;
End;
Then whenever you need to call the procedure within your server class just call :-
If Assigned(FNotifyProc) Then
FNotifyProc(DataStringReceived);

Delphi 2010 Can I have a TFrame with Generic properties and methods to pass an event?

I have a TFrame that I use for searching for entities in a Delphi 2010 VCL project, in the TFrame I have a button edit, that allows the user to open a specific form to browse for that entity. (All the browse forms inherit from a common base browse form)
Currently I achieve this by inheriting from the base frame, then implement the Browse event that fires off the specific form. The only difference each time is what form (type) is shown on the click event, is there a way I can achieve this with generics.
That way I can reuse the same base frame without having to rewrite the same code for each entity (there are over 100), and at form create of the host form pass the type constraint to open the appropriate form on browse.
I have tried adding a generic type to the frame:
type
Browser<T: TfrmBrowser, constructor> = class
class function BrowseForm(Owner: Tcomponent): T;
end;
class function Browser<T>.BrowseForm(Owner: Tcomponent): T;
var
_browseForm: T;
begin
_browseForm := T.Create; // 1st problem T.Create(Owner); throws a comile error
Result := _browseForm;
end;
and then in the picker frame I expose Start that can be called from the the host form's create event:
procedure TPickerFrame.Start<T>(const idProp, nameProp, anIniSection: string; aDto: IDto);
begin
_browseForm:= Browser<T>.BrowseForm(self);
_iniSectionName:= anIniSection;
_idField:= idProp;
_descriptionField:= nameProp;
_dto := aDto;
end;
the truth is, I don't really get generics in Delphi, and none of this is working.
Below are excerpts from the frame:
_browseForm: TfrmBrowser;
procedure TPickerFrame.Browse(var DS: TDataSet; var Txt: string; var mr: TModalResult);
begin
// How do I achieve this with Generics
// _browseForm := T.Create(nil); // <-- this line is what needs to know the form type at runtime
// Everything else from here is the same
_browseForm.ProductName := Application.Title;
_browseForm.PageSize := 20;
_browseForm.DatabaseType := bdbtADO;
_browseForm.ADOConnection := dmdbWhereHouse.BaseADOConnection;
_browseForm.INISectionName := _iniSectionName;
_browseForm.DoSelBrowse(DS, Txt, mr, _descriptionField, _text);
if mr = mrOk then
begin
DoSelect(DS);
end;
end;
Does anyone have any experience with a similar requirement? Any help would be appreciated.
Thanks
Below is an example of the rack master browser:
type
TfrmMbfRACK_MASTER = class(TMxfrmBrowseHoster)
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
//...
private
FWHID: Integer;
procedure SetWHID(const Value: Integer);
{ Private declarations }
public
{ Public declarations }
procedure BuildADO(Sender: TObject; Q: TADOQuery); override;
end;
implementation
{$R *.DFM}
{ TfrmMbfRACK_MASTER }
procedure TfrmMbfRACK_MASTER.FormCreate(Sender: TObject);
begin
inherited;
fmeMxFrmBrowseHoster1.KeyField := 'RACK_ID';
// FWHID := -2; // 22/06/04
FWHID := 0; // 22/06/04
end;
procedure TfrmMbfRACK_MASTER.BuildADO(Sender: TObject; Q: TADOQuery);
begin
Q.Close;
Q.SQL.Clear;
Q.SQL.Add(
'SELECT R.RACK_DESC, R.RACK_BARCODE, W.ERP_WH, WC.CLASS_NAME, W.DESCRIPTION WAREHOUSE, R.RACK_PACKING_ORDER, ');
//...
end;
The base class
type
TMxfrmBrowseHoster = class(TfrmMxForm)
protected
// ...
procedure FormCreate(Sender: TObject);
procedure BuildADO(Sender: TObject; ADOQ: TADOQuery); virtual; abstract;
public
procedure TMxfrmBrowseHoster.FormCreate(Sender: TObject);
begin
TMxFormProductName := Application.Title;
fmeMxFrmBrowseHoster1.Initialise;
INISectionName := Name;
AbortAction := False;
fmeMxFrmBrowseHoster1.OnSelect := SelectNormaliser;
fmeMxFrmBrowseHoster1.OnNeedADO := BuildADO;
fmeMxFrmBrowseHoster1.INISectionName := self.Name;
fmeMxFrmBrowseHoster1.MultiSelect := dxBarLargeButton10.Down;
fmeMxFrmBrowseHoster1.AutoSaveGrid := True;
dxBarEdit1.OnChange := ActPageSizeChangedExecute;
FormStorage.RestoreFormPlacement;
ActConfirmDelete.Execute;
end;
I find your question a little on the vague side and I'm not 100% sure I understand exactly what you are asking. However, I know how to deal with your problem when calling the constructor. Perhaps that's all you need help with.
You need to use virtual constructor polymorphism and a bit of casting:
class function Browser<T>.BrowseForm(Owner: Tcomponent): T;
var
_browseForm: T;
begin
_browseForm := TfrmBrowser(T).Create(Owner);
Result := _browseForm;
end;
This relies on virtual constructor polymorphism. So you must make sure that each constructor for every class derived from TfrmBrowser is marked with the override directive.

Resources