How to do something similar to C# Invoke() in Delphi? - delphi

I'm writing a project with Delphi. I need to do something to do the function that the Invoke method in C# does. I saw the Synchronize() method do this somewhere, but I don't know exactly how to write it. How can I do it ?
C# code:
private void ListenerOnDataTransmit(DataTransmitEventArgs e)
{
transmittedMsg = BitConverter.ToString(e.TransmittedBytes, 0, e.TransmittedBytes.Length);
//I want to write here
try { Invoke(new EventHandler(UpdateTransmittedMessagesListView)); }
catch { }
}
// UpdateTransmittedMessagesListView
public void UpdateTransmittedMessagesListView(object sender, EventArgs e)
{
ListViewItem item = new ListViewItem(DateTime.Now.ToLongTimeString());
item.SubItems.Add(transmittedMsg);
lvTransmittedMessages.Items.Insert(0, item);
if (lvTransmittedMessages.Items.Count > 100)
lvTransmittedMessages.Items.Clear();
}
public MainForm()
{
InitializeComponent();
plistener.OnDataTransmit += new DataTransmitEventHandler(ListenerOnDataTransmit);
}
The codes are like this, I want to do the operations on the Invoke line in the first function.

In C#, the Invoke() method "Executes a delegate on the thread that owns the control's underlying window handle".
In Delphi, UI controls are (supposed to be) owned by the main UI thread. You can use the TThread.Synchronize() or TThread.Queue() method to execute code from a worker thread in the context of the main UI thread, eg:
type
TWorkerThread = class(TThread)
private
FData: string;
procedure DoUpdate;
protected
procedure Execute; override;
end;
procedure TWorkerThread.Execute;
begin
...
FData := ...;
Synchronize(DoUpdate);
...
end;
procedure TWorkerThread.DoUpdate;
var
Item: TListItem;
begin
Item := MyForm.lvTransmittedMessages.Items.Add;
Item.Caption := DateTimeToStr(Now);
Item.SubItems.Add(FData);
if MyForm.lvTransmittedMessages.Items.Count > 100 then
MyForm.lvTransmittedMessages.Items.Clear;
end;
Alternatively:
procedure SomeWorkerThreadProc;
var
s: string;
begin
s := ...;
TThread.Synchronize(nil,
procedure
var
Item: TListItem;
begin
Item := MyForm.lvTransmittedMessages.Items.Add;
Item.Caption := DateTimeToStr(Now);
Item.SubItems.Add(s);
if MyForm.lvTransmittedMessages.Items.Count > 100 then
MyForm.lvTransmittedMessages.Items.Clear;
end
);
end;

Related

Delphi TThread descendant return result

