Prevent methods with empty bodies from deletion on save - delphi

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.

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.

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

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.

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

Display a warning when dropping a component on a form at design time

I'm tidying up components used in a large legacy project, I've eliminated about 90 of 220 custom components, replacing them with standard Delphi controls. Some of the remaining components require a significant amount work to remove which I don't have available. I would like to prevent anyone from making additional use of some of these components and was wondering if there was a way of showing a message if the component is dropped on the form at design time - something like "Don't use this control, use x or y instead".
Another possibility would to hide the control on the component pallet (but still have the control correctly render on the form at design time).
There is protected dynamic method TComponent.PaletteCreated, which is called only in one case: when we add this component to a form from component palette.
Responds when the component is created from the component palette.
PaletteCreated is called automatically at design time when the component has just been created from the component palette. Component writers can override this method to perform adjustments that are required only when the component is created from the component palette.
As implemented in TComponent, PaletteCreated does nothing.
You can override this method to show warning, so it will alert the user just one time, when he tries to put it to form.
UPDATE
I couldn't make this procedure work in Delphi 7, XE2 and Delphi 10 Seattle (trial version), so it seems that call to PaletteCreated from IDE is not implemented.
I sent report to QC:http://qc.embarcadero.com/wc/qcmain.aspx?d=135152
maybe developers will make it work some day.
UPDATE 2
There are some funny workarounds, I've tried them all this time, works normally. Suppose that TOldBadButton is one of components that shouldn't be used. We override 'Loaded' procedure and WMPaint message handler:
TOldBadButton=class(TButton)
private
fNoNeedToShowWarning: Boolean; //false when created
//some other stuff
protected
procedure Loaded; override;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
//some other stuff
end;
and implementation:
procedure TBadOldButton.Loaded;
begin
inherited;
fNoNeedToShowWarning:=true;
end;
procedure TOldBadButton.WMPaint(var Message: TWMPAINT);
begin
inherited;
if (csDesigning in ComponentState) and not fNoNeedToShowWarning then begin
Application.MessageBox('Please, don''t use this component','OldBadButton');
fNoNeedToShowWarning:=true;
end;
end;
The problem is, this works only for visual components. If you have custom dialogs, imagelists etc, they never get WMPaint message. In that case we can add another property, so when it is shown in object inspector, it calls getter and here we display warning. Something like this:
TStupidOpenDialog = class(TOpenDialog)
private
fNoNeedToShowWarning: boolean;
function GetAawPlease: string;
procedure SetAawPlease(value: string);
//some other stuff
protected
procedure Loaded; override;
//some other stuff
published
//with name like this, probably will be on top in property list
property Aaw_please: string read GetAawPlease write SetAawPlease;
end;
implementation:
procedure TStupidOpenDialog.Loaded;
begin
inherited;
fNoNeedToShowWarning:=true; //won't show warning when loading form
end;
procedure TStupidOpenDialog.SetAawPlease(value: string);
begin
//nothing, we need this empty setter, otherwise property won't appear on object
//inspector
end;
function TStupidOpenDialog.GetAawPlease: string;
begin
Result:='Don''t use this component!';
if (csDesigning in ComponentState) and not fNoNeedToShowWarning then begin
Application.MessageBox('Please, don''t use this component','StupidOpenDialog');
fNoNeedToShowWarning:=true;
end;
end;
Older versions of Delphi always scroll object inspector to the top when new component is added from palette, so our Aaw_please property will surely work. Newer versions tend to start with some chosen place in property list, but non-visual components usually have quite a few properties, so it shouldn't be a problem.
To determine when the component is first created (dropped on the form)?
Override "CreateWnd" and use the following if statement in it:
if (csDesigning in ComponentState) and not (csLoading in ComponentState) then
// We have first create
More detail here >>
Link

problem subclassing TTreeNode in delphi

