Jul 232014
 

I have got a form that usually displays outputs from various sources, each of which has a frame for its own purpose. Now, if an error occurs with any of theses sources, I want to display that error message on the bottom of the form, just above the panel that contains the action buttons. It looks like this:

    P2  **********************************
        *      *          *              *
        *  F1  *   F2     *      F3      *
        *      *          *              *
        **********************************
        *                                *
        *            F4                  *
    L1  *                                *
        **********************************
    P1  *         buttons go here        *
        **********************************

F1, F2, F3, F4 are the output frames for four of the sources, there can be many of various sizes.

My current solution is two panels and a label:
P2 is client aligned and contains all the frames
L1 is bottom aligned and normally invisible
P1 is bottom aligned

If an error occurs, I set the label’s caption and make it visible.

That works fine, if the error messages are short and fit within the window. Unfortunately, that’s not always the case. If the messages are longer, I would like to automatically word wrap them and increase the label’s hight.

I was prepared to start using Canvas.TextExtend etc. to write code for all of that, but it turned out there is a very simple solution:

Set the label’s WordWrap property to true and make sure that its AutoSize property is also true. That way the VCL will automatically take care of both, the word wrapping and increasing the label’s height if necessary.

(I think TLabel.WordWrap was introduce with Delphi 2007, but it might be even older.)

Adding fields to a TDataset in code

 Delphi  Kommentare deaktiviert
Jul 122014
 

The Delphi IDE allows you to add fields to a TDataset (descendant e.g. TTable, TQuery, TAdoTable etc.) by right clicking on the component and selecting “Add Field” or “New Field”. For a particular project I didn’t want to do that because I kept changing the query for which I want to add the fields. But since there was one calculated field I had to add fields to the dataset otherwise the OnCalcFields event wouldn’t be called. So I ended up adding the fields in code.

Basically that’s easy, you just create a TField descendant (e.g. TStringField,TAutoIncField, TDateTimeField etc.) set the field name and dataset and that’s it. But there are some pitfalls:

var
  fld: TField;
  ds: TDataset;
begin
  // init Dataset
  // ...
  // add fields
  fld := TWideStringField.Create(ds);
  fld.Name := '';
  fld.FieldName := 'TE_Employee';
  ds.Fields.Add(fld);

While this looks fine, it will not work. You will get the error “Field does not have a dataset” (or similar, I don’t remember the exact error message). The reason is that adding the field to the Dataset’s Fields collection does not automatically tell the field to which dataset it belongs. For that you need to assign the field’s Dataset property.

var
  fld: TField;
  ds: TDataset;
begin
  // init Dataset
  // ...
  // add fields
  fld := TWideStringField.Create(ds);
  fld.Name := '';
  fld.FieldName := 'TE_Employee';
  fld.Dataset := ds;
  ds.Fields.Add(fld);

If you write it like this, it will seem to work, but you will get an access violation when the dataset is being destroyed. Can you spot why?

I had to trace into the RTL sources to find the problem: The field was added to the Fields collection twice, because setting its Dataset property also adds it to the Fields collection. So, when the field’s collection got freed, the Field was freed twice, resulting in the AV.

So the correct and working code is this:

var
  fld: TField;
  ds: TDataset;
begin
  // init Dataset
  // ...
  // add fields
  fld := TWideStringField.Create(ds);
  fld.Name := '';
  fld.FieldName := 'TE_Employee';
  fld.Dataset := ds; // this automatically adds the field to the Dataset's Fields collection.

You might be wondering about the line

  fld.Name := '';

I set the name of all components created in code to an empty string to avoid name collisions with any existing components or even a different instance of the component created by the same code. “A component with the name Edit1 already exists.” has bitten me too often.

Since I also used a TDbGrid to display the dataset, I also added Columns to the DBGrid to set the caption and column width. And since I am lazy (as any good programmer should be), I moved most of that code into a (sub-) procedure to make the code more readable (actually I did it so I had less to type, but the result is the same):

var
  TheDataset: TDataset;
  TheGrid: TDbGrid;

procedure TMyForm.InitDbGrid;

  procedure AddField(_fld: TField; const _Fieldname: string; const _Caption: string; _Width: Integer);
  var
    Col: TColumn;
  begin
    _fld.FieldName := _Fieldname;
    _fld.ReadOnly := True;
    _fld.Dataset := TheDataset;
    Col := TheGrid.Columns.Add;
    Col.Expanded := False;
    Col.FieldName := _Fieldname;
    Col.Title.Caption := _Caption;
    Col.Width := _Width;
  end;

