2021年下半年公司拿下了一个大项目,其中为了打通现有收银系统和青蛙pro以及蜻蜓F4两种进行双通信,需要收银机安装一个收银插件,实时获取现有收银系统的预结算总金额,并将金额传送给蜻蜓以及青蛙Pro。为了尽快落地,我的第一个解决方案是使用图像识别,即OCR光学识别技术,实时截取支付金额区域,识别现有收银系统的支付金额,在收银系统和蜻蜓以及青蛙Pro的lot小程序已打通双向通信能力情况下,将预结算总金额发送到蜻蜓以及青蛙Pro的lot小程序引导用户进行扫码付款操作。
WPF引入一种新的XAML语言来开发界面,使用XAML语言将界面开发以及后台逻辑开发很好的分开,降低了前后台开发的耦合度,后台逻辑开发基于.Net语言。与前端Vue、React等框架都是MVVM思想,决定再用WPF露一手。截图功能在Win7上没有出现问题,但是在Win10中发现如果屏幕125%、150%放大布局,截取的屏幕在可视区域内竟然只显示一半,而有部分超出了可视区域,导致想识别的区域无法截图。下图是正常截图情况:
下图是屏幕的截取部分超出了可视区域,底部任务栏都不见了:
在不重启截图程序情况下,设置屏幕100%缩放布局应该是正常的,但是祸不单行,又出现了新的问题:
正常100%缩放布局之后,截取的屏幕在可视区域下边和右边无缘无故多了黑框,真实一波未平一波又起。
经过在网上搜索了一番,大多提到是DpiX和DpiY变化引起,解决方案是通过监听这两个值的变化进行处理,比如System.Drawing.Graphics.DpiX或者System.Drawing.Graphics.DpiY、通过GetDeviceCaps获取逻辑Dpi等等,但是不能100%解决问题。我在两台都是win10机器上试了一下,在不重启插件,也不重启电脑情况下,改变屏幕缩放比例,发现有一台DpiX和DpiY是没有任何变化的,所以我个人认为,Dpi是不能完美解决问题的,而且围绕这个Dpi解决问题,反而增加了我们开发的难度,非常不划算。
屏幕缩放布局设置下,Win系统会自动按比例缩放窗口的所有控件,包括控件布局、文本字体等等。通过对比了关于获取屏幕尺寸的方法,100%缩放布局结果如下:
125%缩放布局如下:
我两台电脑真实尺寸是1920x1080,其中一台100%布局VirtualScreen和AllScreens[0].Bounds竟然不是1920x1080,而是2400x1350,而另一台却没问题。不过两台电脑GetDeviceCaps获取值无论如何布局都保持一致,PrimaryScreen获取的尺寸在125%布局下为1536x864,即,宽高比等比缩放。
通过两种结果对比和分析,只有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对象之后其坐标是相对于真实屏幕来衡量,导致截取矩形区域坐标错位。
最后解决方案如下代码段:
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上也能正常使用。