Как я могу получить правильный DPI моего экрана в WPF?

Это может звучать как двойной вопрос, но я обещаю, что это не так. Я уже посмотрел ответы на другие вопросы, например, по следующей ссылке:

Как я могу получить DPI в WPF?

Проблема в том, что они не возвращают правильный DPI.

Я точно знаю, что мой монитор (Dell U3415W) имеет DPI 109 точек на дюйм. Разрешение 3440x1440.

В WPF я попытался использовать следующие методы для получения DPI моего экрана:

//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;
}

Все три из вышеперечисленных методов возвращают 192 в качестве моего DPI (масштабный коэффициент, возвращаемый в методах #1 и #3, равен 2).

Я пишу код, в котором мне нужно надежно отображать расстояния (в физических единицах, например, сантиметрах) между некоторыми объектами на экране, и этот код будет не просто запущен на моем экране, поэтому я не могу просто жестко кодировать "109" в Это.

В связи с этим я озадачен тем, что кажется экземпляром WPF, использующим реальные пиксели вместо независимых от устройства пикселей.

У меня есть следующий XAML, объявляющий окно с простой сеткой и прямоугольником внутри сетки:

<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>

В коде позади у меня есть код, который устанавливает некоторые свойства этого прямоугольника:

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

Когда мой прямоугольник появляется на экране, он имеет размер 4,4 см х 3,4 см. WPF говорит, что 1 независимый от устройства пиксель равен 1/96 дюйма, поэтому я бы предположил, что 96 dip составляет 1 дюйм (что составляет 2,54 см). Поэтому наклон 189,5625 должен составлять 1,9746 дюйма (или 5,01 см). Тем не менее, похоже, что он не использует dip в этом случае. Если мы вставим фактическое разрешение моего монитора (109 точек на дюйм) в уравнение, мы получим фактический размер отображаемого прямоугольника:

189,5625 пикселей / 109 точек на дюйм = 1,7391 дюйма (или 4,4 см)

Тем не менее, в документации WPF говорится, что свойства Width и Height используют независимые от устройства пиксели:

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

Итак, сделаем вывод:

(1) Почему все принятые методы запроса DPI не возвращают мне правильный DPI?

(2) Почему кажется, что когда я устанавливаю размер прямоугольника, он интерпретирует этот размер в единицах пикселей, а не в независимых от устройства пикселях?

Спасибо за любую помощь!!!

2 ответа

Это старый вопрос, но вы можете получить физические характеристики монитора из Windows.Devices.Display.DisplayMonitor.

Подготовьтесь к использованию WinRT, затем вызовите метод Windows.Devices.Enumeration.DeviceInformation.FindAllAsync .

      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}");
    }
}

В случае Dell U2415 этот код выдает следующее:

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

Сделайте расчет по своему усмотрению.

Хотя Windows знает разрешение монитора, на самом деле она не знает его физического размера. И, таким образом, у него нет средств для определения фактического физического DPI. Windows просто предполагает, что все экраны имеют разрешение 96 dpi. Если пользователь выбирает коэффициент масштабирования в настройках дисплея, Windows настраивает системное разрешение на дюйм на основе этого.

Для вашего приложения лучше всего иметь страницу калибровки, где оно отображает линию на экране и просит пользователя измерить ее с помощью линейки и ввести измерение. Это даст вам достаточно информации, чтобы выяснить фактическое значение dpi на экране. И технически вам нужно сделать это как по горизонтали, так и по вертикали, поскольку DPI в каждом направлении теоретически могут быть разными. (Но я думаю, что на практике они почти всегда одинаковы.)

Другие вопросы по тегам