3

So, Delphi programs are not DPI aware. This didn't bother me much until recently when I needed the real screen resolution (Wrong resolution reported by Screen.Width when "Make it easier to read what's on screen" is 150%) in a computer with High DPI. Some recommended was to make the application High DPI aware (XML manifest) but others are warning us that this involves a lot of work! So, being lazy (or lacking time), I wonder if there is a trick to compute the real resolution.

One very dirty trick that cross my mind would be to create a companion tool (tiny console app) that is DPI aware. All I have to do then is to call this tool and obtain the real resolution from it. Pretty spartan but it should work. Anyway, there must be a nicer way to do it!

Community
  • 1
  • 1
Gabriel
  • 20,797
  • 27
  • 159
  • 293

3 Answers3

5

The Win32_DesktopMonitor WMI class will yield the information.

For instance, using code taken from here: Delphi7: Get attached monitor properties

{$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.

If for any reason WMI is not available then you need a separate DPI aware process to do the work. Which will also entail some IPC.

Another problem is that recent versions of Windows have changed behaviour for these WMI classes. You may need to use different WMI queries. See here: https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/138d387c-b222-4c9f-b3bb-c69ee890491c/problem-with-win32desktopmonitor-in-windows-8-platform?forum=windowsgeneraldevelopmentissues

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • This sounds like an wonderful solution. Unfortunately, I had bad experience with WMI that was not available on some computers. My program used before some WMI functions. I had to remove them in the end. If you do a search for 'WMI not available' in google you will see lots of reports. – Gabriel Oct 28 '15 at 17:33
  • @davidhefferman-I know why I got so many errors related to WMI: WMI is a service and is not on by default on some systems!!!!! https://www.youtube.com/watch?v=JgsHfc-4TUE – Gabriel Oct 28 '15 at 18:03
  • That doesn't chime with my experience of WMI – David Heffernan Oct 28 '15 at 18:10
  • @hikari see final paragraph – David Heffernan May 04 '16 at 11:20
  • 1
    Gave up on this. Made a helper exe with dpi-aware manifest instead, running it with main app's handle as param -> helper sends a message with w/h on l/w params, done. (faster than reading console output) – hikari May 05 '16 at 09:14
  • @hikari - I also use the same idea (external helper). Works like a charm. The only problem I have is that sometime the antivirus will let the main app to run but it will block the helper. But doesn't happen that often. Apparently users are disabling the WMI because it could be used by malware: https://www.youtube.com/watch?v=0SjMgnGwpq8 – Gabriel Sep 22 '17 at 13:10
0

This is how I got around this limitation:

Function GetScreenRect():TRect;
var
  canvas: TCanvas;
begin
  canvas := TCanvas.Create;
  canvas.Handle := GetDC(0);
  result := Canvas.ClipRect;
  ReleaseDC(0, canvas.Handle);
  canvas.Free;
end;
dummzeuch
  • 10,975
  • 4
  • 51
  • 158
Sergei123
  • 11
  • 1
  • So you don't need anything else, you insert my code, you get the true screen dimensions under HighDPI, the problem is solved. – Sergei123 Sep 05 '21 at 10:26
0

If you not hesitate to require Direct3D, you can use it to obtain the real display mode resolution in pixels via:

IDirect3D8.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, displayMode);
vimuth
  • 5,064
  • 33
  • 79
  • 116