Compiling GExperts while the DLL is loaded in the IDE

 Delphi, GExperts  Comments Off on Compiling GExperts while the DLL is loaded in the IDE
Jan 292016
 

If you have tried to work on GExperts (or any other DLL-based Delphi IDE expert) you have probably encountered this annoyance: Of course you want GExperts to be active in your IDE so you can use its features. But if you then compile it, the output file already exists and is locked because it is opened by the IDE.

So, you either live without the features while working on it, or don’t compile it while the IDE is open. I for one find that really annoying.

But, there is a partial solution for this:

Windows allows you to rename/move DLLs that are locked, so it’s possible to replace the file. And to make it convenient, you can use a pre-build event. So, I changed the pre-build event to this:

call ..\..\buildtools\prebuild.cmd $(PROJECTPATH)
move /y $(OUTPUTPATH)  $(OUTPUTPATH)~

(The first is a call to my standard prebuild script which handles version information.)

This works, kind of. Since once the DLL has been moved, the destination file exists and cannot be overwritten, move will fail and return a result of 1, which causes the pre-build event to fail.

I could have unchecked the “Cancel build on error” option, but that is not a clean solution to the problem. So instead, I opted for calling a batch file that always returns 0:

@echo off
echo moving %1 so the DLL can be compiled even though it is currently loaded in the IDE
move %1 %1~
exit /b 0

and calling it instead:

call ..\..\buildtools\prebuild.cmd $(PROJECTPATH)
call ..\..\buildtools\movedll.cmd $(OUTPUTPATH)

Works nicely for me.

I have added that script to my build tools, so I can use it everywhere I need it.

And, of course, debugging the expert also works fine now, because when you start a second instance of the IDE it will load the freshly compiled DLL.

Backwards compatibility of uses lists

 Delphi, GExperts  Comments Off on Backwards compatibility of uses lists
Jan 282016
 

Sometimes the uses lists become an issue, when you want to keep your source code backwards compatible to older Delphi versions. This usually happens when some declaration was moved from one RTL/VCL unit to a different one. One example is TAction which has moved from the unit ActnList to the new unit Actions which was introduced sometime after Delphi 2007 and later called System.Actions.

This is a major annoyance because the Delphi IDE insists on adding System.Actions to the uses list of every form that uses TAction and descendants in any way, even though it is already there, but surrounded by an {$IFDEF}.

One workaround for this problem has been introduced with Delphi 3 (or even Delphi 2?) and is called Unit alias. Back then it was mostly a convenience issue because the units WinTypes and WinProcs were merged into the single unit Windows and the units DbiTypes, DbiProcs and DbiErrs were merged into the single unit BDE. By defining unit aliases for these, old code, that referenced e.g. WinTypes still compiled without having to change the uses list. We all have seen these entries which still automatically get added to every single new Delphi project even though they are no longer necessary.

Project-Options_Unit-Aliases

So, why not use the Uses alias list for our own purpose? Just add Actions=ActnList to the list and let the IDE add the unit Actions to the uses list whenever it likes to do that. Just make sure to remove the “System.” prefix from this entry if you want to be backwards compatible to Delphi 6 and older which did not support dotted unit names.

I have done just this with GExperts and it is really nice to no longer have to check whether Actions was added to the uses list again before committing any changes.

dzDeleteProp 1.0.0 released

 Delphi  Comments Off on dzDeleteProp 1.0.0 released
Jan 172016
 

dzDeleteProp is a small tool I just wrote, that deletes configurable properties from all Delphi .dfm files in a given directory and optionally its subdirectories.

dzDeleteProp

The reason for such a tool is backwards compatibility to older Delphi versions, e.g. Delphi 6 does not know about ExplicitWidth and ExplicitHeight, which Delphi 2007 and above insist on putting into the .dfm files no matter what.

The tool has its own page here.

Automatically scroll a memo line by line in Delphi

 Delphi, GExperts  Comments Off on Automatically scroll a memo line by line in Delphi
