Delphi using pointers to methods properly - delphi

If I have a Pointer focused to a FormActivate(Sender: TObject); -
MyPtr := #FormActivate; - how do I run FormActivate with a sender of my choice.
I do not understand how to add in the sender.

You should not use a Pointer in this case. I don't think that you can even do it as you need the form instance as well as the method. The FormActivate is a plugged into the OnActivate event handler, defined as TNotifyEvent:
TNotifyEvent = procedure(Sender: TObject) of object;
NB: The "of object" which the compiler interprets to require an instance (the mechnanism hidden from us as it should be).
The correct was to do what you want (not sure that you should be doing it, but hey..) is to replace your Pointer with a MyForm :TMyForm (what ever yours is) and then call:
MyForm.FormActivate(mySender);
Alternatively, you can replace Pointer with a MyEvent :TNotifyEvent, thus:
MyEvent := MyForm.OnActivate;
then call
MyEvent(mySender);
Replacing mySender with whatever you wish in both examples.
I hope that this clears things up for you.

Related

Access Violation when handling forms

I have procedure to show/hide one element on TForm like that:
procedure ShowHideControl(const ParentForm: TForm; const ControlName: String; ShowControl: Boolean);
var
i: Integer;
begin
for i := 0 to pred(ParentForm.ComponentCount) do
begin
if (ParentForm.Components[i].Name = ControlName) then
begin
if ShowControl then
TControl(ParentForm.Components[i]).Show
else
TControl(ParentForm.Components[i]).Hide;
Break;
end;
end;
end;
then I try to use it like:
procedure TForm1.Button6Click(Sender: TObject);
begin
ShowHideEveryControl(TForm(TForm1), 'Button4', True);
end;
Why do I get Access Violation on Button6 click?
For me everything is OK... Button4 exists as a child :)
This cast is wrong:
TForm(TForm1)
You are telling the compiler to ignore the fact that TForm1 is not a TForm instance, and asking it to pretend that it is. That is fine until you actually try to use it as an instance, and then the error occurs.
You need to pass a real instance to a TForm descendent. You can write it like this:
ShowHideEveryThing(Self, 'Button4', True);
Just in case you are not clear on this, the parameter of your procedure is of type TForm. That means you need to supply an instance of a class that either is, or derives from TForm. I repeat, you must supply an instance. But you supply TForm1 which is a class.
And then the next problem comes here:
if (ParentForm.Components[i].Name = FormName) then
begin
if ShowForm then
TForm(ParentForm.Components[i]).Show
else
TForm(ParentForm.Components[i]).Hide;
Break;
end;
Again you have used an erroneous cast. When the compiler tells you that it a particular object does not have a method, you must listen to it. It's no good telling the compiler to shut up and pretend that an object of one type is really an object of a different type. Your button is categorically not a form, so don't try to cast it to TForm.
It is very hard to know what you are actually trying to do here. When you write:
ShowHideEveryThing(Self, 'Button4', True);
It would seem to me to me more sensible to write:
Button4.Show;
It is not a good idea to refer to controls using their names represented as text. It is much safer and cleaner to refer to them using reference variables. That way you let the compiler do its job and check the type safety of your program.
The names used in your function are suspect:
procedure ShowHideEveryThing(const ParentForm: TForm; const FormName: String;
ShowForm: Boolean);
Let's look at them:
ShowHideEveryThing: but you claim that the function should show/hide one element. That does not tally with the use of everything.
FormName: you actually pass a component name, and then look for components owned by ParentForm that have that name. It seems that FormName is wrong.
ShowForm: again, do you want to control visibility of the form, or a single element?
Clearly you need to step back and be clear on the intent of this function.
As an aside, you should generally never need to write:
if b then
Control.Show
else
Control.Hide;
Instead you can write:
Control.Visible := b;
My number one piece of advice to you though is to stop casting until you understand it properly. Once you understand it properly, design your code if at all possible so that you don't need to cast. If you ever really do need to cast, make sure that your cast is valid, ideally by using a checked cast with the as operator. Or at least testing first with the is operator.
Your code shows all the hallmarks of a classic mistake. The compiler objects to the code that you write. You have learnt from somewhere that casting can be used to suppress these compiler errors and now you apply this technique widely as a means to make your program compile. The problem is that the compiler invariably knows what it is talking about. When it objects, listen to it. If ever you find yourself suppressing a compiler error with a cast, take a step back and think carefully about what you are doing. The compiler is your friend.

