Delphi, GExpertsComments Off on Working on the GExperts Code Formatter again
Nov302019
The code formatter can now handle inline variable declarations. That was actually just a simple change (fixes bugs #157 and #158 (and the duplicate #165))
Now I’m trying to get it to format function declarations like this:
function RegisterClipboardFormatW(lpszFormat: PWideChar): UINT; stdcall;
external user32 Name 'RegisterClipboardFormatW';
or something as simple as this;
procedure bla;
overload;
forward;
Currently it doesn’t indent the additional lines. It works fine for method declarations though.
This turns out to be much more complicated than I thought. In my attempts to fix this, I have multiple times broken other test cases. Good thing there’s unit tests and source code management.
By pure chance I found a nasty side effect in the code.
Delphi, GExpertsComments Off on GExperts 1.3.15 installers were/are detected as malware
Nov282019
The installers for GExperts 1.3.15 were/are detected as malware by several virus scanners on Virus Total. I have since submitted them all as false positives to Kaspersky and some to Microsoft and after their latest signature updates their scanners are now fine. I tried the same with other virus scanner vendors, but their procedure was so complicated that I decided that it is not worth the effort. So if you are using BitDefender or McAfee, you might be out of luck. Try to submit your installer to them yourself if you like, just so you see how much time that takes. Now multiply that by 19 installers and > 10 virus scanners … I guess you will understand then why I don’t bother.
Well for a DLL yes as you would never get a situation where the Editor Popup Menu is called when you DLL is not in memory any more however this is not true for a BPL based plug-in so we need to reverse our hook.
… because a BPL based plugin can be loaded and unloaded at any time.
For which he then gives the following code:
Procedure TDDTWizard.UnhookEditorPopupMenu;
Var
EditorPopupMenu : TPopupActionBar;
Begin
EditorPopupMenu := FindEditorPopup;
If Assigned(EditorPopupMenu) And Assigned(EditorPopupMenu.OnPopup) Then
EditorPopupMenu.OnPopup := TNotifyEvent(FEditorPopupMethod);
End;
And here we have a problem: How do you know that you can safely remove your hook? Some other plugin might have changed it and you will never know. If you simply change the OnPopup event back to the value you saved when you installed it, you might disable another plugin which was installed/initialized after yours. Or even worse, what if the plugin whose event you saved also has been unloaded? What do you do then? Simply reinstalling its original event handler will potenially crash the IDE the next time the event is called.
I use this in my own plugins (Delphi IDE Explorer and parts of GExperts) and it works fine if both plugins adhere to this “standard”. Unfortunately I know of no other plugins who do. If you write Delphi IDE plugins, you might want to consider also trying to play nice with other plugins.
(Btw David, your blog still says “Copyright David Hoyle 2018” in the footer.)
Delphi, GExpertsComments Off on GExperts 1.3.15 experimental twm 2019-11-23 released
Nov232019
I just released GExperts 1.3.15 for all supported Delphi versions.
There have again been various bug fixes and the following notable changes:
There is now a start menu entry for a new stand alone version of the GExperts Code Formatter. Note that it not only has a GUI for selecting a single file to format and show the settings dialog, but can also be called with a list of files to be formatted. So, you could for example drag a group of files onto the executable to format them all. I plan to expand that command line interface in the future to allow formatting all files in a project or in a directory and its subdirectories.
Talking about the code formatter: Many more bugs have been fixed. A while ago I was actually thinking I got them all, but unfortunately that turned out to be a delusion. 😉
The GoTo Line Number dialog enhancer has been removed and replaced with a Go To expert with the same functionality but its own dialog, which has a setting to replace the Search -> Go to Line Number menu entry. If you have any suggestions on further improving that expert, please file a feature request.
Bugfix for the PE Information expert: It should now display all exported class names.
Many changes to the Uses Clause Manager expert (most of them contributed by Peter Panettone)
Bugfixes for the Identifier tab. It wrongly found some identifiers that just weren’t exported from that unit
It now highlights any units it will add.
It can now parse map files generated by the Win64 compiler, not just those from the Win32 compiler
It now shows an error message if it cannot use the map file and resorts to the dpr file.
The filter on the Identfier tab now has two modes:
Match anywhere
Match at start
There is now a status bar that shows the full file name of the currently selected unit. A popup menu allows to
Copy that file name to the clipboard
Copy that file to the clipboard (like pressing Ctrl+C on the file name in the Windows File Explorer)
Open the file’s location in the Windows File Explorer
The library path for Delphi XE and later was wrong, so the VCL/RTL tab was always empty.
There was a bug with the Clipboard History expert: It still hooked the clipboard even if it was disabled. In addition it could kill the IDE when it added new entries to the (possibly invisible) list view. Under some strange conditions setting the text of a memo apparently can fail with an EInvalidOperation exception. This exception was not caught and since it happened deep inside a chain of event handlers, it caused an Access Violation somewhere which then silently killed the IDE. That one was quite difficult to track down.
Improvement to the Backup Project expert: A new dialog shows any files it cannot find and it only shows each file once, not every time it does not find it. This was particularly annoying with include files.
And last, but not least: The GExperts version for Delphi 10.3 is now compiled with the latest and greatest Delphi 10.3 update 3 release. Let’s hope this fixed some bugs and hasn’t introduced any new ones. (Unfortunately RSP-25645 “Creating sub components with IOTAFormEditor.CreateComponent raises access violation” still hasn’t been fixed, so replacing e.g. TTable with TSqlTable still does not work.)
dzBdsLauncher tries to solve the problem of accidentally opening a Delphi project with the wrong Delphi version. The latest version 1.0.2 now also detects .DPROJ file from Delphi 10.3.3. See the dzBdsLauncher page for details.
Before you ask: No, there is no new GExperts release yet for Delphi 10.3.3. I haven’t even downloaded it yet. I might get to it this weekend.
In the meantime you can get the sources and compile your own DLL with the new Delphi version. I don’t expect any problems (but I have been wrong before).
Delphi, GExpertsComments Off on If you compiled your own GExperts clear the Uses Clause Manager cache
Nov192019
This is only relevant, if you have recently compiled your own GExperts DLL and you use the Uses Clause Manager’s Identifier tab:
There were several bugs in the unit parsing code that have been fixed. But you won’t see the effect, because the buggy results from before that have been cached. In order to get the benefit of these bugfixes, you must clear the Uses Clause Manager’s cache.
To do that,
Go to GExperts -> Configuration
On the Experts tab, enter “uses” and press Alt+C
Press the “Clear Cache” button
Yes, I know, that I should make a new release soon, but I keep finding old and sometimes new bugs and then there are people who submit patches. It’s difficult to determine when to make a release. But hey, you have got the source code and the compiler, so why not compile your own DLL?
DelphiComments Off on Creating an array of controls in Delphi
Nov172019
One frequently asked question that still gets asked today goes like this: “How do I create an array of [component] and fill it with existing [component] instances from the form?” Where [component] usually is TLabel, TCheckbox or TEdit.
I’m going to outline some solutions here.
Let’s start by defining some parameters:
We have got a (VCL) form
On that form there are several controls of the same type. Let’s make them CheckBoxes.
We want to do something with all these controls
In order to make this easier, we want to create an array that contains all these controls
And for just 6 CheckBoxes we probably would. But lets assume there are 30 and we don’t only want to disable them all but also sometimes enable or check or uncheckm them all. That starts to get a bit tedious, so wouldn’t it be great if we could just create an array of TCheckbox and fill it with those pesky things? We could then just write:
for chk in CheckBoxArr do begin
chk.Enabled := false;
end;
But how do we get this array? If you ever programmed in Visual Basic, you know the concept of control arrays, which you get by “simply” setting an Index property of the controls. They would then all have the same name and you could access them individually by TheCheckBox[i]. While that looks nice at first glance, just consider setting these indexes correctly for 30 CheckBoxes using the (crappy) property editor that VB had. I hated it. But I deviate …
We want a function which we can pass a TForm parameter that returns a TCheckBoxArray:
type
TCheckBoxArray = array of TCheckBox;
function AllCheckboxes(_frm: TForm): TCheckBoxArray;
If you look closely at the form declaration above you will notice that all CheckBoxes follow a common naming pattern: They all start with ‘chk_’ followed by a number. So we could use FindComponent to get them:
function GetAllCheckboxes(_frm: TForm): TCheckBoxArray;
var
i: Integer;
cmp: TComponent;
begin
SetLength(Result, _frm.ComponentCount);
i := 1;
repeat
cmp := _frm.FindComponent('chk_' + IntToStr(i));
if cmp <> nil then begin
Result[i - 1] := cmp as TCheckBox;
Inc(i);
end;
until cmp = nil;
SetLength(Result, i - 1);
end;
But if you think about it this is quite a bit of work, because you must make sure that your CheckBoxes all conform to that name pattern and for the above code to work, there must not be any gaps in the numbering. We usually want those CheckBoxes to have meaningful names rather than numbers, so it’s chk_Green, chk_Blue, chk_LightOn rather than chk_1, chk_2 etc. Also, what if you accidentally add another control that conforms to the name pattern but is not a CheckBox?
So FindComponent is out. What alternatives are there?
There is the Component array of the form, that contains every component on the form whose Owner is the form (this is usually the case for all components on a form when the form has been created using the form designer). So we enumerate all components and check whether they are CheckBoxes:
function GetAllCheckboxes(_frm: TForm): TCheckBoxArray;
var
i: Integer;
cmp: TComponent;
cnt: Integer;
begin
SetLength(Result, _frm.ComponentCount);
cnt := 0;
for i := 0 to _frm.ComponentCount - 1 do begin
cmp := _frm.Components[i];
if cmp is TCheckBox then begin
Result[cnt] := TCheckBox(cmp);
Inc(cnt);
end;
end;
SetLength(Result, cnt);
end;
Much better. We don’t need any naming pattern and we only get CheckBoxes.
I mentioned above, that the form’s Components[] property contains all components placed on the form using the form designer. But components can also be created in code and there you can pass any other control as the Owner:
The function will not find such a CheckBox because by passing grp_1 to its constructor we add it the Components[] property of the GroupBox rather than the form. That’s perfectly OK. We also clear the name, which would prevent it from being found by FindComponent too. But if you write code like this, you probably know what you are doing and won’t have read this article up to here.
I could stop now, but there is another use case: What if we want to get an array of only those CheckBoxes in one of the GroupBoxes? We could pass that GroupBox as a second parameter to the function and check that the Parent property of the CheckBoxes matches the GroupBox. But there is a simpler way: Each WinControl (that is: Control that itself can contain other controls, which includes GroupBoxes, Panels etc. and of course Forms or Frames) has got a Controls[] property that contains those controls. So we make a simple change to the function:
function GetAllCheckboxes(_Parent: TWinControl): TCheckBoxArray;
var
i: Integer;
ctrl: TControl;
cnt: Integer;
begin
SetLength(Result, _Parent.ControlCount);
cnt := 0;
for i := 0 to _Parent.ControlCount - 1 do begin
ctrl := _Parent.Controls[i];
if ctrl is TCheckBox then begin
Result[cnt] := TCheckBox(ctrl);
Inc(cnt);
end;
end;
SetLength(Result, cnt);
end;
// [...]
begin
CheckBoxes := GetAllCheckboxes(grp_1);
This will return all CheckBoxes placed on the GroupBox grp_1.
But note that …
CheckBoxes := GetAllCheckboxes(Form1);
… will now return an empty array! None of the CheckBoxes have been placed on the form itself, so they will not be in the form’s Controls[] property.
Delphi, dzPrepBuildComments Off on dzPrepBuild 1.3.4 for Delphi bugfix release
Nov022019
I found a bug in dzPrepBuild that required a new release: If a .dpr file is passed as parameter the ProjectName placeholder would also have that extension.
The reason for this release was that I wanted to allow automatically updating the ProductVersion when the FileVersion changes. In order to do that, the tool now supports two additional placeholders that an be used in any version string: {MajorVer} and {MinorVer} which, as you might have guessed, resolve to the major and minor version number.
e.g.
[Version Info Keys]
CompanyName=www.dummzeuch.de
FileDescription=This is a Testproject for dzPrepBuild
FileVersion=1.0.0.2
InternalName={ProjectName}
LegalCopyright=Copyright 2002-{ThisYear} by Thomas Mueller
LegalTrademarks=
OriginalFilename={ProjectName}.exe
ProductName=Testproduct
ProductVersion={MajorVer}.{MinorVer}
BuildDateTime={today}
I also added {ProjectName} which resolves into the name of the Delphi project and is used in the example above for the InternalName and OriginalFilename entries.
While working on this enhancement unfortunately I also found a major bug that was introduced by a patch I accepted nearly years ago. It added support for private and special build flags but used the wrong names for these entries in the .dof, .bdsproj and .dproj files which resulted in –ReadDof, -ReadBdsProj and -ReadDproj failing. This is now also fixed. I never noticed this problem because I don’t use the built in Delphi version information any more. All my projects have an external [projectname]_version.ini file for maintaining the version information.
And since I was at it, I moved the project from SourceForge to OSDN. The new version can be downloaded from there.