Is safe to set a private/protected/public method as event handler? - delphi

While assigning event handlers, I've noticed that the Object Inspector allows to choose only between methods who have not an explicit access modifier.
Taking the following class as example:
TMyForm = class(TForm)
MyButton: TButton;
procedure MyButtonClick(Sender: TObject);
private
procedure MyButtonPrivateClick(Sender: TObject);
protected
procedure MyButtonProtectedClick(Sender: TObject);
public
procedure MyButtonPublicClick(Sender: TObject);
end;
In the dropdown list, the Object Inspector shows only MyButtonClick:
Due to this reason, I'm wondering if it's safe to set a private/protected/public method to an event handler by code or if there could be some problems deriving from this practice.
MyButton.OnClick := MyButtonPrivateClick;
//...

This is perfectly safe without having to worry about any hidden issues. This is especially common when writing a custom component (as opposed to a form) which has a sub-component inside of it, for example. You can put it virtually anywhere you want, but I suggest to keep it under private.

Related

Code disappears on saving project [duplicate]

It's quite a contradictory habit of mine to press Ctrl+S permanently. The negative side is that delphi deletes empty functions/procedures on save.
Is there a way to prevent IDE from deleting functions/procedures with empty bodies on save?
Converted from the comment as per OP request. My comment is too tiny for an answer, so I'm going to add few details maybe already obvious to an OP.
This happens with event handlers only¹. Write them without delay or
comment them with todo²
¹ That is, event handlers are methods of design class and they are created, listed and deleted (if caught empty when saving or compiling) by the form designer (this include data module designer and any of other custom designers installed). Confer to delegates you probably familiar with from C# background. Any other methods are subject to "manual" management.
² TODO items (Ctrl+Shift+T in default keybinding) are definitely better than just blank comments:
procedure TForm1.MagicButton1Click(Sender: TObject);
begin
{ TODO -ctomorrow : I'm going to write the code, I promise! }
end;
Possible special case
TAction with AutoCheck set must (see the comment from Sir Rufo below for another possibility at run time) have its OnExecute assigned in order to be Enabled. In this case it is inevitably to have such blank event handlers within design class. Example:
procedure TMonitor.AutoCheckActionExecute(Sender: TObject);
begin
// dummy stub
{ DONE -crefactor : merge with other stub(s) }
end;
Just add an empty comment like //
begin
//
end;
an other way would by moving the declaration to the published part
type
TForm5 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject); // will be removed if empty
private
{ Private-Deklarationen }
public
published
procedure Button2Click(Sender: TObject); // will not be removed if empty
{ Public-Deklarationen }
end;
Is there a way to prevent IDE from deleting functions/
procedures with empty bodies on save?
There is no option in the IDE to disable this behaviour.

How to execute an ActionList item from a TListViewItem