Custom Component TImage events causing error "Does not exist"

I have a custom component witht the following events
private
{ Private declarations }
...
fOnImageClick: TNotifyEvent;
fOnImageMouseUp: TMouseEvent;
fOnImageMouseDown: TMouseEvent;
fOnImageMouseMove: TMouseMoveEvent;
fOnImageMouseEnter: TNotifyEvent;
fOnImageMouseLeave: TNotifyEvent;
fOnImageSelect: TNotifyEvent;
fOnImageDblClick: TNotifyEvent;
protected
...
public
{ Public declarations }
...
published
...
property OnImageClick: TNotifyEvent read fOnImageClick write fOnImageClick;
property OnImageSelect: TNotifyEvent read fOnImageSelect write fOnImageSelect;
property OnImageDblClick: TNotifyEvent read fOnImageDblClick write fOnImageDblClick;
property OnImageMouseDown: TMouseEvent read fOnImageMouseDown write fOnImageMouseDown;
property OnImageMouseUp: TMouseEvent read fOnImageMouseUp write fOnImageMouseUp;
property OnImageMouseMove: TMouseMoveEvent read fOnImageMouseMove write fOnImageMouseMove;
property OnImageMouseLeave: TNotifyEvent read fOnImageMouseLeave write fOnImageMouseLeave;
property OnImageMouseEnter: TNotifyEvent read fOnImageMouseEnter write fOnImageMouseEnter;
end;
I assign them to a TImage whose parent is TPanel whos parent is TScrollBox
img:= TImage.Create(ThumbPnl);
img.Parent:= ThumbPnl;
img.Tag:= I;
img.Align:= alClient;
img.Stretch:= true;
img.OnClick:= fOnImageClick;
img.OnDblClick:= fOnImageDblClick;
img.OnMouseEnter:= fOnImageMouseEnter;
img.OnMouseLeave:= fOnImageMouseLeave;
img.OnMouseDown:= fOnImageMouseDown;
img.OnMouseUp:= fOnImageMouseUp;
img.OnMouseMove:= fOnImageMouseMove;
The component compiles and bulds just fine. The application with this component also compiles and runs jus fine. If I assign an OnClick event, it works. All other events, if I assign them and try to run the app, i get an error saying the event doesn't exist
Anyone know why that is?
You are making a fairly common mistake for new component creators. You are breaking this rule:
Component users write event handlers, component writers "fire" events
In your case what you are doing is assigning events when you as the component developer shouldn't be doing that. It's up to the component user to assign code to occur in the OnClick, OnDblClick, etc. events.
It's enough for you to declare the events as Published and as TNotifyEvent types.
property OnSomeEvent: TNotifyEvent read FOnSomeEvent write FOnSomeEvent;
That's all you need to do to create the events. Your job is to "fire" them; that is, make them happen.
That is done in your component at the appropriate moment. Usually, what you'll do is create a method DoXXXXX where XXXXX is the thing that is happening. So you'd create
procedure TMyImage.DoSomeEvent;
begin
if Assigned(FOnSomeEvent) then FOnSomeEvent(Self);
end;
Then, within your code, when the event should be fired, you simply call DoSomeEvent.
Now, if you want to enhance the functionality of the default events, then you need to override the method that fires the event.
I hate to do it, but my classic component TSmiley illustrates this simply and clearly:
http://tsmiley.svn.sourceforge.net/viewvc/tsmiley/
Take a look at that example and you should see how to create events.
What is an event
The event in Delphi is a method pointer. A method pointer is basically two pointers, one points to the method you assign to the event, and the other points to the live instance (object) you assign.
What I assume you're expecting
I think you expect the event in your inner object follows the events assigned to your outer object, and that will not happen automatically.
When you assign it, you're doing pointer assignments. So to take an example, your line
img.OnDblClick:= fOnImageDblClick;
Performs a pointer assignment with the value fOnImageDblClick have at the time. If it is nil, img.OnDblClick will be nil from now. If it points to Form1.MyComponentImageClick at the time, img.OnDblClick will point to the same method on the same object from now, but, if you later change where fOnImageDblClick points, the imgOnDblClick will not follow... it will remains pointing to the same old address. If you want it to change, you have to make that change by code also.
If you want that to happen, you can do that in a event setter in the outer class.
First, declare your events like this:
published
...
property OnImageDblClick: TNotifyEvent read fOnImageDblClick write SetOnImageDblClick;
...
procedure TMyClass.SetOnImageDblClick(Value: TNotifyEvent);
begin
FOnImageDblClick := Value;
//pass the new value to the inner object.
if Assigned(Img) then
Img.OnDblClick := Value;
end;
If your Img inner object exists all the time, you don't need the FOnImageDblClick variable, you can write also a getter for the property and take the value directly from the inner object, like this:
function TMyClass.GetOnImageDblClick: TNotifyEvent;
begin
Result := Img.OnDblClick;
end;