i'm writing a delphi 2009 app that uses a TTreeView on a docking panel.
i saw i could make big simplifications in my app if i subclassed the TTreeNode. the tree view it's on is placed on a docking panel.
TInfoTreeNode=class(TTreeNode)
private
// remember some stuff
public
end;
procedure TfraInfoTree.tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
begin
NodeClass:=TInfoTreeNode;
end;
i think i've hit a wall though...each "TInfoTreeNode" instance needs to remember things about itself. since the handles are freed when the panel containing the TTreeView auto-hides, the classes are destroyed.
that's a problem because then everything the classes knew is then forgotten.
is there a way around this (other than reloading every TInfoTreeNode from the database again)?
thank you!
IIRC, the Tag Data property on each TTreeNode instance is preserved through the handle rebuild.
You could either use this as an index into a List containing objects with additional information, or use type-casting to store an object reference and access the objects directly.
the problem is caused by the wrong implementation of your custom TreeNode - it doesn't preserve its information when the TreeView's parent window gets recreated after it has been hodden. As a solution, create a TTreeView descendant and override its DestroyWnd method, to preserve your custom values. For example, take a look at how the TCustomTreeView.DestroyWnd method is implemented.
Having looked at TCustomTreeView.DestroyWnd like Joe Meyer proposes, I would suggest you revert to using the TreeNode.Data property, and store a reference to objects of a new class inheriting from TObject directly. The OnDeletion event of the TreeView offers a good spot to put the destruction code: "TMyObject(Node.Data).Free;"
Usage is pretty similar except you'll need to use "TMyObject(Node.Data)" instead of "TMyNode(Node)". A warning though: experience has taught me to pay close attention not to forget the ".Data" part, since "TMyObject(Node)" will not throw a compile error and raise access violations at run-time.
thank you all for your replies!
i have for 10 years been using the tree view using TTreeNode's data property. i wanted to be free of:
setting the Data property
creating/destroying the "data" object in a manner so there are no memory leaks
i have used the Data property for an ID number in the past as well.
today, my nodes have GUIDs to find their data in the database so they don't "fit" into the Data property anymore.
using a descendant of TTreeNode seems to have addressed my wishes nicely but in order to make that work nicely i had to do a few things:
handle TTreeView.OnCreateNodeClass event
handle TTreeView.OnDeletion event to retrieve latest data from the nodes before they are destroyed
handle TTreeView.OnAddition event to: 1) maintain a simple list of the nodes 2) set the node's Data property so we can use it to find the place in the list allocated for storing it's data.
here's the code:
TInfoTreeNodeMemory=record
...
end;
TInfoTreeNode=class(TTreeNode)
private
m_rInfoTreeNodeMemory:TInfoTreeNodeMemory;
public
property InfoTreeNodeMemory:TInfoTreeNodeMemory read m_rInfoTreeNodeMemory write m_rInfoTreeNodeMemory;
end;
TInfoTreeNodeMemoryItemList=class
private
m_List:TList<TInfoTreeNodeMemory>;
public
constructor Create;
destructor Destroy; override;
procedure HandleOnDeletion(Node: TInfoTreeNode);
procedure HandleOnAddition(Node: TInfoTreeNode);
end;
TfraInfoTree = class(TFrame)
tvInfo: TTreeView;
procedure tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
procedure tvInfoDeletion(Sender: TObject; Node: TTreeNode);
procedure tvInfoAddition(Sender: TObject; Node: TTreeNode);
private
m_NodeMemory:TInfoTreeNodeMemoryItemList;
...
procedure TfraInfoTree.tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
begin
// THIS IS VITAL!
NodeClass:=TInfoTreeNode;
end;
procedure TfraInfoTree.tvInfoDeletion(Sender: TObject; Node: TTreeNode);
begin
m_NodeMemory.HandleOnDeletion(TInfoTreeNode(Node));
end;
procedure TfraInfoTree.tvInfoAddition(Sender: TObject; Node: TTreeNode);
begin
m_NodeMemory.HandleOnAddition(TInfoTreeNode(Node));
end;
g_icTreeNodeNotInList=MAXINT;
procedure TInfoTreeNodeMemoryItemList.HandleOnDeletion(Node: TInfoTreeNode);
var
iPosition:integer;
begin
iPosition:=integer(Node.Data);
if iPosition=g_icTreeNodeNotInList then
raise Exception.Create('Node memory not found!')
else
// we recognize this node; store his data so we can give it back to him later
m_List[iPosition]:=Node.InfoTreeNodeMemory;
end;
procedure TInfoTreeNodeMemoryItemList.HandleOnAddition(Node: TInfoTreeNode);
var
iPosition:integer;
begin
// "coat check" for getting back node data later
iPosition:=integer(Node.Data);
if iPosition=g_icTreeNodeNotInList then
begin
// Node.Data = index of it's data
// can't set Node.Data in OnDeletion so we must assign it in OnAddition instead
Node.Data:=pointer(m_List.Count);
// this data may very well be blank; it mostly occupies space; we harvest the real data in OnDeletion
m_List.Add(Node.InfoTreeNodeMemory);
end
else
// we recognize this node; give him his data back
Node.InfoTreeNodeMemory:=m_List[iPosition];
end;
very cool...it meets all my objectives!
to add a node to the tree, all i need to do is:
// g_icTreeNodeNotInList important so the "coat check" (TInfoTreeNodeMemoryItemList)
// can recognize this as something that's not in it's list yet.
MyInfoTreeNode:=TInfoTreeNode(tvInfo.Items.AddChildObject(nParent, sText, pointer(g_icTreeNodeNotInList))));

Resources