Apr 172014
 

Since I work on several projects that require multiple Delphi installations, I needed a way to start the correct Delphi IDE and call the correct command line compiler for each of these projects. There apparently is no way to automatically detect the Delphi version from the .dof,.dpr or .dproj file (or at least this is limited), so I added batch files to these projects that call the correct Delphi version.

One of the things that irked me was that I had to adapt these batch files whenever I was working on a different computer and Delphi was not installed in the default installation directory. Finally I found a solution for this: The required information is stored in the registry at

HKCU\Software\(Borland|CodeGear|Embaracdero)\(TheDelphiVersion)\root

And the place where this is stored does not vary. I set out to find a way to read the registry from a batch file and found this answer on stackoverflow.

So, here is the result. A batch file that, when called with a Delphi version as parameter, returns the directory it is installed in:

@rem set the DelphiPath variable for various Delphi versions

setlocal

set DelphiVersion=%1

rem Support for Windows 7/8, 64 Bit
set ProgFiles=%ProgramFiles(x86)%
if not "%ProgFiles%"=="" goto Win64Bit
set ProgFiles=%ProgramFiles%
:Win64Bit

set DelphiPath=

rem this is equivalent to a case/switch statement
rem call :Delphi%DelphiVersion% resolves into call :Delphi6 etc.
call :Delphi%DelphiVersion%
goto DelphiEndCase
:Delphi6
  call :ReadReg Borland\Delphi\6.0
  goto :eof
:Delphi7
  call :ReadReg Borland\Delphi\7.0
  goto :eof
:Delphi2005
  call :ReadReg Borland\BDS\3.0
  goto :eof
:Delphi2006
  call :ReadReg Borland\BDS\4.0
  goto :eof
:Delphi2007
  call :ReadReg Borland\BDS\5.0
  goto :eof
:Delphi2009
  call :ReadReg CodeGear\BDS\6.0
  goto :eof
:Delphi2010
  call :ReadReg CodeGear\BDS\7.0
  goto :eof
:DelphiXE
  call :ReadReg Embarcadero\BDS\8.0
  goto :eof
:DelphiXE2
  call :ReadReg Embarcadero\BDS\9.0
  goto :eof
:DelphiXE3
  call :ReadReg Embarcadero\BDS\10.0
  goto :eof
:DelphiXE4
  call :ReadReg Embarcadero\BDS\11.0
  goto :eof
:DelphiXE5
  call :ReadReg Embarcadero\BDS\12.0
  goto :eof
:DelphiXE6
  call :ReadReg Embarcadero\BDS\14.0
  goto :eof
:DelphiEndCase

echo DelphiPath: "%DelphiPath%"
if exist "%DelphiPath%" goto allok
echo *** Error: Directory "%DelphiPath%" does not exist. ***
pause
goto :eof

:allok
endlocal & set DelphiPath=%DelphiPath%
rem echo DelphiPath: "%DelphiPath%"
goto :eof

:ReadReg
rem read the registry entry
set DelphiPath=
FOR /F "usebackq skip=2 tokens=3,*" %%A IN (`REG QUERY HKCU\Software\%1 /v RootDir 2^>nul`) DO (
  set DelphiPath=%%A %%B
)
rem remove one trailing space which might have been added because %%B was empty
rem remove any quotes
set DelphiPath=%DelphiPath:"=%
rem add quotes
set DelphiPath="%DelphiPath%"
rem remove space before the closing quote
set DelphiPath=%DelphiPath: "="%
rem remove any quotes
set DelphiPath=%DelphiPath:"=%
goto :eof

On this particular computer …

call delphipath.cmd XE6
echo %DelphiPath%

… writes …

d:\delphi\DelphiXE6

… and this …

call delphipath.cmd XE4
echo %DelphiPath%

… writes …

C:\Program Files (x86)\Embarcadero\RAD Studio\11.0\

This batch file is now part of my collection of build tools.

Apr 162014
 

Following up on Erik’s overnight surprise release of GExperts for Delphi XE6, here is a new release of my experimental version with the code formatter.

The DLLs for all Delphi versions supported by the official GExperts are included.

BE WARNED: This version has only tested in so far, that it can be installed into Delphi XE6. Nothing else!

Head over to the Experimental GExperts page to download the new version.

Apr 132014
 

While looking for the equivalent of a switch / case statement for Windows batch files, I came across this answer on Stackoverflow.

Basically, what it does is using environment variable substitution for simulating:

case %var% of
  1: rem do something
  2: rem do something else
  3: rem do something completely different
end

The code looks like this:

call :SomePrefix%var%
goto aftercase
:SomePrefix1
  rem do something
  goto :eof
:SomePrefix2
  rem do something else
  goto :eof
:SomePrefix3
  rem do something completely different
  goto :eof
:aftercase

