Bugfix for the Build Connection String dialog appearing on the wrong monitor

 Delphi, dzLib  Comments Off on Bugfix for the Build Connection String dialog appearing on the wrong monitor
Apr 142017

According to MSDN the window handle passed to IDBPromptInitialize::PromptDataSource will be used this way:

hWndParent [in]
The parent window handle for dialog boxes to be displayed. The dialog box will always be centred within this window.

Unfortunately it doesn’t work like this: If the parent form is on a secondary monitor the dialog is shown on the primary monitor on the side adjacent to the secondary monitor. It works fine when the parent form is on the primary monitor.

This is the code I used:

class function TConnectionInfoRec.EditConnectionString(_ParentHandle: HWND;
  var _ConnectionString: string): Boolean;
  DataInit: IDataInitialize;
  DBPrompt: IDBPromptInitialize;
  DataSource: IUnknown;
  InitStr: PWideChar;
  s: WideString;
  DataInit := CreateComObject(CLSID_DataLinks) as IDataInitialize;
  if _ConnectionString <> '' then begin
    s := _ConnectionString;
    DataInit.GetDataSource(nil, CLSCTX_INPROC_SERVER,
      PWideChar(s), IUnknown, DataSource);
  DBPrompt := CreateComObject(CLSID_DataLinks) as IDBPromptInitialize;
  Result := Succeeded(DBPrompt.PromptDataSource(nil, _ParentHandle,
    DBPROMPTOPTIONS_PROPERTYSHEET, 0, nil, nil, IUnknown, DataSource));
  if Result then begin
    InitStr := nil;
    DataInit.GetInitializationString(DataSource, True, InitStr);
    _ConnectionString := InitStr;

Called as

procedure TForm1.b_TestClick(Sender: TObject);
  s: string;
if  TConnectionInfoRec.EditConnectionString(Self.Handle, s) then
  ed_Result.Text := s;

I asked on Google+ for help but nobody could find an error in my code, so it apparently is a bug in Windows.

So I set out to find a workaround: Start a background thread (since the foreground thread is blocked by showing that dialog) that finds the dialog and moves it to the correct place.

Unfortunately GetActiveWindow didn’t return this window, so I resorted to GetForegroundWindow and checked the window’s process id with GetThreadProcessId to make sure I got the correct window. While this worked, it left a bad smell. Using EnumThreadWindow as suggested by Attila Kovacs also worked, so I used that instead.

But while writing this blog post, I reread the GetActiveWindow documentation and noticed the reference to GetGUIThreadInfo which sounded as if it might do exactly what I was looking for. And lo and behold, it actually does. So here is the solution I eventually used:

  TMoveWindowThread = class(TNamedThread)
    FParentHandle: HWND;
    FParentCenterX: Integer;
    FParentCenterY: Integer;
    procedure CenterWindow(wHandle: hwnd);
    procedure Execute; override;
    constructor Create(_ParentHandle: HWND);

{ TMoveWindowThread }

constructor TMoveWindowThread.Create(_ParentHandle: HWND);
  FreeOnTerminate := True;
  FParentHandle := _ParentHandle;
  inherited Create(False);

procedure TMoveWindowThread.CenterWindow(wHandle: hwnd);
  Rect: TRect;
  WindowCenterX: Integer;
  WindowCenterY: Integer;
  MoveByX: Integer;
  MoveByY: Integer;
  GetWindowRect(wHandle, Rect);
  WindowCenterX := Round(Rect.Left / 2 + Rect.Right / 2);
  WindowCenterY := Round(Rect.Top / 2 + Rect.Bottom / 2);
  MoveByX := WindowCenterX - FParentCenterX;
  MoveByY := WindowCenterY - FParentCenterY;
  MoveWindow(wHandle, Rect.Left - MoveByX, Rect.Top - MoveByY,
    Rect.Right - Rect.Left, Rect.Bottom - Rect.Top, False);

procedure TMoveWindowThread.Execute;
  Rect: TRect;
  MaxTickCount: DWORD;
  ThreadInfo: TGUIThreadinfo;
  GetWindowRect(FParentHandle, Rect);
  FParentCenterX := Round(Rect.Left / 2 + Rect.Right / 2);
  FParentCenterY := Round(Rect.Top / 2 + Rect.Bottom / 2);

  ThreadInfo.cbSize := SizeOf(ThreadInfo);
  MaxTickCount := GetTickCount + 10000; // 10 Seconds should be plenty
  while MaxTickCount > GetTickCount do begin
    if GetGUIThreadInfo(MainThreadID, ThreadInfo) then begin
      if ThreadInfo.hwndActive <> FParentHandle then begin

This thread is started from EditConnectionString by inserting the following code:

  // ...
  DBPrompt := CreateComObject(CLSID_DataLinks) as IDBPromptInitialize;

  if _ParentHandle <> 0 then begin
    // This is a hack to make the dialog appear centered on the parent window
    // According to https://msdn.microsoft.com/en-us/library/ms725392(v=vs.85).aspx
    // the dialog should automatically be centered on the passed parent handle,
    // but if the parent window is not on the primary monitor this does not work.
    // So, we start a background thread that waits for the dialog to appear and then
    // moves it to the correct position.

  Result := Succeeded(DBPrompt.PromptDataSource(nil, _ParentHandle,
    DBPROMPTOPTIONS_PROPERTYSHEET, 0, nil, nil, IUnknown, DataSource));
  // ...

Unfortunately the dialog briefly still appears in the wrong position. I found now way around that.

The code is in my dzlib library in unit u_dzConnectionString.

 Posted by on 2017-04-14 at 19:52

tdbf Packages for Delphi 10.2

 Delphi  Comments Off on tdbf Packages for Delphi 10.2
Apr 142017

Since we use it at work, I have had write access to the tdbf repository on SourceForge for a while. And apparently I am the only one who cares about support for the latest Delphi versions.

So, now there are packages for Delphi 10.2 Tokyo in the repository.

Be warned though: They compile and install into the IDE but I haven’t done any tests at all.

 Posted by on 2017-04-14 at 15:35

Delphi Known IDE Packages Manager updated to Delphi 10.2

 Delphi, KnownIdePackagesManager  Comments Off on Delphi Known IDE Packages Manager updated to Delphi 10.2
Apr 142017

A while ago, after my post on Known IDE Packages in Delphi I wrote a the KnownIdePackagesManager tool which lists those packages, allows to disable and enable some of them and also set the package description for those packages that don’t have a meaningful description (usually “(Untitled)”).

Today, I updated the tool to support Delphi 10.2.

 Posted by on 2017-04-14 at 12:20