When Screen.Monitor[x].WorkAreaRect contains garbage

I’m posting this here so I can look it up later in case I forget it again:

Some Delphi versions (I know about Delphi 2007 and earlier, not sure when it was fixed) have got a bug in the VCL code for the TScreen (global Screen object) when the monitor configuration changes. That class caches the monitor configuration and does not update it when it changes, so it returns garbage afterwards. This particularly affects getting the WorkAreaRect of a monitor.
The monitor configuration changes more often than you might think, e.g. when you connect to a computer via Remote Desktop or when you attach or detach a monitor from a notebook computer.
However there is a simple workaround for this: Access the form’s Monitor property first, e.g.:

procedure TMyForm.Button1Click(Sender);
var
  i: Integer
begin
  Self.Monitor; // only call the getter, no need to actually use that value
  for i := 0 to Screen.MonitorCount-1 do begin
    WA := Screen.Monitors[i].WorkAreaRect;
    Memo1.Lines.Add(Format('Monitor#%d Top: %d Left: %d', [WA.Top, Wa.Left]));
  end;
end;

This works, because TCustomForm.GetMonitor gets the monitor handle for the window, then looks for it in Screen.Monitors[]. If it doesn’t find it there, it assumes that the monitor configuration has changed and calls Screen.GetMonitors which updates its internal list.

Of course, calling Screen.GetMonitors directly would be much easier, but unfortunately that method is private. TCustomForm.GetMonitor can only call it because its located in the same unit.

But wait, it is not that simple because you cannot rely on a button being clicked: The VCL also uses this functionality, e.g. when trying to show a hint. And that will even cause an Access Violation after the monitor configuration has changed. So it would be ideal to automate this. One option for this that I found is a handler for the WM_SETTINGCHANGE message.

type
  TMyForm = class(TForm)
  // ...
  protected
    procedure WMSettingChange(var _Msg: TMessage);
      message WM_SETTINGCHANGE;
  // ...
  end;
// ...
procedure TMyForm .WMSettingChange(var _Msg: TMessage);
begin
  if _Msg.WParam = SPI_SETWORKAREA then begin
    // Bufix for Access Violation when showing a hint
    // after the monitor configuration has changed:
    // Access the form's Monitor property
    Monitor;
  end;
  inherited;
end;

That did fix the problem in my tests.

I am sure I had found a different solution for this problem several years ago, but I can’t remember it and also can’t find it in my source code any more. I must be getting old. This solution is from Ruud Schmeitz answer to his own question on StackOverflow.