begin
  AddField(TAutoIncField.Create(nil), 'SeNr', _('Number'), 50);
  AddField(TDateTimeField.Create(nil), 'TeArbeitstag', _('Date'), 70);
  AddField(TWideStringField.Create(nil), 'SeVon', _('Start'), 40);
  AddField(TWideStringField.Create(nil), 'SeBis', _('End'), 40);
  fld := TWideStringField.Create(nil);
  fld.FieldKind := fkCalculated;
  AddField(fld, 'Stunden', _('Hours'), 50);
  AddField(TWideStringField.Create(nil), 'PROJ_IDENT', _('Project'), 50);
  AddField(TWideStringField.Create(nil), 'SSchluessel', _('Activity'), 50);
  AddField(TWideStringField.Create(nil), 'SeBeschreibung', _('Description'), 200);
  ResizeGridColumns;
end;

And just to show why this code is actually useful, here are the ResizeGridColumns method and TheDatasetCalcFields and FormResize events.

procedure TMyForm.ResizeGridColumns;
var
  cnt: Integer;
  TotalWidth: Integer;
  i: Integer;
begin
  TotalWidth := 0;
  cnt := TheGrid.Columns.Count;
  for i := 0 to cnt - 2 do begin
    TotalWidth := TotalWidth + TheGrid.Columns[i].Width;
  end;
  TheGrid.Columns[cnt - 1].Width := TheGrid.ClientWidth - TotalWidth - 50;
end;

procedure TMyForm.FormResize(Sender: TObject);
begin
  ResizeGridColumns;
end;

procedure TMyForm.TheDatasetCalcFields(Dataset: TDataSet);
var
  Von: TdzNullableTime;
  Bis: TdzNullableTime;
begin
  Von.AssignVariant(Dataset['seVon']);
  Bis.AssignVariant(Dataset['seBis']);
  Dataset.FieldByName('Stunden').Value := (Bis - Von).ToHourStr(2);
end;

