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;
var
  DataInit: IDataInitialize;
  DBPrompt: IDBPromptInitialize;
  DataSource: IUnknown;
  InitStr: PWideChar;
  s: WideString;
begin
  DataInit := CreateComObject(CLSID_DataLinks) as IDataInitialize;
  if _ConnectionString <> '' then begin
    s := _ConnectionString;
    DataInit.GetDataSource(nil, CLSCTX_INPROC_SERVER,
      PWideChar(s), IUnknown, DataSource);
  end;
  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;
  end;
end;

Called as

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

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:

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

{ TMoveWindowThread }

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

procedure TMoveWindowThread.CenterWindow(wHandle: hwnd);
var
  Rect: TRect;
  WindowCenterX: Integer;
  WindowCenterY: Integer;
  MoveByX: Integer;
  MoveByY: Integer;
begin
  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);
end;

procedure TMoveWindowThread.Execute;
var
  Rect: TRect;
  MaxTickCount: DWORD;
  ThreadInfo: TGUIThreadinfo;
begin
  inherited;
  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
    Sleep(50);
    if GetGUIThreadInfo(MainThreadID, ThreadInfo) then begin
      if ThreadInfo.hwndActive <> FParentHandle then begin
        CenterWindow(ThreadInfo.hwndActive);
        Exit;
      end;
    end;
  end;
end;

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.
    TMoveWindowThread.Create(_ParentHandle);
  end;

  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.

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.

Apr 142017
 

A while ago, after my post on Known IDE Packages in Delphi I wrote a 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.

Sourcecode and a prebuild executable are available form SourceForge.

A word of caution: You can easily damage your Delphi installation by disabling the wrong packages. I have tried to make it easy to revert those changes by keeping the disabled entries in a separate registry key, but you still get to take the full responsibility for what you do with this tool!

Apr 142017
 

I just updated the Custom Container Pack sources to support Delphi 10.2. It was mostly a matter of creating the packages for the “new” version. I also used the latest version of the Delphiversions.inc file.

It now compiles and installs. I have not tested it extensively.

Custom Containers Pack (CCPack) is an integrated tool and component mini-library to produce and maintain composite controls (or simply “composites”) and other containers (forms, data modules and frames). The process of building composite components looks like ActiveForm and Frame creating, but the result is the native VCL component. You can create new composites just as usual forms.

Here is the original documentation in RTF format.

It was originally developed by Sergey Orlik who posted the source code to code central

%d bloggers like this: