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

Mar 192017
 

A while ago I stopped using the formatter branch and officially took over the trunk of the GExperts repository. When I announced that on G+, Stefan Glienke asked me about my plans for GExperts.

My answer today is still the same as back then:

I will release new GExperts versions when I feel like it. And I will continue to call them experimental. There will be no rigorous testing, since nobody has volunteered to take on that responsibility. I will be using the latest development version in my daily work, so for now you can assume that at least the Delphi 2007 and XE2 versions are reasonably stable. This will change once I move on to newer versions, but unfortunately I don’t see that in the near future.

So, if you don’t like this, I would welcome volunteers to step forward and assume the responsibility of testing GExperts preferably with the Delphi versions they use every day. Your reward will be a place in the contributor list and possibly the pain of working with a not quite stable IDE.

Mar 192017
 

Some people have started pestering me about making a new release (you know how you are!). Don’t think that this has done anything to actually make me do it, I simply thought it to be the right time with Delphi 10.2 Tokyo apparently right around the corner.

I have created installers for all supported Delphi versions.

I don’t remember all the new features and bug fixes that went into this release, but I am pretty sure it is more stable and has more features than the previous one.

But anyway:

Please be aware that I mostly work with Delphi 2007, so this version can be regarded as tested quite well, followed by Delphi XE2. The others are only known to compile and new features are usually tested superficially with all versions. This is particularly true for Delphi 6/7 and 2005/2006.

Head over to the Experimental GExperts page to download the latest release it.

Mar 122017
 

For my dzComputerInfo tool I created a window without a title that can still be moved with the mouse. This is quite easy to do:

  1. To remove the title, set BorderStyle to bsNone.
  2. To let the user move it with the mouse, add the following message handler:
type
  TMyForm = class(TForm)
  private
    procedure WMNCHitTest(var Msg: TWMNcHitTest); message WM_NCHITTEST;
  end;

procedure TMyForm .WMNCHitTest(var Msg: TWMNcHitTest);
begin
  inherited;
  if (Msg.Result = htClient) then
    Msg.Result := htCaption;
end;

It tells Windows, that the user clicked on the title rather than the client area. Windows then does the rest, and the user can move the window with the mouse as if he clicked on the window title.

If you also want the window to have a context menu, you’ll have to change the message handler, so it does not affect right mouse clicks:

procedure TMyForm .WMNCHitTest(var Msg: TWMNcHitTest);
var
  Res: SmallInt;
begin
  inherited;
  Res := GetKeyState(VK_RBUTTON);
  if Res >= 0 then
    // only if the right mouse button was not pressed
    // (otherwise the popup menu wont show)
    if (Msg.Result = htClient) then
      Msg.Result := htCaption;
end;
Mar 122017
 

USB serial converters from FTDI are quite popular. We also use them at work quite a lot because they do not have the problem of the competing products (like Prolific): Windows does not detect devices on them as Microsoft ball point devices.

These converters can be configured interactively using a dialog accessible from the hardware manager’s device property dialog, page “Port Settings” by pressing the “Advanced …” button.

There are various settings, the most common ones to change are

  • COM Port Number
  • BM Options: Latency Timer
  • Miscellaneous Options: Serial Enumerator

The first one is obvious: It sets the COM port number of the emulated serial port. Every converter ever connected to the computer will reserve one COM port, so if you attach many of them you will sooner or later get rather high port numbers which many tools cannot use. The workaround is to force the driver to use a particular COM port here.

The second one, Latency Timer is not that obvious: It sets the latency timer in milliseconds to be used when the data received is not large enough to fill the buffer. Reducing this value from the default 16 to e.g. 4 solves many problems where data is being received with a delay of several seconds (e.g. the GPS position displayed is lagging behind your vehicle position by several seconds, which results in several 10th of metres at higher velocities. I have seen 4 seconds which at 60 km/h equals about 80 metres.)

The last one, Serial Enumerator, solves the Microsoft Ball Point detection mentioned above. As long as it is checked and a device is attached that sends data, Windows might mistakenly think it’s a mouse and the mouse cursor will jump all around the screen and even randomly click everywhere. This is quite annoying when it happens (and it happens very often when you connect a GPS). To resolve the problem, uncheck this option.
(Btw: Microsoft Ball Point devices have not been in use for over a decade, but the bug is still present in Windows XP, 7 and 8/8.1. (don’t know about Windows 10) despite users having problems because of it for many years. Shame on you, Microsoft!)

%d bloggers like this: