2

This may sound like a duplicate question, but I promise it is not. I have already looked at the answers provided in other questions, such as the one at the following link:

How can I get the DPI in WPF?

The problem is, they are not returning the correct DPI.

I know for a fact that my monitor (Dell U3415W) has a DPI of 109 ppi. The resolution is 3440x1440.

In WPF, I have attempted the following methods to get the DPI of my screen:

//Method 1
var dpi_scale = VisualTreeHelper.GetDpi(this);
double dpiX = dpi_scale.PixelsPerInchX;
double dpiY = dpi_scale.PixelsPerInchY;

//Method 2
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))
{
    double dpiX = g.DpiX;
    double dpiY = g.DpiY;
}

//Method 3
PresentationSource source = PresentationSource.FromVisual(this);
double dpiX, dpiY;
if (source != null)
{
    dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
    dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;
}

All three of the above methods return 192 as my DPI (The scale factor being returned in methods #1 and #3 is 2).

I am writing code in which I need to reliably display the distances (in physical units such as centimeters) between some objects on the screen, and this code will not just be running on my screen so I can't just hardcode "109" into it.

On a related note, I am mystified by what seems to be an instance of WPF using actual pixels instead of device-independent pixels.

I have the following XAML declaring a window with a simple grid and a rectangle inside of the grid:

<Window x:Class="MyTestWindow.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Height="768" 
        Width="1024">
    <Grid x:Name="MainObjectLocationGrid">
        <Rectangle x:Name="MainObjectLocationRectangle" HorizontalAlignment="Left" VerticalAlignment="Top" />
    </Grid> 
</Window>

In the code-behind, I have some code that sets some properties on that rectangle:

MainObjectLocationRectangle.Width = 189.5625;
MainObjectLocationRectangle.Height = 146.4;
MainObjectLocationRectangle.Fill = new SolidColorBrush(Colors.White);
MainObjectLocationRectangle.Stroke = new SolidColorBrush(Colors.Transparent);
MainObjectLocationRectangle.StrokeThickness = 0;
MainObjectLocationRectangle.Margin = new Thickness(0, 0, 0, 0);

When my rectangle appears on the screen, the rectangle is 4.4cm x 3.4cm in size. WPF says that 1 device-independent pixel is 1/96th of an inch, so I would assume that 96 dip is 1 inch (which is 2.54 cm). Therefore 189.5625 dip should be 1.9746 inches (or 5.01 cm). Yet it seems that it isn't using dip in this instance. If we insert my monitor's actual resolution (109 dpi) into the equation, we get the actual size of the rectangle being displayed:

189.5625 pixels / 109 dpi = 1.7391 inches (or 4.4 cm)

Yet in the WPF documentation it states that the Width and Height properties use device-independent pixels:

https://learn.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.width?view=netframework-4.7.2#System_Windows_FrameworkElement_Width

So, to conclude:

(1) Why are all the accepted methods for querying the DPI not returning the correct DPI to me?

(2) Why does it seem like when I set the size of the rectangle, it is interpreting that size in units of pixels rather than device-independent pixels?

Thanks for any help!!!

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
David
  • 1,847
  • 4
  • 26
  • 35
  • It has to be the least understood number in computing. Not directly based on hardware, it is a software setting that is quickly and easily changed with Control Panel > Display. You pick a number that makes it easy enough to read the text on the screen. Which is primarily based on the location of your chair, only secondarily based on the size and resolution of the panel. The further away, the higher the number needs to be. You need to allow your user to enter a tweak parameter. – Hans Passant Feb 13 '19 at 08:21
  • 1
    @David when you say "I know for a fact that my monitor (Dell U3415W) has a DPI of 109 ppi", how did you determine this? Physical measurement? Calculation? Dell Spec Sheet? Windows can only know the DPI from what is reported to it by the Monitor driver and even then does some estimation. It probably does not know the actual physical size of the screen. – Jeff R. Feb 20 '19 at 16:40

2 Answers2

1

While Windows knows the resolution of the monitor, it doesn't appear to actually know the physical size of the monitor. And thus it has no means of determining the actual physical DPI. Windows simply assumes all screens are 96 dpi. If the user selects a scale factor in display settings, Windows adjusts the system dpi based upon that.

It is best for your app to have a calibration page, where it displays a line on the screen, and asks the user to measure it with a ruler and input the measurement. That would give you enough information to figure out the actual dpi of the screen. And technically, you'd need to do this both horizontally and vertically as the DPIs in each direction could, in theory, be different. (But I think in practice, they are almost always the same.)

1

This is old question but you can get the physical specifications of a monitor from Windows.Devices.Display.DisplayMonitor.

Prepare to use WinRT, then call Windows.Devices.Enumeration.DeviceInformation.FindAllAsync method.

using Windows.Devices.Display;
using Windows.Devices.Enumeration;

public async Task CheckMonitors()
{
    const string deviceInstanceIdKey = "System.Devices.DeviceInstanceId";

    DeviceInformationCollection devices = await DeviceInformation.FindAllAsync(DisplayMonitor.GetDeviceSelector(), new[] { deviceInstanceIdKey });
    foreach (DeviceInformation? device in devices)
    {
        DisplayMonitor? monitor = await DisplayMonitor.FromInterfaceIdAsync(device.Id);
        if (monitor is null)
            continue;

        Debug.WriteLine($"DeviceInstanceId: {device.Properties[deviceInstanceIdKey]}");
        Debug.WriteLine($"DisplayName: {monitor.DisplayName}");
        Debug.WriteLine($"PhysicalSizeInInches: {monitor.PhysicalSizeInInches}");
        Debug.WriteLine($"RawDpiX: {monitor.RawDpiX}");
        Debug.WriteLine($"RawDpiY: {monitor.RawDpiY}");
        Debug.WriteLine($"NativeResolutionInRawPixels: {monitor.NativeResolutionInRawPixels.Width},{monitor.NativeResolutionInRawPixels.Height}");
    }
}

In the case of Dell U2415, this code produces the following:

DisplayName: DELL U2415
PhysicalSizeInInches: 20.393702,12.755906
RawDpiX: 94.14671
RawDpiY: 94.074066
NativeResolutionInRawPixels: 1920,1200

Make a calculation as you wish.

emoacht
  • 2,764
  • 1
  • 13
  • 24