How do I attach an OnHelp event handler to the Application object? - delphi

In Embarcadero Delphi XE7, I use a component which has a help-button.
In the component (which shows a message dialog), I specify a help context number. If the user clicks on the button, the help should show, but I get an error instead:
Project ... raised exception class $C00000FD with message 'stack overflow at 0x006f089e'.
The command executed when the user clicks on the button is:
Application.HelpContext(HelpContextNumber);
On Launch HTML Help as Separate Process, I read that I should attach an OnHelp event handler to the Application object.
I saved the Help unit but how do I attach it?
Application.OnHelp := ....?

The TApplication.OnHelp event is declared as a THelpEvent:
THelpEvent = function(Command: Word; Data: THelpEventData; var CallHelp: Boolean): Boolean of object;
So, you would need to declare a method in your Form like this:
type
TMyForm = class(TForm)
...
private
function MyOnHelpHandler(Command: Word; Data: THelpEventData; var CallHelp: Boolean): Boolean;
...
end;
And then you can assign that handler to the TApplication.OnHelp event at runtime, eg:
procedure TMyForm.FormCreate(Sender: TObject);
begin
Application.OnHelp := MyOnHelpHandler;
end;
procedure TMyForm.FormDestroy(Sender: TObject);
begin
Application.OnHelp := nil;
end;
function TMyForm.MyOnHelpHandler(Command: Word; Data: THelpEventData; var CallHelp: Boolean): Boolean;
begin
Result := ...;
end;
Alternatively, you can drop a TApplicationEvents component onto your Form at design-time, and then create an OnHelp event handler for it using the Object Inspector.

Related

How to capture an event, when TTreeview structure changes?

I am making descendant of TTreeview and I want to implement an event in the case, when the TTreeview structure changes. For example, one TTreeNode is moved from one position to another, or it becomes the child of any other TTreenode.
When I call for example: Treeview1.Selected.MoveTo(ADropNode,naAddChildFirst);
no event fires.
How can I catch this?
Thanx.
I have found no message so far, what could respond to the change of structure.
The solution in this case was to make descendand of TTreeNode, where I overwrite dynamical procedure MoveTo and attach an event handler to it:
THierarchyTreeNode = class (TTreeNode)
private
FOnNodeMove:TTVNodeMoveEvent;
public
procedure MoveTo(Destination: TTreeNode; Mode: TNodeAttachMode); override;
property OnNodeMove:TTVNodeMoveEvent read FOnNodeMove write FOnNodeMove;
end;
...
procedure THierarchyTreeNode.MoveTo(Destination: TTreeNode; Mode: TNodeAttachMode);
begin
inherited;
if Assigned(FOnNodeMove) then FOnNodeMove(Treeview, Self);
end;
then I have done necessary changes in TTreeview descendand, where the procedure CreateNode is the key, where are THierarchyTreeNodes created instead of TTreenode. It is somewhat dirty, but... just an example:
TTreeViewHierarchy = class(TTreeView)
private
FOnNodeMove : TTVNodeMoveEvent;
protected
function CreateNode: TTreeNode; override;
procedure DoNodeMove(Sender: TObject; Node: TTreeNode);
published
property OnNodeMove: TTVNodeMoveEvent read FOnNodeMove write FOnNodeMove;
function TTreeViewHierarchy.CreateNode: TTreeNode;
var
LClass: TTreeNodeClass;
begin
LClass := THierarchyTreeNode;
if Assigned(OnCreateNodeClass) then
OnCreateNodeClass(Self, LClass);
Result := LClass.Create(Items);
(Result as THierarchyTreeNode).FOnNodeMove := DoNodeMove;
end;
procedure TTreeViewHierarchy.DoNodeMove(Sender: TObject; Node: TTreeNode);
begin
if Assigned(FOnNodeMove) then FOnNodeMove(Sender, Node);
end;
And it works...

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!

Delphi VCL: Form elements undeclared on custom procedure/function

I've got this:
procedure Welcome(user: string; accesslevel: integer);
begin
if accesslevel>= 10 then btCustomers.Text = 'Customer overview';
end;
Though, while the button exists on the form, btCustomers is declared an 'undeclared identifier'. What am I missing?
P.S. I am aware that this should be handled by the form OnCreate, but the Welcome procedure gets called from an external form.
You could to pass a reference to the form so that the button can in turn be referenced.
procedure Welcome(form: TMyForm; user: string; accesslevel: integer);
begin
if accesslevel>= 10 then form.btCustomers.Text = 'Customer overview';
end;
However, any time you have a global scope function that takes as its first parameter a reference to an object, you have a candidate for a method of that object. So, add a method to TMyForm.
procedure TMyForm.Welcome(user: string; accesslevel: integer);
begin
if accesslevel>= 10 then btCustomers.Text = 'Customer overview';
end;
And call it like this:
MyForm.Welcome(user, accesslevel);