(TdzNullableTime is an extended record type from my dzlib library which is available from sourceforge if you are interested. It is declared in u_dzNullableTime.pas.

GORM experimental release

 Delphi  Kommentare deaktiviert
Jul 052014
 

GORM is an editor for .po files that was originally written by Lars Dybdahl and to which I have been contributing quite a few features in recent years. Since Lars is too busy to do a release and currently even the original download doesn’t seem to work, I have compiled the current sources and provide a download here:

Gorm_2014-07-05.zip

In addition to the features described on the Gorm homepage this new version can do the following:

  • Edit the po file header.
  • Auto translate, using Google translate (not sure whether it is still available) or Microsoft Translator. Both need an application key that is not part of Gorm.
  • Auto translate from a local translation repository, which is an MS Access database, one per language, that can be shared by multiple developers
  • Auto translate using translation memory, that is a po file that is automatically generated.
  • Load and save translations to be ignored from a ignore.po file.
  • In addition to English, I provide it with German localization, that, of course, is created with Gorm itself.

The sources are available from the dxgettext repository on sourceforge.
Of course I like Gorm much better than PoEdit and other alternatives. If there are any bugs, that’s probably my fault.

Comparing Variants

 Delphi  Kommentare deaktiviert
Jul 012014
 

I hate Variants, did I say that before? They just make everything more complex than necessary.

Did you ever try to compare two variants? E.g. you have got one that contains an empty string (”) and another that contains a date. Now you want to know whether they are equal or not. Simple?

  v1 := '';
  v2 := EncodeDate(2014,07,01);
  if v1 <> v2 then
    WriteLn('not equal')
  else
    WriteLn('equal');

Not so. The code above raised a EVariantConversionError exception because it could not convert an empty string into a date.

Let me introduce you to two Delphi RTL functions from Variants.pas:

VarSameValue and VarCompareValue.

(I bet you didn’t know about them, did you?)

Assuming these functions work as described, the above must be written like this:

  v1 := '';
  v2 := EncodeDate(2014,07,01);
  if not VarSameValue(v1, v2) then
    WriteLn('not equal')
  else
    WriteLn('equal');

Unfortunately they fail as well with exactly the same error. So all that’s left is implementing it myself.

function VarIsSame(_A, _B: Variant): Boolean;
var
  LA, LB: TVarData;
begin
  LA := FindVarData(_A)^;
  LB := FindVarData(_B)^;
  if LA.VType <> LB.VType then
    Result := False
  else
    Result := (_A = _B);
end;

Note that this too might not do what you expect:

  VarIsSame('3', 3);

will return false! That’s why it’s called VarIsSame rather than VarIsSameValue. It doesn’t try to convert variants of different types, it just checks whether they are the same.
For my use case this is exactly what I need.

Did I mention that I hate Variants?

Comparing INI files

 Delphi, Windows  Kommentare deaktiviert
Jun 282014
 

INI files are a simple but powerful means of storing a program’s configuration. They have existed since the 16 bit Windows times, where they were stored in the Windows directory, later on they were stored in the program’s executable directory and nowadays they usually go somewhere into a user’s home directory. Microsoft has tried to replace them with more structured storage, first the Registry and later (in dotNEt) with XML files, but they are still popular because they are simple to copy and move around, easy to use and can be edited with a text editor. Programmers and system administrators love this kind of configuration.

They have one problem, though: They tend to get larger than expected and since they are only barely structured, it is rather tedious to compare them. Is there anybody who has not edited an INI file in notepad and changed the order of sections and entries so he can use a tool like Beyond Compare to find the actual differences between two INI files? I have done that a lot more often than I care to remember and it was always painful and slow.

Enter dzIniFileFormatter: This is a tool I wrote actually several years ago but only today remembered that I never blogged about it. It allows to sort sections and entries in two different ways:

  • Alphabetically
  • By Template

Using either it also tries to keep comments where they belong (I use // to mark comments, even though Microsoft proposed a semicolon, but unfortunately the TIniFile implementation in Delphi removes these latter comments while it leaves // comments intact.).

I hope the user interface is self explanatory:

dzIniFileFormatter

The most useful function is probably sorting by template. It allows you to resort e.g. a modified version of an INI file so it matches the original and can easily be compared using the above mentioned Beyond Compare or any other side by side file comparison utility.

The source code (Delphi 2010) is available from the dzIniFileFormatter page on SourceForge and I have just now uploaded a pre-compiled executable. I hope you’ll find this tool as useful as I did. If you have any suggestions, please contact me via google plus (see link in the upper right corner).

Setting the caret in a TMemo

 Delphi  Kommentare deaktiviert
Jun 252014
 

Once in a while you want to set the caret position (aka cursor position) in a TMemo to a given line and character in that line. If you google for it you will find lots of hits that tell you to do the following:

With Memo1 do
    SelStart := Perform(EM_LINEINDEX, Line, 0);

or without the ugly with statement

Memo1.SelStart := Memo1.Perform(EM_LINEINDEX, Line, 0);

But that looks to be at least pre-Delphi 2007 because there already is a TMemo.CaretPos property that does the same:

Memo1.CaretPos := Point(0, Line);

This is a lot easier to understand than the above. Note that you can’t just assign only x or y like this:

Memo1.CaretPos.y := Line; // does not work!

This won’t work, because accessing CaretPos calls a getter method that returns a TPoint value. Changing this value does not change the CaretPos property, you must write the actual CaretPos property.

Unfortunately, while this works fine in general, Borland has introduced a bug in TCustomGrid.Paint that breaks this (both) code. If you have got a TCustomGrid descendant (TStringGrid and friends) on the form or a different form, you might find that the caret of the memo shows at the position where it would be on the grid. See QC 25702 for a description and workaround.
Warning:
The workaround requires you to modify the VCL unit Grids.pas. To do that you should copy it from the Delphi installation to your project’s source code, add it to your project and only then modify it. Otherwise you might end up with a Delphi update overwriting it or even worse, failing to install.

Jun 192014
 

I was enhancing my dzMdbViewer tool with a view that shows the table structure rather than the data of the table, when I out of curiosity added

  ReportMemoryLeaksOnShutdown := True;

to the source code. I found some memory leaks that were easy to fix (and which I would probably have fixed anyway later when reviewing the code).

I continued working on the new functionality and used some rather old code I wrote about 10 years ago (part of dzlib, but only used in a special tool) for it. It allows reading the table structure of an MS Access database and puts it into a structure of interfaces. Everything worked fine, until I closed the program and got gazillions of memory leak reports.

It turned out that back then I had created circular references between these interfaces: Tables were referencing Indexes and Columns, Indexes were referencing Tables and Columns, Columns were referencing Tables and Indexes. Of courses, this had to create memory leaks because the reference counter of a circular reference can never reach 0 so these objects were never freed.

There is a cure for this kind of problem: It’s called weak references. Since the Delphi compiler for desktop (Windows) does not support weak references (The mobile compiler supports it via a [weak] attribute, but that attribute is ignored by the desktop compiler.), it must be implemented in code. I am a lazy bastard, so I turned to Google for an answer. This is an ancient problem, so somebody was bound to have blogged about it. I found several hits on StackOverflow and also this Synopse blog post that covers the problem in depth.

So, the solution was this procedure:

procedure SetWeak(_InterfaceField: PIInterface; const _Value: IInterface);
begin
  PPointer(_InterfaceField)^ := Pointer(_Value);
end;

It is used like this:

type
  TChild = class(TInterfacedObject, IChild)
  private
    FParent: IParent; // This must be a weak reference!
  public
    constructor Create(_Parent: IParent);
    destructor Destroy; override;
  end;

constructor TChild.Create(_Parent: IParent);
begin
  inherited Create;
  SetWeak(@FParent, _Parent);
end;

destructor TChild.Destroy;
begin
  SetWeak(@FParent, Nil);
  inherited;
end;

Assuming that TParent (which implements IParent) maintains a strong reference to its TChild objects via IChild interfaces, but the TChild object also needs a reference to its parent (via an IParent interface), we get a circular reference. To solve this, the FParent reference is declared as a weak reference (which is only a comment) and assigned via the SetWeak procedure. Now, assigning FParent does not increment the reference counter of the parent, so when there are no other references to it, it will get destroyed. When the parent gets destroyed all references to its children also get removed, which in turn triggers the child to be destroyed. The child still maintains the reference to the parent, but it is a weak reference, so it must not decrement the reference counter when FParent gets NILed. So, again, the destructor of TChild must use SetWeak to assign NIL to it.

Delphi7Help4BDS revisited

 Delphi  Kommentare deaktiviert
Jun 152014
 

Today I updated the Delphi 7 Help for BDS Expert (Delphi7Help4BDS) I wrote back then when Delphi 2005 came out and Borland managed to turn the online help into a mess (again).

It now supports Delphi 2005 to XE6 and allows to reconfigure F1 + any of the modifier keys Shift, Ctrl, Alt and Alt+Ctrl to call either a help file (you will need the old winhelp viewer that Microsoft dropped with (Vista?)), chm file or internet url. It comes preconfigured with a few example internet urls for searching with Google, Bing, the Embarcadero docwiki or MSDN.

To install it, get the sources from SourceForge, open the package for your Delphi version, compile and install it. You should then find a new entry in the IDE’s Help menu called “Configure Delphi7HelpForBds” which gets you the following dialog where you can configure the actions for each of the key combinations.

Delphi7HelpForBdsWizard

Jun 142014
 

Among a lot of other things you can add names for remote repositories to your mercurial.ini so you can access them without having to type that long path. This can be quite convenient e.g.

[path]
dzlib=ssh://twm@hg.code.sf.net/p/dzlib/hgdzmaincode

allows me to clone a copy of my dzlib+tools main repository on sourceforge like this:

hg clone dzlib dzlib+tools

rather than having to type:

hg clone ssh://twm@hg.code.sf.net/p/dzlib/hgdzmaincode dzlib+tools

Unfortunately this also tends to add the possibility for undesired side effects. Consider this:

hg clone dzlib dzlib

What is it supposed to do? What I wanted it to do, is simple: Clone the remote repository dzlib (as configured in the [path] section) to the subdirectory dzlib.
What it actually tries to do is: Clone the remote repository dzlib to the remote repository dzlib, which is definitely not what I wanted it to do.

Since I rarely create new clones from the remote repository I have removed the entries in [path] again, because the time potentially spent on troubleshooting these side effects is much longer than having to look up the remote url the few times I actually want to clone a remote repository.

Jun 142014
 

As described in a previous post I initially had some problems connecting to Mercurial repositories on SourceForge that went away without me changing anything. In that post I give the following entry for mercurial.ini:

[ui]
ssh="C:\Program Files (x86)\PuTTY\plink.exe" -ssh -agent -v -i "D:\path\to\my\private_key.ppk"

While this works well, if Pageant is already running and has loaded the key, it results in a non responsive console if either of these conditions is missing. This is quite annoying because I tend to forget to start Pageant and it takes me quite a while to realize what the problem is. A little bit of digging into the command line parameters of plink gave me the fix: Add the -batch switch, so it won’t accept any interactive prompts. So it should look like this:

[ui]
ssh="C:\Program Files (x86)\PuTTY\plink.exe" -ssh -batch -agent -v -i "D:\path\to\my\private_key.ppk"

If Pageant is not running or the private key not loaded, any connection attempt will within seconds result in the following error message:

hg incoming
abort: no suitable response from remote hg!