If “do something” is some complex code that’s pretty nifty and possibly a bit faster than the naive approach:

if "%var%"=="1" rem do something
if "%var%"=="2" rem do something else
if "%var%"=="3" rem do something completely different

But still: The readability suffers quite a lot. And if speed was an issue, why would you use a batch file in the first place? Anyway, I had a use case for it so I used it.

Apr 122014
 

The latest version is the first version that supports Delphi XE5 and also officially XE4.

There is nothing really new about the formatter code. But the new release can be installed even if you don’t have the official GExperts installer (yet). This is an extract from the readme file:

** Installing without an official installer **
   
With two Delphi Releases per year and Erik Berry being busy otherwise,
new GExperts releases have been lagging behind. So in case there is no
official GExperts installer yet, these are the steps to install the
experimental version by hand:

1. Create a suitable GExperts directory. It doesn't have to be a
   subdirectory of %ProgramFiles% but may be anywhere, even on a network
   share if you are sure this share will always be available.
2. Extract all files from the ZIP somewhere
3. Copy all files from the extracted directory to the GExperts directory.
   (Do *NOT* copy the subdirectories!)
4. Copy the appropriate GExperts DLL from one of the subdirectories
   EditorExpert or RegularExpert to the GExperts directory.
5. Copy the files from the subdirectory FromRegularRelease to the
   GExperts directory.
6. Copy the appropriate cmd from the install subdirectory. To the GExperts
   directory.
7. Make sure that the Delphi IDE is not running
8. Run the cmd file. It will register the GExperts dll with the Delphi IDE.
9. Start Delphi and check that the new GExperts DLL has been loaded by
   opening the GExperts About dialog.

In theory it is possible to install GExperts for all Delphi versions into
the same GExperts directory. But be warned: This has not been tested
thoroughly.

Head over to the Experimental GExperts page to download the new version.

Delphi Console Applications

 Delphi  Kommentare deaktiviert
Apr 102014
 

Delphi allows to write Console applications as well as GUI applications, while the latter is the default.

But “Console” application is not clearly defined in the documentation as I found out only today (and I have been programming in Delphi for about 20 years).

There is the {$AppType Console} compiler directive which corresponds to the /cc compiler switch and which has no corresponding setting in the compiler options dialog. The File -> New -> Console Application menu generates a project containing this directive. As documented in the Delphi online help this compiler directive opens the INPUT and OUTPUT files which are associated with the stdin and stdout handles. In addition, at least since Delphi 2007 it also opens the ErrOutput file which is associated with the stderr handle. (The latter doesn’t seem to be documented.) Also, the IsConsole function returns true for programs compiled with this directive. The conditional symbol CONSOLE is not defined for these programs unless you also set the linker option "Generate console application". (Which is odd. Why should a linker option have any effect on a conditional symbol for the compiler? But that’s how it is.)

Then, there is the linker option "Generate console application". This option is not automatically set if you use the File -> New -> Console application menu. If you set it, the conditional symbol CONSOLE is defined. You can also set this option for a GUI program. In this case, CONSOLE also defined and in addition to your program’s form, you will also get a console window. Also IsConsole returns true and the files INPUT, OUTPUT and ErrOutput are opened.

So there are four possible combinations:

  1. AppType CONSOLE + "Generate console application" is set -> CONSOLE is defined and IsConsole returns true
  2. AppType GUI + "Generate console application" is set -> CONSOLE is defined and IsConsole returns true
  3. AppType CONSOLE + "Generate console application" is not set -> CONSOLE is not defined and IsConsole returns true
  4. AppType GUI + "Generate console application" is not set -> CONSOLE is not defined and IsConsole returns false

I could not find any difference between the cases 1 and 2. Maybe some additional startup code is generated in one of these cases? But the executable size doesn’t seem to change either. So, what is the actual point of the compiler directive?

So the important lesson here is:

The files INPUT, OUTPUT and ErrOutput are opened automatically if IsConsole returns true, regardless of the conditional symbol CONSOLE. I don’t really see the use of the latter.

For the curious here is the program code I used to find out the above:

program ConsoleTest;

{.$APPTYPE CONSOLE}

uses
  Windows;

begin
{$IFDEF CONSOLE}
  WriteLn('CONSOLE is defined');
{$ELSE}
  if IsConsole then
    WriteLn('CONSOLE is not defined')
  else
    Windows.MessageBox(0, 'CONSOLE is not defined', nil, MB_OK or MB_ICONINFORMATION);
{$ENDIF}
  if IsConsole then begin
    WriteLn('IsConsole returns true');
    WriteLn('Press Enter');
    Readln;
  end else begin
    Windows.MessageBox(0, 'IsConsole returns false', nil, MB_OK or MB_ICONINFORMATION);
  end;

end.