Create event handlers manually in ZipForge

I used to drop TZipForge component on a form so I could use its event handlers. Now, I'm using several thread worker to extract some zip files, therefore I create an instance of the TZipForge class as a local variable. How do I make the event handlers without Event tab in Object Inspector?
To create a event hanlder manually yo must create a procedure with match with the declaration of the target event and then assign the Address of that procedure to the event of the class, for example if you want to create a event handle for the OnFileProgress event you must create a procedure like this inside of your class.
procedure FileProgress(Sender: TObject; FileName: string;
Progress: Double; Operation: TZFProcessOperation;
ProgressPhase: TZFProgressPhase; var Cancel: Boolean);
Check this sample
procedure TForm1.FileProgress(Sender: TObject; FileName: string;
Progress: Double; Operation: TZFProcessOperation;
ProgressPhase: TZFProgressPhase; var Cancel: Boolean);
begin
//do your stuff here
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Archiver : TZipForge;
begin
Archiver := TZipForge.Create(nil);
try
Archiver.OnFileProgress:=FileProgress;//<- Here the event handler is assigned
Archiver.FileName := 'compressedfile.zip';
Archiver.OpenArchive(fmOpenRead);
try
Archiver.BaseDir := 'C\Foo';
Archiver.ExtractFiles('*.*');
finally
Archiver.CloseArchive();
end;
finally
Archiver.Free;
end;
end;

Can I use a closure on an event handler (ie, TButton OnClick)

If I try to use a closure on an event handler the compiler complains with :
Incompatible types: "method pointer and regular procedure"
which I understand.. but is there a way to use a clouser on method pointers? and how to define if can?
eg :
Button1.Onclick = procedure( sender : tobject ) begin ... end;
Thanks!
#Button1.OnClick := pPointer(Cardinal(pPointer( procedure (sender: tObject)
begin
((sender as TButton).Owner as TForm).Caption := 'Freedom to anonymous methods!'
end )^ ) + $0C)^;
works in Delphi 2010
An excellent question.
As far as I know, it's not possible to do in current version of Delphi. This is much unfortunate since those anonymous procedures would be great to have for quickly setting up an object's event handlers, for example when setting up test fixtures in a xUnit kind of automatic testing framework.
There should be two ways for CodeGear to implement this feature:
1: Allow for creation of anonymous methods. Something like this:
Button1.OnClick := procedure( sender : tobject ) of object begin
...
end;
The problem here is what to put as the self pointer for the anonymous method. One might use the self pointer of the object from which the anonymous method was created, but then one can only create anonymous methods from an object context. A better idea might be to simply create a dummy object behind the scenes to contain the anonymous method.
2: Alternatively, one could allow Event types to accept both methods and procedures, as long as they share the defined signature. In that way you could create the event handler the way you want:
Button1.OnClick := procedure( sender : tobject ) begin
...
end;
In my eyes this is the best solution.
In previous Delphi versions you could use a regular procedure as event handler by adding the hidden self pointer to the parameters and hard typecast it:
procedure MyFakeMethod(_self: pointer; _Sender: TObject);
begin
// do not access _self here! It is not valid
...
end;
...
var
Meth: TMethod;
begin
Meth.Data := nil;
Meth.Code := #MyFakeMethod;
Button1.OnClick := TNotifyEvent(Meth);
end;
I am not sure the above really compiles but it should give you the general idea. I have done this previously and it worked for regular procedures. Since I don't know what code the compiler generates for closures, I cannot say whether this will work for them.
Its easy to extend the below to handle more form event types.
Usage
procedure TForm36.Button2Click(Sender: TObject);
var
Win: TForm;
begin
Win:= TForm.Create(Self);
Win.OnClick:= TEventComponent.NotifyEvent(Win, procedure begin ShowMessage('Hello'); Win.Free; end);
Win.Show;
end;
Code
unit AnonEvents;
interface
uses
SysUtils, Classes;
type
TEventComponent = class(TComponent)
protected
FAnon: TProc;
procedure Notify(Sender: TObject);
class function MakeComponent(const AOwner: TComponent; const AProc: TProc): TEventComponent;
public
class function NotifyEvent(const AOwner: TComponent; const AProc: TProc): TNotifyEvent;
end;
implementation
{ TEventComponent }
class function TEventComponent.MakeComponent(const AOwner: TComponent;
const AProc: TProc): TEventComponent;
begin
Result:= TEventComponent.Create(AOwner);
Result.FAnon:= AProc;
end;
procedure TEventComponent.Notify(Sender: TObject);
begin
FAnon();
end;
class function TEventComponent.NotifyEvent(const AOwner: TComponent;
const AProc: TProc): TNotifyEvent;
begin
Result:= MakeComponent(AOwner, AProc).Notify;
end;
end.

Resources