I'm trying to execute an action (TakePhotoFromCameraAction) in a TActionList, when a TListViewItem is selected.
Neither TlistView nor TListViewItem have an Action property, so I've tried calling ActionList[0].Execute in the event, but nothing happens.
Any ideas?
Further:
The code is very simple, as it was just a test for this problem. I was focussing on the ActionList as that was what I will use (when I sort it out).
Button1 doesn't work (it always fails, even when button 2 doesn't), whereas the (new) Button2 does work OK.
type
TForm1 = class(TForm)
ActionList1: TActionList;
Memo1: TMemo;
TakePhotoFromCameraAction1: TTakePhotoFromCameraAction;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.Button1Click(Sender: TObject);
begin
ActionList1[0].Execute;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
if TakePhotoFromCameraAction1.Execute
then
Memo1.Lines.add('Photo OK')
else
Memo1.Lines.add('Photo Fail');
end;
You can use good old tag property of TListViewItem to store pointer to TAction you want to use with this item. Of course, you can't set it in object inspector, but can do it programmaticaly in TForm.onCreate event or some other convenient place. It has type NativeInt which has the same size as pointer be it 32-bit or 64-bit architecture, so it should work properly.
Something like this:
//in formCreate or other place to initialize actions:
TakePhotoItem.Tag:=NativeInt(TakePhotoFromCameraAction);
SavePhotoItem.Tag:=NativeInt(SavePhotoAction);
//...
//onitemchange event handler
if AItem.Tag<>0 then
TAction(AItem.Tag).Execute;
Maybe it's better to introduce your own descendant of TListViewItem which has Action property, that way you'll have to populate your listview in code only, adding not basic TListViewItem, but TActionListViewItem (name of your class), that has more work to do but will yield more understandable code.
There is no difference (except being ugly) to call ActionList1[0].Execute; versus Action1.Execute;.
You didn't show the the .fmx file so I can't know what linkage you may have setup between the components, but, it seems you haven't assigned anything to the actions OnExecute event, and therefore do not get the expected response to the Execute call.
The FMX Version of the documentation is not very clear, but the VCL version is (IMO) better (In a brief test I don't see any difference in actual functionality):
From documentation :
Responds when a client control "fires".
Execute is called automatically when a client control "fires" (for
example, when the user clicks a button or selects a menu item). It
returns True if an event handler is found to handle the action, False
if there was no event handler or if the action was not enabled.
but you can ofcourse also call Execute directly as you tried. And further
Execute first ensures that the action is updated. Then, if the Enabled
property is True, it attempts to handle the action by generating an
OnExecute event on the action list that contains this action (if the
action belongs to an action list). If the action list's OnExecute
event handler does not handle the action, Execute generates an
OnActionExecute event on the application itself. If neither the action
list nor the application handles the action in response to these
events, Execute generates an OnExecute event on itself. If this action
has no OnExecute event handler, Execute instructs the application to
locate the current target control and call the ExecuteTarget method,
which is the mechanism by which predefined action classes perform
their function.
Note that you can handle the actions in TActionList.OnExecute or in the TAction.OnExecute

Using Child's events

I have a custom component (TScrollBox) that when dropped on a form, it will add a label inside the ScrollBox. How can I disable the ScrollBox's events (onClick, OnMouseDown, ect..) and instead enable the events for the child (Tlabel)
unit MyScrollBox;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls;
type
TMyScrollComponent = class(TScrollBox)
private
FLabel : TLabel;
procedure SetLabelText(AText : string);
function GetLabelText : string;
protected
constructor Create(AOwner : TComponent); override;
published
property LabelText : string read GetLabelText write SetLabelText;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyScrollComponent]);
end;
constructor TMyScrollComponent.Create(AOwner : TComponent);
begin
inherited;
FLabel := TLabel.Create(self);
FLabel.Parent := self;
FLabel.Caption := 'Hello From Scrollbox!';
end;
procedure TMyScrollComponent.SetLabelText(AText : string);
begin
FLabel.Caption := AText;
end;
function TMyScrollComponent.GetLabelText : string;
begin
result := FLabel.Caption;
end;
end.
The published events in TScrollBox cannot be suppressed in derived classes. So, treating your question literally, there is no way to achieve what you ask.
What you could do is derive from TScrollingWinControl. This is the ancestor to TScrollBox. It doesn't publish the events that you want to associate with the control contained in your scroll box.
Then you can surface events in your custom control that are connected to the control contained in your custom control.
Judging from your recent questions I cannot help in thinking that your approach is wrong. I feel that you should have a custom control that has built in scrolling capability.
The event handlers for TControls are declared as protected and dynamic. Redeclare them using the override directive in your derived class - see TScrollBox Members Protected Methods;
To override MouseDown, add the MouseDown method to the TDBCalendar class and many other pages.
But: If you want to implement your own new events you'd have to do something like this:
...
private
fNewEvent:TNotifyEvent;
procedure setNewEvent(notify:TNotifyEvent);
function getNewEvent:TNotifyEvent;
procedure DoOnNewEvent;
....
published
property OnNewEvent:TNotifyEvent read getNewEvent write setNewEvent;
i.e. - You need to implement a property of a method type, like TNotifyEvent which is built into Delphi. You can also create your own if you need to. If you want to see your event in the IDE like other Delphi components' events, you must declare it as published.
Then: In your new component implementation section do something like this:
procedure TMyclass.DoOnNewEvent;
begin
if assigned (fNewEvent) then
begin
....doStuff...
fNewEvent(self);
end;
end;
You call DoOnNewEvent when the event that you want to control 'happens' in your code, so that the function assigned to fNewEvent will get called at that point in your code. (This is commonly known as a callback - when something "happens" in module A it calls back into module B letting it know that it happened, etc.)
If you want to define new GUI behavior, you have to examine the controls you're interested in and understand how to capture their actual "physical" events - i.e. when did the scrollbar scroll, when was the mouse clicked, and when that happens you call your DoOnNewEvent method. (This generally involves inspecting Windows messages coming into your application, "message cracking",etc - these messages inform your application of what's happening in "the outside world".)
In your consumer class, for example your main form where you're putting your scroll box, once you successfully publish your new event, you will see your event in the IDE on your new component, and you assign it and define the behavior you want for it in your consumer class, just like any other event in the IDE.
Take a look at the VCL source code for a simple component to get a better idea of what it looks like.
But: That's only if you really need your own new published events because overriding the parent's events is not sufficient for your needs.

Prevent methods with empty bodies from deletion on save

It's quite a contradictory habit of mine to press Ctrl+S permanently. The negative side is that delphi deletes empty functions/procedures on save.
Is there a way to prevent IDE from deleting functions/procedures with empty bodies on save?
Converted from the comment as per OP request. My comment is too tiny for an answer, so I'm going to add few details maybe already obvious to an OP.
This happens with event handlers only¹. Write them without delay or
comment them with todo²
¹ That is, event handlers are methods of design class and they are created, listed and deleted (if caught empty when saving or compiling) by the form designer (this include data module designer and any of other custom designers installed). Confer to delegates you probably familiar with from C# background. Any other methods are subject to "manual" management.
² TODO items (Ctrl+Shift+T in default keybinding) are definitely better than just blank comments:
procedure TForm1.MagicButton1Click(Sender: TObject);
begin
{ TODO -ctomorrow : I'm going to write the code, I promise! }
end;
Possible special case
TAction with AutoCheck set must (see the comment from Sir Rufo below for another possibility at run time) have its OnExecute assigned in order to be Enabled. In this case it is inevitably to have such blank event handlers within design class. Example:
procedure TMonitor.AutoCheckActionExecute(Sender: TObject);
begin
// dummy stub
{ DONE -crefactor : merge with other stub(s) }
end;
Just add an empty comment like //
begin
//
end;
an other way would by moving the declaration to the published part
type
TForm5 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject); // will be removed if empty
private
{ Private-Deklarationen }
public
published
procedure Button2Click(Sender: TObject); // will not be removed if empty
{ Public-Deklarationen }
end;
Is there a way to prevent IDE from deleting functions/
procedures with empty bodies on save?
There is no option in the IDE to disable this behaviour.

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.

Resources