WPF中彻底解决Win10屏幕125%、100%缩放布局导致截图错位的问题

问题重现

       2021年下半年公司拿下了一个大项目,其中为了打通现有收银系统和青蛙pro以及蜻蜓F4两种进行双通信,需要收银机安装一个收银插件,实时获取现有收银系统的预结算总金额,并将金额传送给蜻蜓以及青蛙Pro。为了尽快落地,我的第一个解决方案是使用图像识别,即OCR光学识别技术,实时截取支付金额区域,识别现有收银系统的支付金额,在收银系统和蜻蜓以及青蛙Pro的lot小程序已打通双向通信能力情况下,将预结算总金额发送到蜻蜓以及青蛙Pro的lot小程序引导用户进行扫码付款操作。

       WPF引入一种新的XAML语言来开发界面,使用XAML语言将界面开发以及后台逻辑开发很好的分开,降低了前后台开发的耦合度,后台逻辑开发基于.Net语言。与前端Vue、React等框架都是MVVM思想,决定再用WPF露一手。截图功能在Win7上没有出现问题,但是在Win10中发现如果屏幕125%、150%放大布局,截取的屏幕在可视区域内竟然只显示一半,而有部分超出了可视区域,导致想识别的区域无法截图。下图是正常截图情况:

WPF中彻底解决Win10屏幕125%、100%缩放布局导致截图错位的问题_第1张图片

下图是屏幕的截取部分超出了可视区域,底部任务栏都不见了:

在不重启截图程序情况下,设置屏幕100%缩放布局应该是正常的,但是祸不单行,又出现了新的问题:

WPF中彻底解决Win10屏幕125%、100%缩放布局导致截图错位的问题_第2张图片

正常100%缩放布局之后,截取的屏幕在可视区域下边和右边无缘无故多了黑框,真实一波未平一波又起。

分析原因

       经过在网上搜索了一番,大多提到是DpiX和DpiY变化引起,解决方案是通过监听这两个值的变化进行处理,比如System.Drawing.Graphics.DpiX或者System.Drawing.Graphics.DpiY、通过GetDeviceCaps获取逻辑Dpi等等,但是不能100%解决问题。我在两台都是win10机器上试了一下,在不重启插件,也不重启电脑情况下,改变屏幕缩放比例,发现有一台DpiX和DpiY是没有任何变化的,所以我个人认为,Dpi是不能完美解决问题的,而且围绕这个Dpi解决问题,反而增加了我们开发的难度,非常不划算。

      屏幕缩放布局设置下,Win系统会自动按比例缩放窗口的所有控件,包括控件布局、文本字体等等。通过对比了关于获取屏幕尺寸的方法,100%缩放布局结果如下:

WPF中彻底解决Win10屏幕125%、100%缩放布局导致截图错位的问题_第3张图片

 125%缩放布局如下:

WPF中彻底解决Win10屏幕125%、100%缩放布局导致截图错位的问题_第4张图片

我两台电脑真实尺寸是1920x1080,其中一台100%布局VirtualScreen和AllScreens[0].Bounds竟然不是1920x1080,而是2400x1350,而另一台却没问题。不过两台电脑GetDeviceCaps获取值无论如何布局都保持一致,PrimaryScreen获取的尺寸在125%布局下为1536x864,即\frac{1920}{1536}=\frac{1080}{864}=1.25,宽高比等比缩放。

 解决方法

通过两种结果对比和分析,只有PrimaryScreen根据缩放布局正常缩放,而GetDeviceCaps无论怎么缩放,它获取的就是实际的物理屏幕尺寸,使用这两个方法设置窗体的大小才是最精准的。截图窗体设置如下:

Left = Screen.PrimaryScreen.Bounds.Location.X; // 全屏截图窗体左上角坐标
Top = Screen.PrimaryScreen.Bounds.Location.Y;  // 全屏截图窗体左上角坐标
Width = SystemParameters.PrimaryScreenWidth; 
Height = SystemParameters.PrimaryScreenHeight; 

这样设置,我们截图窗体就能跟我们看到跟win系统上的其他程序一样正常缩放。那截取屏幕如何绘制到截图窗体上呢?且正常显示呢?直接贴代码段吧,看代码一目了然:

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

