【问题标题】:Using TScreen in Delphi 7在 Delphi 7 中使用 TScreen
【发布时间】:2012-06-23 07:31:33
【问题描述】:

我的 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 是否有一些特殊规则?

【问题讨论】:

  • 我无法模拟它,因为我有一台显示器和 Delphi 2009,但我想问题可能出在显示器列表刷新上(在 Delphi 2009 中,这是通过私有过程 Screen.GetMonitors 完成的) .我猜您在重新启动应用程序时会得到正确的值,不是吗?如果我没记错的话,也许 Sertac 在某处写道,销毁 Screen 实例并重新创建它是安全的。如果是这样,那么下面应该会刷新这些数据Screen.Free; Screen := TScreen.Create(nil);,但是我真的不知道这个动作有多安全。
  • 您是引用 TMonitor 实例 Screen.Monitors[0] 还是每次都获取 Screen.Monitors[0]?
  • @TLama “我猜你在重新启动应用程序时会得到正确的值,不是吗?”这是正确的 。当显示 4 个(或 6 个)参数的语句之前有一个 ShowMessage 语句时,我也会得到正确的值。
  • ShowMessage 将导致消息队列被抽出。但我不希望排队的消息在这里发挥作用。
  • 那么我认为您的主监视器已经更改了它的句柄并且函数(内部调用GetMonitorInfo)由于不是实际句柄而失败(并返回 random 值) .问题似乎在于监视器列表 (TScreen.FMonitors),它是 cached 并且不会更改(在任何时候?我必须看看...)。同时尝试检查Monitor.Width 的值(之前没有Screen,只有Monitor.Width)。这应该顺便说一句。更新Screen.Monitors 缓存列表,以防MonitorFromWindow 调用获得的监视器不包含在该列表中。

标签: delphi delphi-7


【解决方案1】:

因为连接或断开显示器或 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 事件。

【讨论】:

  • 谢谢 Codebeat! 20 多年来,我一直在寻找这个 bug 的解决方案 :-) 我仍然将 Delphi 5 用于旧项目。但我不得不将 EnumMonitorsProc 函数移到源代码的顶部。
【解决方案2】:

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

Screen.MonitorFromWindow(0, mdNull);

【讨论】:

  • 这对我有用。让我们看看它是否也适用于 USB 显示器......
【解决方案3】:

感谢 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);

我只需要添加 1 行代码来解决它:

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,感谢您对这个问题线程的巨大贡献!

【讨论】:

  • 不客气!实际上,您不需要使用Monitor.WidthMonitor.Height 或在某处分配它们的值。当在列表中找不到主监视器的句柄时,touch 其 getter 更新 Screen.Monitors 列表的 Monitor 属性就足够了。我只是用它来检查你的宽度。如果你很幸运并且编译器没有消除这样的声明,那么只使用Monitor; 可能就足够了,这可能会迫使吸气剂做我所描述的事情。但不知何故,这仍然是一种肮脏的方式。
  • 最新版本的 Delphi 中是否仍然存在此错误? (如 XE7)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多