SITUATION. I have created an unit with some classes to solve algebra stuff (congruences and systems), I am showing you the code:
type
TCongrError = class(Exception)
end;
type
TCongruence = class(TComponent)
//code stuff
constructor Create(a, b, n: integer); virtual;
end;
type
TCongrSystem = array of TCongruence;
type
TCongruenceSystem = class(TThread)
private
resInner: integer;
FData: TCongrSystem;
function modinv(u, v: integer): integer; //not relevant
protected
procedure Execute; override;
public
constructor Create(data: TCongrSystem; var result: integer; hasClass: boolean);
end;
I have decided to use TThread because this class has an Execute method that could take some time to finish due to the length of the parameters passed to the constructor. Here's the implementation:
constructor TCongruenceSystem.Create(data: TCongrSystem; var result: integer; hasClass: boolean);
begin
inherited Create(True);
FreeOnTerminate := true;
FData := data;
setClass := hasClass;
resInner := result;
end;
procedure TCongruenceSystem.Execute;
var sysResult, i, n, t: integer;
begin
sysResult := 0;
n := 1;
//computation
Queue( procedure
begin
ShowMessage('r = ' + sysResult.ToString);
resInner := sysResult;
end );
end;
PROBLEM
If you look at the Queue you see that I am using (just as test) the ShowMessage and it is showing the correct value of sysResult. The second line by the way has some problems that I cannot understand.
The constructor has var result: integer so I can have side-effect from the passed variable and then I can assign resInner := result;. At the end (in the Queue) I am giving resInner the value of sysResult and I expect result to be updated too due to the side effect of var. Why doesn't this happen?
I have made another test changing the constructor like this:
constructor TCongruenceSystem.Create(data: TCongrSystem; result: TMemo; hasClass: boolean);
//now of course I have resInner: TMemo
And changing the Queue to this:
Queue( procedure
begin
ShowMessage('r = ' + sysResult.ToString);
resInner.Lines.Add(sysResult.ToString);
end ); //this code now works properly in both cases! (showmessage and memo)
In the constructor I am passing TMemo which is a reference and ok, but isn't the original var result: integer passed as reference too? Why then it doesn't work?
I want to do this because I'd like to do something like this:
//I put var a: integer; inside the public part of the TForm
test := TCongruenceSystem.Create(..., a, true);
test.OnTerminate := giveMeSolution;
test.Start;
test := nil;
Where giveMeSolution is just a simple procedure that uses the variable a containing the result of the system. If this is not possible what could I do? Basically the result at the end of Execute is just an integer number that has to be passed to the main thread.
I have read about ReturnValue but I am not sure how to use it.
Basically the result at the end of Execute is just an integer number that has to be passed to the main thread.
I have read about ReturnValue but I am not sure how to use it.
Using the ReturnValue property is very easy:
type
TCongruenceSystem = class(TThread)
...
protected
procedure Execute; override;
public
property ReturnValue; // protected by default
end;
procedure TCongruenceSystem.Execute;
var
...
begin
// computation
ReturnValue := ...;
end;
test := TCongruenceSystem.Create(...);
test.OnTerminate := giveMeSolution;
test.Start;
....
procedure TMyForm.giveMeSolution(Sender: TObject);
var
Result: Integer;
begin
Result := TCongruenceSystem(Sender).ReturnValue;
...
end;
Let's assume a class field FFoo : integer; ;
procedure TFoo.Foo(var x : integer);
begin
FFoo := x;
end;
Here what you are doing is assigning the value of x to FFoo. Inside the method Foo you are free to modify the value of the variable passed in as x but integers are otherwise value types that are copied on assignment. If you want to keep a reference to an external integer variable you would need to declare FFoo (or, in your case, resInner) as a PInteger (pointer to an integer). For example (simplifying) :
TCongruenceSystem = class(TThread)
private
resInner: PInteger;
protected
procedure Execute; override;
public
constructor Create(result: PInteger);
end;
where
constructor TCongruenceSystem.Create(result: PInteger);
begin
inherited Create(True);
FreeOnTerminate := true;
resInner := result;
end;
which you would call as test := TCongruenceSystem.Create(#a); and assign:
{ ** See the bottom of this answer for why NOT to use }
{ Queue with FreeOnTerminate = true ** }
Queue( procedure
begin
ShowMessage('r = ' + sysResult.ToString);
resInner^ := sysResult;
end );
The reason it works with TMemo is that classes are reference types - their variables do not hold values but rather point to the address of the object in memory. When you copy a class variable you are only copying a reference (ie: a pointer) whereas for value types the contents of the variable are copied on assignment.
With that said, there's nothing stopping you from keeping the argument typed as var x : integer and taking a reference in your constructor :
constructor TCongruenceSystem.Create(var result: Integer);
begin
inherited Create(True);
FreeOnTerminate := true;
resInner := #result; {take the reference here}
end;
but this gives the caller the impression that once the constructor is complete that you have made any modifications to the variable you intend to and they are free to dispose of the integer. Passing explicitly as PInteger gives the caller a hint that your object will keep a reference to the integer they provide and that need to ensure the underlying variable remains valid while your class is alive.
And... with all that said, I still fundamentally don't like this idea. By taking in a variable reference like this you are offloading an atypical lifetime management issue to the caller. Passing pointers is best done in place where they are used at the point of transfer only. Holding onto a foreign pointer is messy and it's too easy for mistakes to happen. A far better approach here would be to provide a completion event and have the consumer of your class attach a handler.
For example :
{ define a suitable callback signature }
TOnCalcComplete = procedure(AResult : integer) of object;
TCongruenceSystem = class(TThread)
private
Fx, Fy : integer;
FOnCalcComplete : TOnCalcComplete;
protected
procedure Execute; override;
public
constructor Create(x,y: integer);
property OnCalcComplete : TOnCalcComplete read FOnCalcComplete write FOnCalcComplete;
end;
constructor TCongruenceSystem.Create(x: Integer; y: Integer);
begin
inherited Create(true);
FreeOnTerminate := true;
Fx := x;
Fy := y;
end;
procedure TCongruenceSystem.Execute;
var
sumOfxy : integer;
begin
sumOfxy := Fx + Fy;
sleep(3000); {take some time...}
if Assigned(FOnCalcComplete) then
Synchronize(procedure
begin
FOnCalcComplete(sumOfxy);
end);
end;
Which you would then call as :
{ implement an event handler ... }
procedure TForm1.CalcComplete(AResult: Integer);
begin
ShowMessage(IntToStr(AResult));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
LCongruenceSystem : TCongruenceSystem;
begin
LCongruenceSystem := TCongruenceSystem.Create(5, 2);
LCongruenceSystem.OnCalcComplete := CalcComplete; { attach the handler }
LCongruenceSystem.Start;
end;
You'll also notice that I used Synchronize here instead of Queue. On this topic, please have a read of this question (I'll quote Remy...):
Ensure all TThread.Queue methods complete before thread self-destructs
Setting FreeOnTerminate := True in a queued method is asking for a memory leak.