private const int DESKTOPVERTRES = 117;
private const int DESKTOPHORZRES = 118;

// 获取真是屏幕宽高
public static System.Drawing.Size GetScreenByDevice()
{
      IntPtr hDc = GetDC(IntPtr.Zero);
     return new System.Drawing.Size()
    {
        Width = GetDeviceCaps(hDc, DESKTOPHORZRES),
        Height = GetDeviceCaps(hDc, DESKTOPVERTRES)
    };
}
// 截取屏幕
public static Bitmap CaptureScreenSnapshot()
{
    //using (Graphics currentGraphics = Graphics.FromHwnd(IntPtr.Zero))
    //{
    //    Console.WriteLine("dpiXRatio={0}", currentGraphics.DpiX / 96);
    //}
    System.Drawing.Size sysize = GetScreenByDevice();
    Bitmap background = new Bitmap(sysize.Width, sysize.Height);
    using (Graphics gcs = Graphics.FromImage(background))
    {
        gcs.CopyFromScreen(0, 0, 0, 0, sysize, CopyPixelOperation.SourceCopy); 
// Screen.AllScreens[0].Bounds.Size, CopyPixelOperation.SourceCopy);
     }
     return background;
}

其实我们所看到的屏幕是逻辑放大,但真实屏幕大小没有发生变化,GetDeviceCaps可以获取真实屏幕Width和Height值,并使用CopyFromScreen截取真实尺寸大小,给截图窗口的Background属性设置背景图,也就是将屏幕截图映射到随布局缩放的窗体上面。

但是问题又来了,屏幕截图展示正常了,但是截取某个区域的时候,竟然发现截图区域错位了。如下面左边的图预截取的区域有文本,但是截图之后,竟然啥都没有了。原因还是一样,截取的矩形是截取窗体的一个控件,区域坐标是按照125%放大之后的进行计算,但是截图矩形区域转换成Bitmap对象之后其坐标是相对于真实屏幕来衡量,导致截取矩形区域坐标错位。

WPF中彻底解决Win10屏幕125%、100%缩放布局导致截图错位的问题_第5张图片WPF中彻底解决Win10屏幕125%、100%缩放布局导致截图错位的问题_第6张图片

最后解决方案如下代码段:

private Rect GetIndicatorRegion()
{
    //using (System.Drawing.Graphics currentGraphics = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))
    //{
    //    double currentDpiX = currentGraphics.DpiX;
    //    double dpiXRatio = currentDpiX / 96;
    //    //MessageBox.Show(string.Format("原始:{0}.{1}.{2}.{3}", GetLeft(indicator), GetTop(indicator), indicator.ActualWidth, indicator.ActualHeight));
    //    return new Rect(GetLeft(indicator)* dpiXRatio, GetTop(indicator) * dpiXRatio, indicator.ActualWidth * dpiXRatio, indicator.ActualHeight* dpiXRatio);
    //}
    System.Drawing.Size dsize = ComHelper.GetScreenByDevice();
    double scaleX = dsize.Width / SystemParameters.PrimaryScreenWidth;
    double scaleY = dsize.Height / SystemParameters.PrimaryScreenHeight;
    //return new Rect(0, 0, 20, 20);
    return new Rect(GetLeft(indicator) * scaleX,
        GetTop(indicator) * scaleY,
        indicator.ActualWidth * scaleX,
        indicator.ActualHeight * scaleY);
}

这样处理之后,我把屏幕设置100%、125%甚至150%布局,在两台win10上问题就得到解决了,同时win7上也能正常使用。 

总结

  • 网上解决方案大多很相似,不知道是否有深入研究过,也不一定能帮你解决所有的问题,还得自己会分析问题,形成一套自己的思考解决思路。
  • 虽然看似一个小小的功能,但B端客户使用插件的还是很多,而且经过使用一段时间后,对插件产生了依赖,因为能帮助他们快速收银。当然,可能会有更好的解决方案,只能在后面不断的探索中解决和完善。

你可能感兴趣的:(WPF,wpf,c#)