Jan 162016
 

AboutScrolling

Today vanity caught up with me and I decided to add myself to the list of “major contributors” shown on the GExperts About dialog. But this list has become rather long over the years (actually it is surprisingly short given the time GExperts has been in existence), so only the top few names are visible. Since the list is alphabetically sorted by last names, unfortunately my name, together with the ones of such notables as Stefan Hoffeister, Ray Lischner or Martin Waldenburg was only visible when scrolling down the list. And let’s be honest: Nobody scrolls down that list.

So, we’ll scroll the list for you, you lazy basterd™.

Basically, it’s quite simple: Use a timer to periodically send a WM_VSCROLL message to the memo with wParam = SB_LINEDOWN to scroll down one line:

procedure TfmAbout.tim_ScrollTimer(Sender: TObject);
begin
  inherited;
  SendMessage(mmoContributors.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
end;

But what do we do when we reach the end of the list? We could stop, we could revert the scrolling direction, or we jump to the top again. I decided to do the latter.

But how do you know when you reached the end? I thought that this can probably be determined from the SendMessage return value, but MSDN states only

Return value

If an application processes this message, it should return zero.

Which I thought odd, so I just tried it and guess what? you have reached the end, when the function returns 0.

procedure TfmAbout.tim_ScrollTimer(Sender: TObject);
var
  Res: Integer;
begin
  inherited;
  Res :=  SendMessage(mmoContributors.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
  if Res = 0 then begin
    // we have reached the end
    SendMessage(mmoContributors.Handle, WM_VSCROLL, SB_TOP, 0);
  end;
end;

Bugfix release experimental GExperts 1.38 2016-01-10

 Delphi, GExperts  Comments Off on Bugfix release experimental GExperts 1.38 2016-01-10
Jan 102016
 

There was a major bug in yesterday’s experimental GExperts release 1.38 2016-01-09.

As discovered and reported by +Karheinz Jansen the Delphi IDE could crash when closing and show cascaded access violations. This was caused by a bug in the handling of dockable forms, which has now been fixed.

And since I was at it, I also added the missing icon for the new bookmarks expert.

Bookmarks-Icon-Screenshot

Head over to the Experimental GExperts page to download it.

Dockable form handling within GExperts

 Delphi, GExperts  Comments Off on Dockable form handling within GExperts
Jan 102016
 

Because I messed up the dockable form handling for the new Bookmarks expert which resulted in spurious cascading access violations when closing the Delphi IDE, I had to revisit that code. And so I can look up the information I gathered fixing that problem, I’m putting it here for later reference.

Basically, you create a new dockable form by descending from TfmIdeDockForm which is declared in GX_IdeDock. You then have to call IdeDockManager.RegisterDockableForm like this:

unit GX_MyExpert;

interface

uses
[...]
  GX_IdeDock;

type
  TfmMyExpertForm = class(TfmIdeDockForm)
    [...]
  end;

[...]

implementation

var
  fmMyExpert: TfmMyExpertForm = nil;

[...]

  IdeDockManager.RegisterDockableForm(TfmMyExpertForm, fmMyExpert, 'MyExpertForm');

This tells the IDE about this new form, what it is being called (the name must be unique within the IDE), what type it is and the address of the variable where it should store the form reference.

There is no need to instantiate the form before calling RegisterDockableForm because the method does not access the form variable but only the address of the form variable. But because it stores the address of that variable, it should not be an instance variable of your expert, because that would mean that the address becomes invalid when your expert instance gets freed. So you should use a global variable for this (but declare it in the implementation section to limit its scope to the current unit).

(This, by the way, was the bug that caused the above mentioned cascading access violations.)

In addition, since every expert in GExperts can be enabled and disabled using the GExperts configuration dialog, you should call RegisterDockableForm in the expert’s SetActive method, when it is being enabled, and likewise call UnregisterDockableForm when it is being disabled. And since it doesn’t make sense to keep the form itself around when the expert is disabled, free it and nil the global variable.

procedure TMyExpert.SetActive(New: Boolean);
begin
  if New <> Active then begin
    inherited SetActive(New);
    if New then
      IdeDockManager.RegisterDockableForm(TfmMyExpertForm, fmMyExpert, 'MyExpertForm')
    else begin
      IdeDockManager.UnRegisterDockableForm(fmMyExpert, 'MyExpertForm');
      FreeAndNil(fmMyExpert);
    end;
  end;
end;

But where do you instantiate the form?

There are two ways the form gets instantiated. First, it should of course be done when the user clicks on the GExperts menu item for your expert.

procedure TMyExpert.Click(Sender: TObject);
begin
  if fmMyExpert= nil then begin
    fmMyExpert:= TfmMyExpertForm.Create(nil);
  end;
  fmMyExpert.Init;
  IdeDockManager.ShowForm(fmMyExpert);
  EnsureFormVisible(fmMyExpert);
end;

As you can see, you don’t call your form’s show method either, you call IdeDockManager.ShowForm instead. Also, since the user probably wants your form to become active, you call the EnsureFormVisible global procedure (located in GX_GenericUtils).

The second way the form is instantiated is out of your control: The docking manager creates it, e.g. when the user has saved a desktop containing your form and activates that desktop later. So, never assume that your form does not exist just because your code hasn’t instantiated it yet.

To make sure your instance variable is always valid or nil, you should add code to your form’s destructor that sets it to nil:

destructor TfmMyExpertForm.Destroy;
begin
  fmMyExpert:= nil;
  inherited;
end;

Another little gem, just in case you haven’t noticed it yet: GExperts forms have their own distinctive icon, the same that is also shown in the GExperts menu. To make your form look the same, you have to call the expert’s SetFormIcon method after it has been instantiated. But since, as I said above, you haven’t got any control when the form is being instantiated, and SetFormIcon is a method of the expert and not the form, how do you do that?

Unfortunately this requires yet another global (but declared in implementation) variable, this time a reference to your expert. Initialize it in your expert’s constructor and set it to nil in the destructor. Using this global variable, you can then access the expert’s icon from your form’s constructor.

[...]
implementation

var
  MyExpert: TMyExpert = nil;

[...]

constructor TMyExpert.Create;
begin
  inherited Create;

  [...]

  MyExpert := Self;
end;

destructor TMyExpert.Destroy;
begin
  MyExpert := nil;

  [...]

  inherited;
end;

constructor TfmMyExpertForm.Create(_Owner: TComponent);
begin
  inherited;
  if Assigned(MyExpert) then
    MyExpert.SetFormIcon(Self);
end;

btw: If fixed that for the ToDo-Expert. It didn’t show its own icon because SetFormIcon was called in the Click method only but the window was already instantiated so that call was never executed. There are probably other experts that have the same bug.

Experimental GExperts Version 1.38 2016-01-09 released

 Delphi, GExperts  Comments Off on Experimental GExperts Version 1.38 2016-01-09 released
Jan 092016
 

New year, new GExperts 😉

I just want to show off the one new feature that resides in the IDE tab of the configuration dialog:

GExperts Configuration-Enhance-Install-Packages2

(I also fixed the tab order in this dialog.)

What does it do? Quite simpple: It shows a little ‘…’ button in the package list dialog(s). You get this dialog by either selecting Component -> Install Packages or as part of the Project Options dialog:

GExperts-Install-Packages

A click on this button will open a new explorer window showing the directory in which the selected package resides and where the file is selected.

The code for opening the explorer window btw. is very simple:

  ShellExecute(0, nil, 'explorer.exe', PChar('/select,' + FPkgNameLabel.Caption), nil, SW_SHOWNORMAL)

The more complex part this time was to find the controls to modify because their names and classes have changed between the various Delphi versions. I had to even hack two different dialogs one called DlgPackageCheckList: TDlgPackageCheckList the other DelphiProjectOptionsDialog: TDelphiProjectOptionsDialog or in earlier Delphi versions ProjectOptionsDialog: TProjectOptionsDialog.

Unfortunately one additional feature I had planned apparently isn’t possible: Changing the description of a package. I could have sworn that the descriptions are read from the registry, but my experiments have shown that even though these descriptions are stored there, apparently the Delphi IDE does not display them but rather reads them from the package files. That’s a pity because I already had the button and code for doing this in place.

OK, I lied, there is more:

A new Expert shows the bookmarks set in the currently active editor view plus some context. You can double click on the list to jump to these bookmarks or use context menu to add, edit or delete bookmarks.

GExperts-Bookmark-Expert

The Open Tools API functionality for bookmarks is rather rudimentary. There is no way to actually edit bookmarks, all you can do is read them, set a new one, toggle one and jump to one:

type
  IOTAEditView140 = interface(IOTAEditView40)
    function BookmarkGoto(BookmarkID: Integer): Boolean;
    function BookmarkRecord(BookmarkID: Integer): Boolean;
    function BookmarkToggle(BookmarkID: Integer): Boolean;
[...]
    function GetBookmarkPos(BookmarkID: Integer): TOTACharPos;

So in order to change the line of an existing bookmark, you have to:

  • Save the current cursor position
  • Move the cursor to the line of the bookmark
  • Place the bookmark
  • And finally restore the cursor position
procedure TfmGxBookmarksForm.SetBookmark(const _ModuleName: string; _LineNo: Integer; _BmIdx: Integer = -1);
var
  EditView: IOTAEditView;
  SaveCursorPos: TOTAEditPos;
  BmEditPos: TOTAEditPos;
begin
  if not TryGetEditView(_ModuleName, EditView) then
    Exit;

  SaveCursorPos := EditView.GetCursorPos;
  try
    BmEditPos.Line := _LineNo;
    BmEditPos.Col := 1;
    EditView.SetCursorPos(BmEditPos);
    EditView.BookmarkRecord(_BmIdx);
  finally
    EditView.SetCursorPos(SaveCursorPos);
  end;
end;

btw: Did you know that bookmarks not only store the line but also the character position? You can have two bookmarks in the same line at different character positions.

With that out of the way, there is one small enhancement to GExperts itself. On an additional tab of the configuration dialog you can now re-enable messages that you had permanently suppressed:

GExperts Configuration-Suppressed-Messages

Up to now you had to use RegEdit for doing that:

GExperts-Suppressed-Messages

The are stored in

HKEY_CURRENT_USER\Software\Embarcadero\BDS\17.0\GExperts\Misc\SuppressedMesages

The key changed with the Delphi version, older versions use Borland or Codegear instead of Embarcadero, and Delphi instead of BDS. Delphi 10 Seattle is actually the 16th Delphi version. They cowardly left out version 13 (and we all want to forget version 8).

Placing a bookmark in an Excel spreadsheet and jumping to it

 MSOffice  Comments Off on Placing a bookmark in an Excel spreadsheet and jumping to it
Jan 052016
 

Many programs have got a bookmarking functionality that allow you to place a bookmark (optionally give it a name) at a given position. You can then later use that bookmark to jump to that previously marked position.

In Excel, a “bookmark” is synonymous to a hyper-link, but that’s not what I want, but the same functionality can be achieved by recording a macro:

  1. Select Tools -> Macro -> Record new Macro
  2. Give it a name and optionally a keyboard shortcut
  3. Click on the cell you want to bookmark
  4. Stop recording the macro

That’s it, now you have a macro that jumps to the cell you selected.

Now, if I just could simply press Ctrl+k+<number> to place a bookmark and Ctrl+q+<number> to jump to it.

Alternatively you can give that cell a name:

  1. Select the cell you want to bookmark
  2. Click into the “Name Box” (the box that usually displays the cell’s column and row)
  3. Type a custom name for the cell

That’s it, you can now jump to that cell by selecting it from the list that drops down when you click on the down arrow to the right of the name box.

Both methods also work for an area not just a single cell.