delphi reference event object by variable TObject

i have component with function getsomedata (key:string;listener:Tlistener)
which listener declared like event as follow :
Tlistener = procedure (name,age,sex:string) of object ;
but in my component listener manager when i add the new listener takes listener parameter as TObject class like .
ListenerManager.addListener(key:string;Listener:TObject);
when i complile the code i got error message
Not enough actual parameters
because Event Object (TListener) and ListenerManager Parameter (TObject).
sample of full function code .
procedure getsomedata (key:string;listener:Tlistener) ;
begin
ListenerManager.addListener(key,listener); //error Here >>> addListener input parameters (key:string;Listener:TObject);
end;
how can i resolve it ?
In this code:
procedure getsomedata (key:string;listener:Tlistener) ;
begin
ListenerManager.addListener(key,listener);
end;
You are attempting to pass a variable of type TListener to the second parameter of addListener. That parameter is typed as being TObject.
Now, TListener is typed as being
procedure(name,age,sex:string) of object;
A variable of procedural type cannot be passed to a parameter of type TObject.
It's rather difficult to know exactly what the correct code would look like because the question doesn't contain enough background information. Perhaps addListener should receive a TListener rather than a TObject. But that's just a guess. If you want more complete advice, then you will need to add sufficient detail to the question.
You state in a comment that:
I can not change the parameter type TObject to TListener.
In that case you are stuck. It's simply not possible to cast a TListener to a TObject. Now, you could implement a class that had a single field of type TListener, and pass that. But I doubt very much that's really the right solution.

Method pointer and regular procedure incompatible