Creating DLLs with Delphi

 Delphi  Kommentare deaktiviert
Apr 102014
 

I just stumbled upon an article on The Delphi Wiki that I wrote years ago:

Creating DLLs

It’s still mostly valid. There have been some improvements in newer Delphi versions, in particular since Delphi 2006 you no longer need the borlndmm.dll if you want to use ShareMem. (But still: Using ShareMem means that your DLL can only be used by Delphi programs, so don’t do that, if you can avoid it.)

That’s not the only article I wrote for that Wiki. It’s interesting to see how much time I apparently had on my hands back then. Sometimes I wish those “good times” came back.

Apr 082014
 

One of my problems with Windows 8 is that Microsoft recommends to keep Automatic Updates on the setting “Install updates automatically” while I prefer the setting “Download updates but let me choose whether to install them”. (Actually I don’t really want to choose whether to install them but rather when to install them.)

It’s possible to change this setting, but Windows Defender then won’t download its signature updates because that’s bound to the same mechanism as Windows Update. So every day I get a message on my boot screen, that there are important updates available and that I should start Windows Update to install them. This is annoying like hell.

Thankfully with a little bit of googling I found a solution. There is a tool that that checks for signature updates (an supposedly downloads them, I couldn’t try it yet because I just updated them using Windows update). It must be called like this:

"C:\Program Files\Windows Defender\MpCmdRun.exe" -SignatureUpdate -MMPC

So adding this command to e.g. task scheduler should solve yet another Windows 8 annoyance for good.

Translating file filters

 Delphi  Kommentare deaktiviert
Apr 012014
 

You know these innocuous file filters used by Open / Save dialogs? They usually look like this:

First Filetype (*.ex1)|*.ex1|Second Filetype (*.ex2)|*.ex2|All Files (*.*)|*.*

And since typing them is so difficult because you must remember to type all these pipe symbols, Delphi provides a property editor for it.

Is there anybody who actually likes this property editor? I think it’s even more annoying than directly typing these strings. But why, oh why do I have to type the file filter twice? Once for the description and once for the actual filter?

It gets worse: Just imagine you localize an application that uses many such file filters. So you have lots of strings that contain the ‘All Files (*.*)|*.*’ part, but are different because they support different, but partly overlapping file types. You end up translating the same text over and over again:

First Filetype (*.ex1)|*.ex1|All Files (*.*)|*.*
Second Filetype (*.ex2)|*.ex2|All Files (*.*)|*.*
First Filetype (*.ex1)|*.ex1|Second Filetype (*.ex2)|*.ex2|All Files (*.*)|*.*

Maybe you even spelled ‘All Files’ inconsistently as ‘all files’, ‘all Files’ or ‘All files’. Which confuses the translator and looks bad in your program.

And now add human error: The translator overlooks the part with the actual filter because he just doesn’t know that the string contains two different kinds of text: The first part is human readable and must be translated, the second part is used by the computer to filter files, and it must not be translated, it must not even be changed. Your translator might create translations like this:

Erster Dateityp (*.ex1)|All Files|*.*
Zweiter Dateityp *.ex2|All Files (*.*)|*.*
Erster Dateityp (*.ex1)|*.ex1|Zweiter Dateityp  (*.ex2)|*.*e|All Files (*.*)|*.*

Did you notice the errors? Nobody really does and the program gets shipped. Now you have not only a translation error but also a bug in your program, because these strings are no longer valid file filters.

After this happened to me the gazillionth time, I had enough, so I created the following helper code:

type
  IdzFileFilterBuilder = interface ['{1EEE52D6-EA31-4C4D-8454-32B2C2BE1814}']
    function Add(const _Description: string; const _Mask: string; _AddMaskToDesc: Boolean = True): IdzFileFilterBuilder;
    function AddAvi: IdzFileFilterBuilder;
    function AddBmp: IdzFileFilterBuilder;
    // ... some more ...
    function Filter: string;
  end;

function FileFilterBuilder(_IncludeAllFiles: Boolean = True; const _AllSupported: string = ''): IdzFileFilterBuilder;

The idea is to call the FileFilterBuilder function once and add any filters you need to the returned interface by chaining the calls like this:

  od.Filter := FileFilterBuilder.AddBmp.AddJpg.AddGif.Value;

Or, if you like this formatting better:

  od.Filter := FileFilterBuilder
    .AddBmp
    .AddJpg
    .AddGif
    .Value;

No unwieldy strings that mix human readable and computer interpreted text, just a simple list of easily readable file extensions. And even if you want to do something more unusual like having one entry for all supported file types and adding a file type that is not predefined by the interface like this:

  od.Filter := FileFilterBuilder(true, 'All supported types')
    .AddBmp
    .AddJpg
    .AddGif
    .Add('Some other graphics format', '*.blub')
    .Value;

