【问题标题】:Accurately determine DPI in Windows (10)准确判断Windows中的DPI(10)
【发布时间】:2017-07-16 05:26:10
【问题描述】:

我有我认为(技术上)正确的代码,可以在 Windows 10 中获取显示器的 DPI。

[DllImport("shcore.dll")]
static extern UInt32 GetDpiForMonitor(IntPtr hmonitor,
                                      int dpiType,
                                      out UInt32 dpiX,
                                      out UInt32 dpiY);

[DllImport("user32.dll")]
static extern IntPtr MonitorFromWindow(IntPtr hwnd, UInt32 dwFlags);
...
var whdl = Process.GetCurrentProcess().MainWindowHandle;
var mhdl = MonitorFromWindow(whdl, 0);

GetDpiForMonitor(mhdl, 2, out DpiX, out DpiY); // 255, 256

var ct = PresentationSource.FromVisual(MainImage).CompositionTarget;
var scaleX = ct.TransformToDevice.M11;
var scaleY = ct.TransformToDevice.M22;

var pixelWidth = (int)(GridImage.ActualWidth * DpiX / 96.0);
var pixelHeight = (int)(GridImage.ActualHeight * DpiY / 96.0);

writeableBitmap = new WriteableBitmap(pixelWidth, 
                                      pixelHeight,
                                      DpiX,
                                      DpiY,
                                      System.Windows.Media.PixelFormats.Bgr32,
                                      null);

我使用 17 英寸(笔记本电脑)4K 面板(3840x2160,缩放比例为 250%)对此进行了测试。我使用了一个简单的 WPF 网格控件,其中包含一个图像控件。来自GetDpiForMonitor 的 DPI 是 (X:Y) 255:256。问题是这不准确。当我绘制一个简单的网格(线 DPI 分开)时,正方形不是 1",它们短 17(原始)像素。 WriteableBitmap 位于 WPF Image 控件内,并且该控件具有 Stretch="None"

任何人都知道为什么这些值不准确或如何获得准确的值。有没有办法获得显示器的分辨率和尺寸(以便我可以计算 DPI)?

更新

这个问题被否决了,我仍然没有答案,但我发现这个小谜题既有趣又令人讨厌(“互动”、“令人讨厌”)。无论如何,这里有一些额外的信息发送到未来的任何人遇到它。

首先,这是一台运行 Windows 10 Pro Build 15063.483 的 HP Envy 4K 笔记本电脑 (W2K87UA#ABA)。使用我查询系统监视器尺寸的 GetDevCaps

[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

private const int HORZSIZE = 4;
private const int VERTSIZE = 6;
private const double MM_TO_INCH_CONVERSION_FACTOR = 25.4;
...
var hDC = System.Drawing.Graphics.FromHwnd(whdl).GetHdc();
int horizontalSizeInMilliMeters = GetDeviceCaps(hDC, HORZSIZE);
double horizontalSizeInInches = horizontalSizeInMilliMeters / MM_TO_INCH_CONVERSION_FACTOR;
int vertivalSizeInMilliMeters = GetDeviceCaps(hDC, VERTSIZE);
double verticalSizeInInches = vertivalSizeInMilliMeters / MM_TO_INCH_CONVERSION_FACTOR;

这将返回以下内容:

horizontalSizeInMilliMeters: 382
horizontalSizeInInches: 15.039370078740159
Measured by hand: 15 1/32 "      15.0625"   382.6 mm

vertivalSizeInMilliMeters: 214
verticalSizeInInches: 8.4251968503937018
Measured by hand:  8 15/32"       8.46875   215.1 mm

这一切都是 255.329842931937 的 X dpi 和 255.329842931937 的 Y dpi 256.373831775701,或大约 (255, 256),这正是 GetDpiForMonitor 返回的值。

使用 WriteableBitmap,我使用以下代码在屏幕上绘制了一个网格:

color = Colors.White;
int color_data = (color.R << 16)
               | (color.G << 8)
               | color.B;
dpiy = (int) DpiY;
for (var y = 0; y < height; y += dpiy) {
    for (var x = 0; x < width; x++) {
        var p = BackBuffer + (y * Stride) + (x * 4);
        unsafe {
            *((int*)p) = color_data;
            }
        }
    }
dpix = (int) DpiX;
for (var x = 0; x < width; x += dpix) {
    for (var y = 0; y < height; y++) {
        var p = BackBuffer + (y * Stride) + (x * 4);
        unsafe {
            *((int*)p) = color_data;
            }
        }
    }

当我测量网格线时,我发现我需要在两种方式中添加大约 17 个像素才能获得 1" 单位正方形。如果我的“真实”DPI 是 272 (255 + 17),那么我的屏幕的实际宽度会为 14.1176470588235" (3840/272)。

出于好奇,我打开 Word 2016 将比例设置为 100% 并测量 Word“标尺”工具。它也关闭了 ~15/16" 而不是 1",我必须缩放到 105% 到 106% 之间才能获得“真实”英寸。因此,无论是什么问题,它都会影响 MS 编写的应用程序。

【问题讨论】:

  • 你的代码没有检查GetDpiForMonitor的返回值。文档说明它可以返回 E_INVALIDARG 并且因为它们是 out 参数,所以它们的值在错误条件下是没有意义的,这解释了您看到的 255 值。
  • 从 WPF 进程中调用 GetDpiForMonitor 可能无法按预期工作,因为函数的行为取决于调用进程的“意识”级别(而 WPF 喜欢对此进行干扰.. . 特别是如果您的用户使用的是较旧的 .NET Framework 版本)。您应该从不创建窗口并使用最新的 Windows 应用程序清单声明的辅助进程间接调用它。
  • @Dai,好主意,我检查了一下,GetDpiForMonitor 返回 0。此代码使用 4.5.2 框架。
  • 请发布您对GetDpiForMonitor 和其他导入函数的P/Invoke 声明,并且在发布代码时请指定完整的类型名称而不是var(我们无法将鼠标悬停在标识符上以查看其在浏览器中输入)。
  • 我更新了代码以显示导入。

标签: c# wpf


【解决方案1】:

自 .NET Framework 4.6.2 起,WPF 支持每个显示器的 DPI。 GitHub 上提供了更多信息和示例:http://github.com/Microsoft/WPF-Samples/tree/master/PerMonitorDPI

您可能还想查看VisualTreeHelper.GetDpi 方法。

【讨论】:

  • 谢谢,我尝试了 VisualTreeHelper.GetDpi,但它返回 240x240,与 MDT_EFFECTIVE_DPI (0) 监视器类型的 GetDpiForMonitor 相同。
  • 您是否在应用程序清单文件中为您的应用程序启用了每个监视器 DPI 并在您的 App.Config 中打开了 WPF 功能?
  • 我做了第一个 (app.manifest),但由于我的目标是 4.6.2 框架,所以我没有更改 app.config。
猜你喜欢
  • 2017-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-30
  • 2018-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多