I have an app, which has multiple forms. All these forms have a PopupMenu. I build the menu items programatically, all under a common root menu item. I want ALL the menu items to call the same procedure, and the menu item itself is basically acting as an argument....
I had this working when I just had one form doing this functionality. I now have multiple forms needing to do this. I am moving all my code to a common unit.
Example.
Form A has PopupMenu 1. When clicked, call code in Unit CommonUnit.
Form B has PopupMenu 2. When clicked, call code in unit CommonUnit.
When I need to call my popup from each form, I call my top level procedure (which is in unit CommonUnit), passing the name of the top menu item from each form to the top level procedure in the common unit.
I am adding items to my PopupMenu with with code.
M1 := TMenuItem.Create(TopMenuItem);
M1.Caption := FieldByName('NAME').AsString;
M1.Tag := FieldByName('ID').AsInteger;
M1.OnClick := BrowseCategories1Click;
TopMenuItem.Add(M1);
I am getting an error message when I compile. Specifically, the OnClick line is complaining about
Incompatible types: 'method pointer and regular procedure'.
I have defined BrowseCategories1Click exactly like it was before when I was doing this on a single form. The only difference is that it is now defined in a common unit, rather than as part of a form.
It is defined as
procedure BrowseCategories1Click(Sender: TObject);
begin
//
end;
What is the easiest way to get around this?
Thanks
GS
A little background...
Delphi has 3 procedural types:
Standalone or unit-scoped function/procedure pointers declared like so:
var Func: function(arg1:string):string;
var Proc: procedure(arg1:string);
Method pointers declared like so:
var Func: function(arg1:string):string of object;
var Proc: procedure(arg1:string) of object;
And, since Delphi 2009, anonymous(see below) function/method pointers declared like so:
var Func: reference to function(arg1:string):string;
var Proc: reference to procedure(arg1:string);
Standalone pointers and method pointers are not interchangeable. The reason for this is the implicit Self parameter that is accessible in methods. Delphi's event model relies on method pointers, which is why you can't assign a standalone function to an object's event property.
So your event handlers will have to be defined as part of some class definition, any class definition to appease the compiler.
As TOndrej suggested you can hack around the compiler but if these event handlers are in the same unit then they should already be related anyway so you may as well go ahead and wrap them into a class.
One additional suggestion I have not seen yet is to backtrack a little. Let each form implement its own event handler but have that handler delegate responsibility to a function declared in your new unit.
TForm1.BrowseCategoriesClick(Sender:TObject)
begin
BrowseCategories;
end;
TForm2.BrowseCategoriesClick(Sender:TObject)
begin
BrowseCategories;
end;
unit CommonUnit
interface
procedure BrowseCategories;
begin
//
end;
This has the added benefit of separating the response to the user's action from the control that triggered the action. You could easily have the event handlers for a toolbar button and a popup menu item delegate to the same function.
Which direction you choose is ultimately up to you but I'd caution you to focus on which option will make maintainability easier in the future rather than which is the most expedient in the present.
Anonymous methods
Anonymous methods are a different beast all together. An anonymous method pointer can point to a standalone function, a method or a unnamed function declared inline. This last function type is where they get the name anonymous from. Anonymous functions/methods have the unique ability to capture variables declared outside of their scope
function DoFunc(Func:TFunc<string>):string
begin
Result := Func('Foo');
end;
// elsewhere
procedure CallDoFunc;
var
MyString: string;
begin
MyString := 'Bar';
DoFunc(function(Arg1:string):string
begin
Result := Arg1 + MyString;
end);
end;
This makes them the most flexible of the procedural pointer types but they also have potentially more overhead. Variable capture consumes additional resources as does inline declarations. The compiler uses a hidden reference counted interface for inline declarations which adds some minor overhead.
You can wrap your procedures into a class. This class might look like this in a separate unit:
unit CommonUnit;
interface
uses
Dialogs;
type
TMenuActions = class
public
class procedure BrowseCategoriesClick(Sender: TObject);
end;
implementation
{ TMenuActions }
class procedure TMenuActions.BrowseCategoriesClick(Sender: TObject);
begin
ShowMessage('BrowseCategoriesClick');
end;
end.
And to assign the action to a menu item in a different unit is enough to use this:
uses
CommonUnit;
procedure TForm1.FormCreate(Sender: TObject);
begin
PopupMenuItem1.OnClick := TMenuActions.BrowseCategoriesClick;
end;
Update:
Updated to use class procedures (instead of object methods) by David's suggestion. For those who want to use the object methods with the need of object instance, follow this version of the post.
This is the difference between a "procedure" and a "procedure of object"
The OnClick is defined as a TNotifyEvent:
type TNotifyEvent = procedure(Sender: TObject) of object;
You cannot assign a procedure to the OnClick as it is the wrong type. It needs to be a procedure of object.
You could choose one of these:
Derive your forms from a common ancestor and declare the method in it so it's available to descendants
Use a global instance of a class (e.g. data module) shared by all forms
Use a procedure as a fake method like this:
procedure MyClick(Self, Sender: TObject);
begin
//...
end;
var
M: TMethod;
begin
M.Data := nil;
M.Code := #MyClick;
MyMenuItem.OnClick := TNotifyEvent(M);
end;
One solution is to place the OnClick method into a TDatamodule.