Implement stack of function pointers in Delphi

We have declared a type which can be used as a progress callback (such as loading every 10,000 lines from a gigantic log file):
// Declared in some base unit
TProcedureCallback = procedure() of object;
// Declared in the class that loads the events
procedure ReadEvents(callback: TProcedureCallback);
// Implementation of above method
procedure TEvents.ReadEvents(callback: TProcedureCallback);
var
nEvents: Integer;
begin
nEvents := 0;
// Read some events...
Inc(nEvents);
// ...and repeat until end of log file
// Every 10,000 events, let the caller know (so they update
// something like a progress bar)
if ((nEvents mod 10000) = 0) then
callback();
end;
// And the caller uses it like this
public
procedure EventsLoadCallBack();
// Implementation of callback
procedure TfrmLoadEvents.EventsLoadCallBack();
begin
// Update some GUI control...
end;
// And the events are loaded like this
events.ReadEvents(EventsLoadCallBack);
This all works very well...but I'd like to extend this to the TObjectStack container so that we can implement an automatic log off feature. The idea is that as each form is created, it registers a callback (ie. pushes it onto some system-wide stack). And when the form is destroyed, it pops the callback off the stack. If the auto log off occurs, you just unwind the stack and return the user to the main form and then do the rest of work associated with an automatic log off.
But, I cannot get it working...when I try and push a TProcedureCallback object onto the stack I get compiler errors:
// Using generic containers unit from Delphi 7
uses
Contnrs;
// Declare stack
stackAutoLogOff: TObjectStack;
// Initialise stack
stackAutoLogOff := TObjectStack.Create();
// Attempt to use stack
stackAutoLogOff.Push(callback);
stackAutoLogOff.Push(TObject(callback));
// Clean up...
stackstackAutoLogOff.Free();
The 1st returns Incompatible types and the 2nd Invalid typecast. What is the correct way to implement a stack of function pointers?
The problem is that TObjectStack expects to contain objects of type TObject and your callback is a TMethod type, which is a record containing two pointers.
If you are using a modern version of Delphi a simple solution is to use generics. For example:
TObjectProc = procedure of object;
TMyCallbackStack = TStack<TObjectProc>;
Without generics, you would need to build your own stack class to manage storage of the callbacks. This is a reasonably simple class to build and, at its most basic, might look something like this :
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyClass = class
procedure foo;
end;
TObjProc = procedure of object;
TObjProcStack = class(TObject)
private
FList: array of TObjProc;
public
function Count: Integer;
procedure Push(AItem: TObjProc);
function Pop: TObjProc; inline;
function Peek: TObjProc; inline;
end;
function TObjProcStack.Peek: TObjProc;
begin
Result := FList[Length(FList)-1];
end;
function TObjProcStack.Pop: TObjProc;
begin
Result := Peek();
SetLength(FList, Length(FList) - 1);
end;
procedure TObjProcStack.Push(AItem: TObjProc);
begin
SetLength(FList, Length(FList) + 1);
FList[Length(FList)-1] := AItem;
end;
function TObjProcStack.Count: Integer;
begin
Result := Length(FList);
end;
{TMyClass}
procedure TMyClass.Foo;
begin
WriteLn('foo');
end;
var
LMyClass : TMyClass;
LStack : TObjProcStack;
begin
LStack := TObjProcStack.Create;
LMyClass := TMyClass.Create;
try
LStack.Push(LMyClass.foo);
LStack.Pop; {executes TMyClass.Foo - outputs 'foo' to console}
finally
LStack.Free;
LMyClass.Free;
end;
ReadLn;
end.
You can wrap the callback in an object and then use the standard Stack options. By wrapping that in your own class, you have a complete solution, like this:
unit UnitCallbackStack;
interface
uses
Contnrs;
type
TProcedureCallback = procedure() of object;
type
TMyCallbackObject = class // wrapper for callback
private
FCallBack : TProcedureCallback;
protected
public
constructor Create( ACallback : TProcedureCallback ); reintroduce;
property CallBack : TProcedureCallback
read FCallBack;
end;
type
TCallBackStack = class( TObjectStack)
private
public
function Push(ACallback: TProcedureCallback): TProcedureCallback; reintroduce;
function Pop: TProcedureCallback; reintroduce;
function Peek: TProcedureCallback; reintroduce;
end;
implementation
{ TCallBackStack }
function TCallBackStack.Peek: TProcedureCallback;
var
iObject : TMyCallbackObject;
begin
iObject := inherited Peek as TMyCallbackObject;
if assigned( iObject ) then
begin
Result := iObject.CallBack; // no delete here as reference not removed
end
else
begin
Result := nil;
end;
end;
function TCallBackStack.Pop: TProcedureCallback;
var
iObject : TMyCallbackObject;
begin
iObject := inherited Pop as TMyCallbackObject;
if assigned( iObject ) then
begin
Result := iObject.CallBack;
iObject.Free; // popped, so no longer needed
end
else
begin
Result := nil;
end;
end;
function TCallBackStack.Push(ACallback: TProcedureCallback): TProcedureCallback;
begin
inherited Push( TMyCallbackObject.Create( ACallBack ));
end;
{ TMyCallbackObject }
constructor TMyCallbackObject.Create(ACallback: TProcedureCallback);
begin
inherited Create;
fCallBack := ACallBack;
end;
end.
You can then use TCallBackStack the way you are trying to use TStack.

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

