A new way to call the experts in GExperts

 Delphi, GExperts  Comments Off on A new way to call the experts in GExperts
Feb 032016
 

The Delphi IDE comes preconfigured with quite a lot of keyboard shortcuts and the remaining keyboard combinations are getting less and less with every plugin that needs some more. Even worse, some functions have multiple shortcuts, e.g. the replace dialog can be called using each of Ctrl+Q H or Ctrl+R or Ctrl+H (what a waste!).

GExperts installs an entry in the main menu, which you can open with Alt+X, and then select one of the enabled experts with the keyboard, either with the menu item’s hotkey or using the up/down keys and Enter. So in theory it should be possible to call each expert with Alt+X + <some character>. And this in turn would alleviate the need for registering a shortcut for each of these experts, freeing this scarce resource for other uses.

So, problem solved? Unfortunately not. Depending on which experts are enabled, you get a few or many conflicting hotkeys for the menu entries. That wouldn’t be too bad, but unfortunately the IDE insists on remapping these hotkeys and in my opinion gets it totally wrong. Instead of first using the hotkeys assigned by GExperts and only then trying to resolve the conflicts, it totally screws it and we end up with many items that don’t get a hotkey, and even worse, which item gets which hotkey depends heavily on how many and which experts you have enabled. So, instead of memorizing Alt+X + G for “&Grep search”, (which is the hotkey assigned by GExperts) you might have to remember Alt+X H (“Grep searc&h”) or might even have to resort to using the arrow keys or the mouse.

GExperts-No-Hotkeys-in-Menu

(click for full size animation)

First I thought, I’d just disable this annoying behaviour by setting the GExperts menu’s AutoHotkeys property to maManual. This should have done the trick in theory, but in practice it does not work if the menu is bound to a toolbar rather than a form. I was unable to prevent the IDE from doing this stupid thing. So I went looking for other options.

The IDE itself still has some of the WordStar keyboard shortcuts that consist of not just one but two key presses, e.g. Ctrl+Q + F for accessing the search functionality or Ctrl+Q + G for replace. This got me thinking: Why not assign a similar leading shortcut, e.g. Ctrl+H to GExperts and let the user configure his own characters for the second key press? And display a hint window which keys are available, so it is easier to memorize them, e.g. like this.

GExperts-hint-windows

The Open Tools API provides a mechanism to register these kinds of shortcuts. You simply specify more than one shortcut to a single callback method using the IOTAKeyBindingServices.AddKeyBinding method:

procedure TGxKeyboardBinding.BindKeyboard(const BindingServices: IOTAKeyBindingServices);
const
  DefaultKeyBindingsFlag = kfImplicitShift + kfImplicitModifier + kfImplicitKeypad;
var
  GExpertsShortcut: Byte;
  ShiftState: TShiftState;
  FirstShortCut: TShortCut;
  SecondShortCut: TShortCut;
begin
  GExpertsShortcut := Ord('H');
  ShiftState := [ssShift, ssCtrl];
  FirstShortCut := ShortCut(GExpertsShortcut, ShiftState);
  SecondShortCut := ShortCut(Ord('X'), []);
  BindingServices.AddKeyBinding([FirstShortCut, SecondShortCut],
    TwoKeyBindingHandler, nil,
    DefaultKeyBindingsFlag, '', '');
end;

The above registers Ctrl+Shift+H as the first shortcut and X as the second, so pressing Ctrl+Shift+H + X should call the TwoKeyBindingHandler. Simple, isn’t it? Not so. It worked for Ctrl+H + X but not for Ctrl+Shift+H + X. I couldn’t figure out what I might be doing wrong, so I turned to StackOverflow, which unfortunately for once didn’t provide a solution either.

And even worse: It turned out that pressing Ctrl+H in Delphi XE+ invoked the Replace dialog and there apparently was nothing I could do about this. So, back to square one (Do not pass Go. Do not collect $200…).

So, I gave up for the time being and went to bed (It was quite late anyway).

Maybe getting some sleep helped or maybe I am just a genius, but the solution I came up with today was quite simple:

Instead of going through the trouble of building and displaying a hint window, why not pop up a menu? This only requires one regular shortcut and by prepending the desired character to the menu items the IDE even does the work of calling the correct expert. No hooking Application.OnMessage for showing the hint window necessary. No two key shortcuts either. And even Ctrl+H no longer opened the the pesky Replace dialog in Delphi XE+. So here you go: An easy to memorize way to call the Experts.

Ctrl+Shift+H-menu

(click for full size animation)

Now I just need to add the configuration UI for this.

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

 none  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 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.

Experimental GExperts Version 1.38 2015-12-22 released

 Delphi, GExperts  Comments Off on Experimental GExperts Version 1.38 2015-12-22 released
Dec 232015
 

Just in time for the holidays, here it is, the Christmas release of my experimental GExperts version.

You might already have read my blog posts about the work on some of the new features, but let’s start with the “boring” one: I fixed the Unicode support again, this time actually the non-Unicode support because the latest changes had broken the formatter in Delphi 6 and 7.

On the more feature-ish side there are now some more dialogues that are being made resizeable if you enabled it in the configuration on the IDE tab:

GExperts Configuration-resize-options

Also, I split the options into “Allow resize” and “Remember position”. I for one disable remembering the position because in a multi monitor set-up, it’s more of an annoyance than a feature. Getting the Options dialogue in Delphi 6 an 7 to be resizeable was a bit of a challenge.

Most of you won’t care about the other two features, but I implemented them anyway: GExperts now shows an icon on the IDE splash screen

Delphi7-SplashScreen-with-plugins

as well as an entry on the IDE about dialog.

D10-About-Dialog-with-GExperts-entry

The latter currently only for Delphi 2005 and later, but I might also think of a way to do the same for Delphi 6 and 7.

I also updated the Experimental GExperts page to reflect the latest changes. It now also includes an animated GIF demonstrating how to configure and use the Code Formatter.