Comparing a pointer to function's value in Delphi

How can I compare the value of a variable that contains a pointer to a function with a function address?
I'm maintaining some code, and it is failing in Delphi 2007. The declaration is:
var
EditorFrameWindow: Function: HWnd Of Object = Nil;
In a form activation, I've got:
procedure TEditForm.FormActivate(Sender: TObject);
begin
EditorFrameWindow := GetFrameWindow;
end;
And in the form deactivation I've got:
procedure TEditForm.FormDeactivate(Sender: TObject);
begin
if EditorFrameWindow = GetFrameWindow then
EditorFrameWindow := nil;
end;
So what is happening is that the form is being deactivated twice, and it is failing as nothing else got activated. The FormDeactivate is called, it matches, and the EditorFrameWindow global is set to (nil,nil) (according to the debugger). Then it is being called again, and the function stored in the variable is called, but of course there isn't one stored so it jumps through nil and creates an exception.
What should I do to stop this happening? (The framework has been changed to a tabbed system, so the operation probably changed.)
Would
procedure TEditForm.FormDeactivate(Sender: TObject);
begin
if Assigned(EditorFrameWindow) and (EditorFrameWindow = GetFrameWindow) then
EditorFrameWindow := nil;
end;
work per chance?
Edit:
You don't compare function addresses, you compare the results of those functions. So even though the fixed code above can no longer cause an exception it may still not do what you want it to. Another function that returns the same result would also reset the event handler.
To really check whether the variable is set to a specific event handler you will need to compare both elements in the TMethod record. Something like:
procedure TEditForm.FormDeactivate(Sender: TObject);
begin
if (TMethod(EditorFrameWindow).Code = #TForm1.GetFrameWindow)
and (TMethod(EditorFrameWindow).Data = Self)
then
EditorFrameWindow := nil;
end;
There are two ways you might want to compare method pointers. Method pointers consist of two pointers, a code pointer and an object pointer. Delphi's native way of comparing method pointers compares only the code pointers, and it looks like this:
if #EditorWindowMethod = #TEditForm.GetFrameWindow then
EditorWindowMethod := nil;
It checks whether the code pointer in the EditorWindowMethod variable matches the starting address of the GetFrameWindow method in TEditForm. It does not check whether the object reference in EditorWindowMethod is the same as Self. If you want to make the the object references are the same, too, then you need to break apart the method pointer into its constituent parts with the TMethod record, which Mghie's answer demonstrates. (And you probably do want to compare the object references since it sounds like you have multiple edit forms. They all have the same GetFrameWindow code pointer, but they have different object references.)
The reason for the # in the code is to tell the compiler that you want to refer to the method pointers. Without it, the compiler will try to call the method pointers, and that's what was getting you into trouble. The first time the window was deactivated, you called EditorWindowMethod and compared the resulting window handle with the return value from calling GetFrameWindow. They matched, of course, so you unassigned EditorWindowMethod. The next time the form was deactivated, you tried to call EditorWindowMethod again, but it was a null pointer.
You should consider getting rid of your dependence on activation and deactivation notification. Instead, simply check whether the form is active inside GetFrameWindow.

Resources