Override of protected method never gets called on TObjectDispatch

I'm trying to extend a protected virtual method of TObjectDispatch. But this method never gets called.
[edited to reproduce the problem].
When I override GetPropInfo and use it in TMyDispatch it works as expected. The overrided method is called. However the overrided method on TMyDispatchItem when created by TMyDispatch (to simulate my real world example) is not called.
{$METHODINFO ON}
TExtDispatch = class(TObjectDispatch)
protected
function GetPropInfo(const AName: string; var AInstance: TObject;
var CompIndex: Integer): PPropInfo; override;
public
constructor Create;
end;
TMyDispatchItem = class(TExtDispatch)
private
FItemValue: string;
public
procedure ShowItemValue;
published
property ItemValue: string read FItemValue write FItemValue;
end;
TMyDispatch = class(TExtDispatch)
public
function GetItem: TMyDispatchItem;
private
FValue: string;
public
procedure ShowValue;
published
property Value: string read FValue write FValue;
end;
{$METHODINFO OFF}
TTestForm = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
TestForm: TTestForm;
implementation
{$R *.dfm}
procedure TTestForm.Button1Click(Sender: TObject);
var
V: Variant;
VI: Variant;
begin
V := IDispatch(TMyDispatch.Create);
V.Value := 100; //this calls inherited getpropinfo
V.ShowValue;
VI := V.GetItem;
VI.ItemValue := 5; //this doesn't
VI.ShowItemValue;
end;
{ TExtDispatch }
constructor TExtDispatch.Create;
begin
inherited Create(Self, False);
end;
function TExtDispatch.GetPropInfo(const AName: string; var AInstance: TObject;
var CompIndex: Integer): PPropInfo;
begin
Result := inherited GetPropInfo(AName, AInstance, CompIndex);
ShowMessage('GetPropInfo: ' + AName);
end;
{ TMyDispatch }
function TMyDispatch.GetItem: TMyDispatchItem;
begin
Result := TMyDispatchItem.Create;
end;
procedure TMyDispatch.ShowValue;
begin
ShowMessage('My dispatch: ' + Value);
end;
{ TMyDispatchItem }
procedure TMyDispatchItem.ShowItemValue;
begin
ShowMessage('My item value: ' + FItemValue);
end;
end.
I've actually found a way to overcome this problem by changing the datatype of TMyDispatch.GetItem to return as a Variant instead. Like this:
function TMyDispatch.GetItem: Variant;
begin
Result := IDispatch(TMyDispatchItem.Create);
end;
And now suddenly the overrided method is called. I really would like to understand what's going on here.
Any more ideas or explainations?
Virtual method dispatch in Delphi is known to work. So, if TExtDispatch.GetPropInfo is not being executed then these are the possible reasons:
The GetPropInfo method is not being called at all.
The actual instance on which GetPropInfo is being called is not an instance of TExtDispatch.
If you showed the rest of the code then we could be more sure, but the above options should be enough for you to work it out.
The only place that calls GetPropInfo is GetIDsOfNames. If your overridden GetIDsOfNames doesn't call GetPropInfo then nothing else will.
Considering your updated code, I ran it under the debugger. When the button is clicked, TObjectDispatch.GetPropInfo is called twice. The first time it is called as a result of the call to inherited GetPropInfo() in TExtDispatch.GetPropInfo. The second time it is called you can inspect ClassName to find out what class Self is. When you do that you will find that ClassName evaluates to 'TObjectDispatch'. In which case, item 2 from my list is the explanation.
I don't really understand what you are trying to do here. However, I suspect that your problem stems from the way GetItem is implemented. I suspect it should be like this:
function TMyDispatch.GetItem: IDispatch;
begin
Result := TMyDispatchItem.Create;
end;
There should have been alarm bells going off when you assigned the return value of a TInterfacedObject constructor to an object reference. That's always an error. You must assign that to an interface reference.
I expect that what happens is that the dispatch code will use an IDispatch if it encounters one, but if it finds an instance of a class instead it creates a new IDispatch to do the work. And that's the third instance of TObjectDispatch.

How to trigger the event together on each two deferent class

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.

Resources