如何在高DPI系统中获取真实的屏幕分辨率?

3
因此,Delphi程序不支持DPI感知。这并没有困扰我太多,直到最近我需要在高DPI的计算机上获得真实的屏幕分辨率(当“使屏幕上的内容更容易阅读”为150%时,Screen.Width报告的分辨率错误)。一些人建议将应用程序设置为高DPI感知(XML清单),但其他人警告我们这需要大量的工作!因此,由于懒惰(或缺乏时间),我想知道是否有计算真实分辨率的诀窍。
一个非常简单粗暴的诀窍是创建一个伴随工具(小型控制台应用程序)它是DPI感知的。然后我只需调用此工具并从中获取真实分辨率即可。相当简陋但应该有效。无论如何,肯定有更好的方法来解决这个问题!
3个回答

5
Win32_DesktopMonitor WMI类将提供信息。
例如,使用从这里获取的代码:Delphi7:获取连接的监视器属性
{$APPTYPE CONSOLE}

uses
  SysUtils,
  ActiveX,
  ComObj,
  Variants;

function VarStrNull(VarStr: OleVariant): string;
// dummy function to handle null variants
begin
  Result := '';
  if not VarIsNull(VarStr) then
    Result := VarToStr(VarStr);
end;

procedure GetMonitorInfo;
var
  objWMIService: OleVariant;
  colItems: OleVariant;
  colItem: OleVariant;
  oEnum: IEnumvariant;
  iValue: LongWord;

  function GetWMIObject(const objectName: String): IDispatch;
  var
    chEaten: Integer;
    BindCtx: IBindCtx;
    Moniker: IMoniker;
  begin
    OleCheck(CreateBindCtx(0, BindCtx));
    OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten,
      Moniker));
    OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
  end;

begin
  objWMIService := GetWMIObject('winmgmts:\\localhost\root\CIMV2');
  colItems := objWMIService.ExecQuery
    ('SELECT * FROM Win32_DesktopMonitor', 'WQL', 0);
  oEnum := IUnknown(colItems._NewEnum) as IEnumvariant;
  while oEnum.Next(1, colItem, iValue) = 0 do
  begin
    Writeln('Caption      ' + VarStrNull(colItem.Caption));
    Writeln('Device ID    ' + VarStrNull(colItem.DeviceID));
    Writeln('Width        ' + VarStrNull(colItem.ScreenWidth));
    Writeln('Height       ' + VarStrNull(colItem.ScreenHeight));
    Writeln;
  end;
end;

begin
  try
    CoInitialize(nil);
    try
      GetMonitorInfo;
      Readln;
    finally
      CoUninitialize;
    end;
  except
    on E: Exception do
    begin
      Writeln(E.Classname, ': ', E.Message);
      Readln;
    end;
  end;
end.

如果由于任何原因WMI不可用,那么您需要一个单独的DPI感知进程来完成工作。这也将涉及一些IPC。
另一个问题是,最近版本的Windows已更改了这些WMI类的行为。您可能需要使用不同的WMI查询。请参见此处:https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/138d387c-b222-4c9f-b3bb-c69ee890491c/problem-with-win32desktopmonitor-in-windows-8-platform?forum=windowsgeneraldevelopmentissues

这听起来像是一个很棒的解决方案。不幸的是,我曾经在一些电脑上遇到了WMI无法使用的糟糕经历。我的程序之前使用了一些WMI函数。最终我不得不将它们移除。如果你在谷歌上搜索“WMI不可用”,你会看到很多报告。 - Gabriel
@davidhefferman-我知道为什么我收到了那么多与WMI相关的错误:WMI是一个服务,在某些系统上默认情况下不会开启!!!https://www.youtube.com/watch?v=JgsHfc-4TUE - Gabriel
这与我的 WMI 经验不符。 - David Heffernan
@hikari 请查看最后一段。 - David Heffernan
1
放弃了这个。制作了一个带有 DPI 感知清单的辅助 exe,将主应用程序的句柄作为参数运行 -> 辅助程序使用 w/h 参数发送消息,完成操作。(比读取控制台输出更快) - hikari
@hikari - 我也使用相同的想法(外部助手)。效果非常好。我唯一遇到的问题是有时候杀毒软件会允许主应用程序运行,但会阻止助手。但这种情况并不经常发生。显然,用户正在禁用WMI,因为它可能被恶意软件利用:https://www.youtube.com/watch?v=0SjMgnGwpq8 - Gabriel

0

如果你不介意使用Direct3D,你可以通过以下方式获取真实的显示模式分辨率(以像素为单位):

IDirect3D8.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, displayMode);

0

这是我如何绕过这个限制的方法:

Function GetScreenRect():TRect;
var
  canvas: TCanvas;
begin
  canvas := TCanvas.Create;
  canvas.Handle := GetDC(0);
  result := Canvas.ClipRect;
  ReleaseDC(0, canvas.Handle);
  canvas.Free;
end;

所以你不需要其他任何东西,只需插入我的代码,就可以在高DPI下获得真实的屏幕尺寸,问题就解决了。 - Sergei123

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