Came here because of refresh problem (bug) of TScreen when connect or disconnect a monitor or USB display device. The answer of @Dave82 doesn't work for me. The result of the function MonitorFromWindow must return another value (unknown/invalid value) to force an update of the TScreen object.
This cheat below does the trick:
Be sure multimon is in the uses clause:
uses
multimon;
Add this to the interface part (of the form)
protected
procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE;
Add this to implementation part (of the form)
function cheatMonitorFromWindow(hWnd: HWND; dwFlags: DWORD): HMONITOR; stdcall;
begin
// Does nothing, returns zero to force invalidate
Result:=0;
end;
procedure TForm1.WMDeviceChange(var Msg: TMessage);
var
iCurrDisplayCount : LongInt;
iNewDisplayCount : LongInt;
pMonitorFromWinProc : TMonitorFromWindow;
begin
iCurrDisplayCount:=Screen.MonitorCount;
// Force monitor update, fix bug in customform, won't update at display change.
// This a hack/cheat to multimon MonitorFromWindow func, it's fakes the result.
// This is required to tell customform.getMonitor() to update the TScreen object.
pMonitorFromWinProc:=MonitorFromWindow; // Backup pointer to dynamic assigned DLL func
MonitorFromWindow:=cheatMonitorFromWindow; // Assign cheat func
monitor; // call the monitor property that calls customform.getMonitor and cheatfunc
MonitorFromWindow:=pMonitorFromWinProc; // restore the original func
// ==========
iNewDisplayCount:=Screen.MonitorCount;
if( iCurrDisplayCount <> iNewDisplayCount ) then
begin
// Display count change!
end;
end;
What happen inside customform (code in Forms.pas)?
function TCustomForm.GetMonitor: TMonitor;
var
HM: HMonitor;
I: Integer;
begin
Result := nil;
HM := MonitorFromWindow(Handle, MONITOR_DEFAULTTONEAREST);
for I := 0 to Screen.MonitorCount - 1 do
if Screen.Monitors[I].Handle = HM then
begin
Result := Screen.Monitors[I];
Exit;
end;
//if we get here, the Monitors array has changed, so we need to clear and reinitialize it
for i := 0 to Screen.MonitorCount-1 do
TMonitor(Screen.FMonitors[i]).Free;
Screen.FMonitors.Clear;
EnumDisplayMonitors(0, nil, @EnumMonitorsProc, LongInt(Screen.FMonitors));
for I := 0 to Screen.MonitorCount - 1 do
if Screen.Monitors[I].Handle = HM then
begin
Result := Screen.Monitors[I];
Exit;
end;
end;
Hopes it helps when somebody is looking for this. When you want to detect display device settings changes (resolution and orientation), catch the WM_DISPLAYCHANGE event instead.
Screen.GetMonitors
). I guess that you get the correct values when you restart your application, don't you ? And if I remember that correctly, maybe Sertac wrote somewhere that it's safe to destroy theScreen
instance and create it again. And if so, then the following should refresh these dataScreen.Free; Screen := TScreen.Create(nil);
, but I really don't know how safe this action is. – LarisGetMonitorInfo
) fails due to not actual handle (and returns random values). The problem seems to be with the list of monitors (TScreen.FMonitors
) which is cached and doesn't change (at any time ? I have to take a look...). In the meantime try to check the value of theMonitor.Width
(withoutScreen
before, onlyMonitor.Width
). This should btw. update theScreen.Monitors
cached list in case the monitor obtained by theMonitorFromWindow
call is not contained in that list. – LarisTTaskMessageDialog.DoOnDialogCreated
called internally by theShowMessage
calling chain gets the monitor information, but it has two flaws. First, it does nothing with theScreen.FMonitors
list which needs to be updated to reflect monitor changes (it just locally obtains the information for itself) and second is that it would be too late (how would you want to pass those values into theShowMessage
function, even if it would update monitor list, you would have to call it once for update changes and second time to display the text with metrics). – Laris