Using TScreen in Delphi 7
Asked Answered
Y

3

8

My Delphi-7 application displays :

Screen.DesktopWidth  
Screen.DesktopHeight  
Screen.Monitors[0].Width  
Screen.Monitors[0].Height  

and , if there's a second monitor selected , also :

Screen.Monitors[1].Width  
Screen.Monitors[1].Height  

With the application running on my WinXP-Pro PC , I go to Control Panel / Display / Settings , and change the settings for the second monitor (either add or remove it) .

I then click on a Refresh button to display the new values of the 4 (or 6) parameters , and something unexpected happens : Screen.DesktopWidth and Screen.DesktopHeight show the correct new values , but the values of the other 2 (or 4) parameters are very wrong .

Like Screen.Monitors[0].Width = 5586935 , while it should be 1680 .

Are there some special rules for using TScreen in Delphi 7 ?

Yonina answered 23/6, 2012 at 7:31 Comment(12)
I can't simulate it since I have one monitor and Delphi 2009 but I guess the problem might be with the monitor list refresh (in Delphi 2009 this is being done through the private procedure 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 the Screen instance and create it again. And if so, then the following should refresh these data Screen.Free; Screen := TScreen.Create(nil);, but I really don't know how safe this action is.Laris
Are you taking a reference to the TMonitor instance Screen.Monitors[0] or are you getting Screen.Monitors[0] every time?Foible
@Laris "I guess that you get the correct values when you restart your application, don't you ?" That's right . And I also get the correct values when there's a ShowMessage statement just before the statements that display the 4 (or 6) parameters .Yonina
ShowMessage will result in the message queue being pumped. But I wouldn't expect queued messages to play a part here.Foible
BTW , the values in the ShowMessage window are still incorrect . But after clicking on the OK button in that window , the application window displays the correct values .Yonina
@ David Heffernan No reference . I always get Screen.Monitors[0] etc. .Yonina
BTW 2 : I just compiled the code in Delphi 2010 , and with regard to the incorrect values problem , the resulting application behaves exactly the same as the one compiled in Delphi 7 .Yonina
@Laris - Actually I'm adding/removing a secondary monitor to Screen.Desktop . The originally (quick and dirty) application was written to test a secondary monitor for any defective (dead/hot/stuck) pixels .Yonina
So then I think your primary monitor has changed its handle and the function (which internally calls the GetMonitorInfo) 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 the Monitor.Width (without Screen before, only Monitor.Width). This should btw. update the Screen.Monitors cached list in case the monitor obtained by the MonitorFromWindow call is not contained in that list.Laris
BTW, ShowMessage checks the monitor information (which automatically updates it) in order to determine where to show the message box, so that's why it works with ShowMessage.Elevate
@hvd, yeah the TTaskMessageDialog.DoOnDialogCreated called internally by the ShowMessage calling chain gets the monitor information, but it has two flaws. First, it does nothing with the Screen.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 the ShowMessage 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
@Laris I wasn't suggesting it as a fix, I was merely trying to explain the behaviour mentioned in an earlier comment.Elevate
K
4

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.

Kine answered 14/9, 2015 at 3:23 Comment(1)
Thank you Codebeat! I searched a solution for this bug over 20 years :-) I still using Delphi 5 for old projects. But I had to move the EnumMonitorsProc function to the top of the source.Bertiebertila
I
1

Screen.Monitors array contain invalid values if you switch user while your program is running. We use this line of code to force the Screen object to update lists:

Screen.MonitorFromWindow(0, mdNull);
Isidore answered 8/8, 2014 at 12:35 Comment(1)
This worked for me. Let's see if it works also for USB monitors....Margotmargrave
Y
0

Thanks to TLama , I found a workaround for the TScreen problem in Delphi 7 .

The original code that 'caused' the problem :

LabMon1.Caption := ' Mon 1: ' + IntToStr (Screen.Monitors[0].Width) +
                   ' x ' + IntToStr (Screen.Monitors[0].Height);

if (Screen.MonitorCount = 1)
then LabMon2.Caption := ' Mon 2: -'
else LabMon2.Caption := ' Mon 2: ' + IntToStr (Screen.Monitors[1].Width) +
                        ' x ' + IntToStr (Screen.Monitors[1].Height);

I only had to add 1 line of code to solve it :

LabMon1.Caption := ' Mon 1: ' + IntToStr (Monitor.Width) +
                   ' x ' + IntToStr (Monitor.Height) ;

LabMon1.Caption := ' Mon 1: ' + IntToStr (Screen.Monitors[0].Width) +
                   ' x ' + IntToStr (Screen.Monitors[0].Height);

if (Screen.MonitorCount = 1)
then LabMon2.Caption := ' Mon 2: -'
else LabMon2.Caption := ' Mon 2: ' + IntToStr (Screen.Monitors[1].Width) +
                        ' x ' + IntToStr (Screen.Monitors[1].Height);

So thanks again TLama , for your great contributions to this Question-thread !

Yonina answered 24/6, 2012 at 12:50 Comment(2)
You're welcome! Actually, you don't need to use the Monitor.Width or Monitor.Height or assign their values somewhere. It is enough to touch the Monitor property whose getter updates the Screen.Monitors list when the handle of the primary monitor is not found in that list. I've used that just for your width check. If you're lucky and compiler doesn't eliminate such statement it might be enough to use just Monitor; what might force the getter to do what I've described. However still it's dirty way somehow.Laris
Is this bug still present in recent editions of Delphi? (like XE7)Margotmargrave

© 2022 - 2024 — McMap. All rights reserved.