It is still quite readable. Of course the above strings must be translated, so we add _() function calls for dxgettext:

  od.Filter := FileFilterBuilder(true, _('All supported types'))
    .AddBmp
    .AddJpg
    .AddGif
    .Add(_('Some other graphics format'), '*.blub')
    .Value;

Notice that only the part that actually needs translation is inside the _() translation marker? There is no more way for the translator to change essential strings of your programs. He might create wrong translations but your program will still run.

Of course that was only the interface. I guess, implementing it won’t be too hard for most of my readers. But since I am in a generous mood, I give it to you for free (no, that’s not an April fools joke). Just download it from my dzlib utility library on sourceforge. The file is u_dzDialogUtils and it contains a few more of the predefined .AddXxx methods. Also, the library will soon contain the German translations for the filter strings, if you are interested. You will also need the pseudo templates in the templates subdirectory.

Oh, did I mention this little gem:

function TdzFileFilterBuilder.AddPicture: IdzFileFilterBuilder;
begin
  Result := Add(_('Picture files'), '*.bmp;*.jpg;*.jpeg').AddBmp.AddJpg;
end;

It adds three entries:

  • Picture Files (*.bmp;*.jpg;*.jpeg)
  • Bitmap Files (*.bmp)
  • JPEG files (*.jpg;*.jpeg)

Resulting in the following file filter string:

'Picture Files (*.bmp;*.jpg;*.jpeg)|*.bmp;*.jpg;*.jpeg|Bitmap Files (*.bmp)|*.bmp|JPEG files (*.jpg;*.jpeg)|*.jpg;*.jpeg'

(with the ‘All files’ entry added, if you passed true to the FileFilterBuilder function.)

Mrz 302014
 

While working on my dzMdbViewer I wondered why the mouse wheel didn’t seem to scroll the listbox containing the table names but did work in the dbtable. It turned out to be a misconception on my side:

The listbox contained only a few items so it didn’t have a vertical scroll bar. The default behaviour is to scroll only the view without changing the currently selected item. Of course this does not have any effect if all items are already visible.

But that wasn’t what I wanted the mouse wheel to do. I wanted it to have the same effect as on the dbtable, that is: Select the next/previous line.

Unfortunately a listbox does not have an OnMouseWheel event (a dbtable does have one), so I could not simply assign an event handler and be done with it. I resorted to a proven hack and created an interposer class that overrides TListBox.DoMouseWheel:

type
  TListBox = class(StdCtrls.TListBox)
    ///<summary>
    /// Overrides the default behaviour of the TListbox for mouse wheel scrolling:
    /// Rather than scroll the view without affecting the selected item, select the
    /// next/previous item as if the user pressed the up/down arrow key. </summary>
    function DoMouseWheel(_Shift: TShiftState; _WheelDelta: Integer; _MousePos: TPoint): Boolean; override;
  end;

type
  Tfr_AccessDb = class(TFrame)
    lb_Tables: TListBox;
  private
  public
  end;

implementation

{$R *.dfm}

uses
  Math,
  u_dzVclUtils;

{ TListBox }

function TListBox.DoMouseWheel(_Shift: TShiftState; _WheelDelta: Integer; _MousePos: TPoint): Boolean;
var
  Idx: Integer;
begin
  // calculate the index of the item to select
  Idx := ItemIndex - Sign(_WheelDelta);
  if Idx >= Items.Count then
    Idx := Items.Count
  else if Idx < 0 then
    Idx := 0;
  // select it
  ItemIndex := Idx;
  // and simulate a mouse click on it so the selected table gets displayed
  Self.Click;

  // tell the caller that the event has been handled
  Result := True;
end;

Simple, isn't it?

I found a more involved version on The Spirit of Delphi that overrides the WndProc method and changes the Message to a keyboard message. I like my solution better, it "feels" cleaner.

The other solution also creates a control rather than using an interposer class. That's cleaner than an interposer class but I'm not a great fan of writing my own components to solve shortcomings like this. It's just too much overhead.

Mrz 292014
 

In my previous post about converting from Subversion to Mercurial I assumed that I would want to migrate the history of changes from my svn repository to the new hg repository.

For some projects, I don’t really care about the history so I decided to take the quick and dirty route:

  • Create a new, empty master hg repository (on a server)
  • Clone that hg repository to a local directory
  • Check out the project from svn to the same local directory
  • Add a new .hgignore file to ignore the .svn subdirectory
  • Add the .hg subdirectory to the svn:ignore property
  • Add .hgignore to svn and commit that change
  • Add everything to hg, commit and push that change

That’s it. Now you have got a directory that contains your sources and they are managed with both, Subversion and Mercurial. Now, get into the habit of committing changes to both as long as you want. Once you are comfortable with Mercurial, just stop using Subversion. You might want to delete the .svn subdirectory at that point.