在Delphi 7中使用TScreen

8
我的 Delphi-7 应用程序显示:
Screen.DesktopWidth  
Screen.DesktopHeight  
Screen.Monitors[0].Width  
Screen.Monitors[0].Height  

如果有第二个显示器被选中,也会:

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

当我在我的WinXP-Pro PC上运行应用程序时,我进入控制面板/显示器/设置,并更改第二个显示器的设置(添加或删除)。
然后,我单击刷新按钮以显示4(或6)个参数的新值,结果出现了一些意外情况:Screen.DesktopWidth和Screen.DesktopHeight显示正确的新值,但另外2(或4)个参数的值非常错误。
例如,Screen.Monitors[0].Width = 5586935,而实际应该是1680。
在Delphi 7中使用TScreen是否有特殊规定?

2
由于我只有一个显示器和 Delphi 2009,所以无法模拟它,但我猜问题可能与监视器列表刷新有关(在 Delphi 2009 中,这是通过私有过程 Screen.GetMonitors 完成的)。我猜当您重新启动应用程序时,会得到正确的值,对吗?如果我记得正确的话,Sertac 可能在某个地方写过,安全地销毁 Screen 实例并重新创建它。如果是这样,那么以下操作应该可以刷新这些数据 Screen.Free; Screen := TScreen.Create(nil);,但我真的不知道这个操作有多安全。 - TLama
1
ShowMessage会导致消息队列被处理。但我不认为排队的消息在这里起作用。 - David Heffernan
顺便提一下,我刚在 Delphi 2010 中编译了这段代码,并且关于“错误值问题”,生成的应用程序的行为与在 Delphi 7 中编译的应用程序完全相同。 - Ruud Schmeitz
@TLama - 实际上我正在添加/移除一个辅助显示器到 Screen.Desktop。最初(草率的)应用程序是为了测试辅助显示器是否存在任何缺陷(死/亮/卡住的)像素而编写的。 - Ruud Schmeitz
2
那么我认为您的主监视器已更改其句柄,而该函数(内部调用GetMonitorInfo)由于不存在实际句柄而失败(并返回随机值)。问题似乎出在监视器列表(TScreen.FMonitors)上,它被缓存并且不会改变(任何时候?我得看看...)。同时尝试检查Monitor.Width的值(没有Screen,只有Monitor.Width)。顺便说一下,如果由MonitorFromWindow调用获取的监视器未包含在该列表中,则应更新Screen.Monitors缓存列表。 - TLama
显示剩余7条评论
3个回答

4

因为连接或断开显示器或USB显示设备时TScreen存在刷新问题(缺陷),所以来到这里。@Dave82的答案对我没有用。函数MonitorFromWindow的结果必须返回另一个值(未知/无效值)以强制更新TScreen对象。

以下作弊代码可以解决问题:

请确保multimon在uses子句中:

uses
 multimon;

将此内容添加到(表单)界面部分。
protected
procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE;

将此内容添加到表单的实现部分

    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;

在customform(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;

希望这对于寻找此类信息的人有所帮助。如果您想要检测显示设备设置的更改(分辨率和方向),请捕获WM_DISPLAYCHANGE事件。


1
谢谢Codebeat!我寻找这个bug的解决方法已经20多年了 :-) 我仍然在为旧项目使用Delphi 5。但是我必须将EnumMonitorsProc函数移到源代码的顶部。 - Sylvio Ruiz Neto

1

如果您在程序运行时切换用户,则Screen.Monitors数组中包含无效值。我们使用以下代码强制更新Screen对象的列表:

Screen.MonitorFromWindow(0, mdNull);

这对我有效。让我们看看它是否也适用于USB显示器.... - Gabriel

0
感谢TLama,我在Delphi 7中找到了解决TScreen问题的方法。
导致问题的原始代码:
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);

我只需要添加一行代码就解决了这个问题:
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);

再次感谢TLama,为这个问题线程做出的巨大贡献!


3
不用谢!实际上,您不需要使用Monitor.WidthMonitor.Height或将它们的值分配到某个地方。只需触碰 Monitor 属性,其getter方法在未在该列表中找到主监视器的句柄时会更新 Screen.Monitors 列表。我只是为了检查您的宽度使用了这个。如果你很幸运编译器不会消除这样的语句,只使用Monitor;可能就足够让getter执行我描述的操作。但是,这仍然是一种有些笨拙的方法。 - TLama
这个 bug 在最近的 Delphi 版本中是否仍然存在?(例如 XE7) - Gabriel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接