WinForm响应式布局规划执行

引言

始建响应式WinForm应用程序并不那么简单。
响应式布局,在此我指的是应用程序在不同屏幕分辨率下的可用性。
对于WinForm应用程序,大家需要明确地依据分辨率来调整控件的大大小小和再度定位。
即便在使用WPF时有相关的实施应用,通过应用控件的docking和anchoring,或利用panels等方法,但本文提供了一种将响应式应用于WinForm应用程序的两样方法。

如今开销的具备代码都早已上传到了GitHub。欢迎我们来Star

背景

自家在一个和谐计划的简单游戏中相遇了问题:我设计了一台分辨率为1920×1080的机器,
然而当自身打算在台式机电脑上广播时,发现应用程序边界跑到屏幕之外。由此很有必要让程序来适应不同分辨率的设备,而不是让用户来适应程序。
由此,我对代码举办了立异。

https://github.com/GiantLiu/AutoJump

技术

事实上没什么技术可言,只是用了一个小技巧。我们用两个常量来保存设计时的屏幕分辨率,我们称为设计时分辨率。这样,无论几时运行应用程序,它都会博得一个乘法因子,这实际上是一个比例因子,通过将眼前分辨率除以设计时分辨率来博取该因子。
窗体的具备控件都被传送给这多少个类对象开展缩放和调整大小。

方今程序分成“全自动版本”和“半活动版本”

代码

自行版本

The Responsive Class – Responsive.cs

始建一个类Responsive.cs,添加5个变量。

float WIDTH_AT_DESIGN_TIME = (float)Convert.ToDouble
                             (ConfigurationManager.AppSettings["DESIGN_TIME_SCREEN_WIDTH"]);
float HEIGHT_AT_DESIGN_TIME = (float)Convert.ToDouble
                              (ConfigurationManager.AppSettings["DESIGN_TIME_SCREEN_HEIGHT"]);
Rectangle Resolution;
float WidthMultiplicationFactor;
float HeightMultiplicationFactor;

设计时屏幕分辨率保存在App.config文件中。

<add key ="DESIGN_TIME_SCREEN_WIDTH" value="1920"/>
<add key ="DESIGN_TIME_SCREEN_HEIGHT" value="1080"/>

当类的一个实例被创立时,当前的解析被提供给构造函数。
之后调用该类的SetMultiplicationFactor()方法。
这种措施通过将近日分辨率除以设计时间分辨率来取得缩放因子。

public Responsive(Rectangle ResolutionParam)
{
    Resolution = ResolutionParam;
}

public void SetMultiplicationFactor()
{
    WidthMultiplicationFactor = Resolution.Width / WIDTH_AT_DESIGN_TIME;
    HeightMultiplicationFactor = Resolution.Height / HEIGHT_AT_DESIGN_TIME;
}

例如,该应用程序设计在1920×1080分辨率。
倘使此应用程序在分辨率为1024×768的微机上运行,则WidthMultiplicationFactor和HeightMultiplicationFactor更改如下:

WidthMultiplicationFactor = 1024/1920 = 0.533
HeightMultiplicationFactor = 768/1080 = 0.711

末尾有三种重载方法,它们为应用程序控件提供响应式解决方案(最佳大小,地方和字体大小)的终极方法。

public int GetMetrics(int ComponentValue)
{
    return (int)(Math.Floor(ComponentValue * WidthMultiplicationFactor));
}

public int GetMetrics(int ComponentValue, string Direction)
{
    if (Direction.Equals("Width") || Direction.Equals("Left"))
        return (int)(Math.Floor(ComponentValue * WidthMultiplicationFactor));
    else if (Direction.Equals("Height") || Direction.Equals("Top"))
        return (int)(Math.Floor(ComponentValue * HeightMultiplicationFactor));
    return 1;
}

譬如,假设存在宽度=465,低度=72,左=366,顶部=41和字体大小=40的控件,则该措施再次来到提议的轻重,地点和字体大小为:

Width = 465 * 0.533 = 248
Height = 72 * 0.711= 51
Left = 366 * 0.533= 195
Top = 41 * 0.711= 29
Font-size = 40 * 0.533 = 21

实质上,这多少个主意重返缩放的控件与大小、地方和字体大小,而这一个值是彰显的最佳值。

WeChat.AutoJump.CMDApp

使用 Responsive Class

俺们需要的是以任何索要响应的样式简单地成立这么些类的靶子。
当前的分辨率是在构造函数中提供的, 之后的办事就是确立所需的乘法因子。

Responsive ResponsiveObj;
ResponsiveObj = new Responsive(Screen.PrimaryScreen.Bounds);
ResponsiveObj.SetMultiplicationFactor();

在这之后,表单的享有控件都将逐个传递,以在表单的加载事件中调整大小和另行定位。
这一个调用在下面的代码中成功。 它所做的是首先将窗体定位到屏幕的中坚。
我在那边设置了一个校准常数(30),为最佳的垂直地方添加控件,这或许因开发人员而异。
之后,表单的每一个控件都会重新定位,调整大小,不分轩轾复校准字体大小。

private void ResponsiveForm_Load(object sender, EventArgs e)
{
    Width = ResponsiveObj.GetMetrics(Width, "Width");           // Form width and height set up.
    Height = ResponsiveObj.GetMetrics(Height, "Height");
    Left = Screen.GetBounds(this).Width / 2 - Width / 2;        // Form centering.
    Top = Screen.GetBounds(this).Height / 2 - Height / 2 - 30;  // 30 is a calibration factor.

    foreach (Control Ctl in this.Controls)
    {
        Ctl.Font = new Font(FontFamily.GenericSansSerif, 
                   ResponsiveObj.GetMetrics((int)Ctl.Font.Size), FontStyle.Regular);
        Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
        Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
        Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
        Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
    }
}

当手机总是好后,打开微信跳一跳

示例

以下是一个分外简单的表单,其中含有一个data
gird,一个label,一个textbox和一个button。
下边的图纸以二种不同的分辨率截取。
上边的截图是在1920×1080分辨率下截取的:
图片 1

下边的截图是在1360×768分辨率下截取的:
图片 2

上边的截图是在1024×768分辨率下截取的:
图片 3

其实,通过压缩/扩大和重新定位控制到极品水平,Form在不同的分辨率下看起来是均等的。

点击”开首游戏”后。运行此程序。就可以实现活动跳了

代码调整

就像大家对垂直中央定位所做的这样,我们可能需要安装有些参数来调整总体布局。

其余,提议开发者尝试以不同的分辨率查看表单的外观,以确认所有的控件都是可见的,并按照预期在屏幕上科学定位。

除此之外,对于一个简便的表单,这是一个通用的措施,它假若表单的有所控件都有着那些属性—宽度,低度,左边,顶部和字体大小。不过,真实情状并非如此。有一对表单控件不具有所有那个属性。例如,图片框没有font-size属性。因而,如若如此的动静下没有明了处理,运行代码将会招致运行时特别。本文目的在于介绍这种措施,开发人员需要基于实际情形举行校准。提议的主意如下:

private void ResponsiveForm_Load(object sender, EventArgs e)
{
    Width = ResponsiveObj.GetMetrics(Width, "Width");           // Form width and height set up.
    Height = ResponsiveObj.GetMetrics(Height, "Height");
    Left = Screen.GetBounds(this).Width / 2 - Width / 2;        // Form centering.
    Top = Screen.GetBounds(this).Height / 2 - Height / 2 - 30;  // 30 is a calibration factor.

    foreach (Control Ctl in this.Controls)
    {
        if (Ctl is PictureBox)
        {
            Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
            Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
            Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
            Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
        }
        else
        {
            Ctl.Font = new Font(FontFamily.GenericSansSerif, 
                                ResponsiveObj.GetMetrics((int)Ctl.Font.Size), FontStyle.Regular);
            Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
            Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
            Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
            Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
        }
    }
}

兴许会按照业务员需要和控件的特性来调整代码。
另外,可能需要为不同的控件类型引入更多的重载方法。

半活动版本

其他

如前所述,还有此外一些措施,例如利用WPF,使用anchoring/docking等,这是一个更智慧的选项。
假若表单上有数千个控件,则可能会遇见加载延迟。
然则,那点延迟对明日运行高效的处理器来说不成问题。
那种情势只是在表单的加载时才实施四回调用操作,由此不会带来沉重的习性降低的题材。

WeChat.AutoJump.WinApp

结尾

创办响应式WinForm应用程序,依据机器的运转时刻分辨率自动调整大小,重新定位字体大小不分畛域新校准字体大小,这是一种面向开发人士的形式。
只需将该类添加到花色中,在App.config文件中设置规划时分辨率,然后在窗体的加载事件中充足响应代码。
So easy!

此版本需要鼠标左键点小黑人的底层,鼠标右键点目的位的主干

下一场程序就会活动跳到相应的职位

 

先后原理
1。将手机点击到《跳一跳》小程序界面;点击“先河游戏”后
2。用Adb工具得到当前手机的截图,半下载到本地
3.1。即便是活动版本,那么快要用鼠标左右键来点击先河和对象地点
接下来程序会自行算出要跳动的距离与要点击屏幕的时间。
3.2。假诺是半自动版本,那么程序会自动算出小黑人的地点与对象的中坚点,
下一场自动算距离与点击屏幕的年华。

4。用Adb工具向无绳电话机发送点击屏幕蓄力命令,完成两回跳动

当下程序只好补助Android设备,IOS设备只写了接口,还未曾实现
步骤:

  • 安卓手机打开USB调试,设置》开发者选项》USB调试
  • 电脑与手机USB线连接,确保实施adb devices可以找到设备id

**

  • 界面转至微信跳一跳游戏,点击起初游戏
    运转活动/半机动版本程序,就足以开始游戏之路

    图片 4

  •  

代码关键实现
1。通过adb拿到手机的屏幕截图,其实就是向手机发送有关的通令

  第一条命令是把屏幕的截图以png格式保存到手机SD卡
  第二条命令是把手机SD卡里面的图片下载到本地硬盘对应的目录
  第三条命令是把手机里的截图删除
  第四条命令是殡葬屏幕按压命令
从X:100,Y:100以此地方向X200,Y:200以此岗位移动,其中时间为500飞秒

adb shell screencap -p /sdcard/1.png
adb pull /sdcard/1.png D:/Download/
adb shell rm /sdcard/1.png
adb shell input swipe 100 100 200 200 500

那里是.net发送命令相关代码

图片 5图片 6

public string AdbCommand(string arg)
        {
            using (Process process = new Process())
            {
                var adbDirectoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AndoridAdb");
                var adbPath = Path.Combine(adbDirectoryPath, "adb.exe");
                process.StartInfo.FileName = adbPath;
                process.StartInfo.Arguments = arg;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardInput = true;   //重定向标准输入   
                process.StartInfo.RedirectStandardOutput = true;  //重定向标准输出   
                process.StartInfo.RedirectStandardError = true;   //重定向错误输出
                process.StartInfo.CreateNoWindow = true;
                process.Start();
                var result = process.StandardOutput.ReadToEnd();
                process.WaitForExit();
                process.Close();
                return result;
            }
        }

View Code

2。假设是机关版本,那么要先鼠标左键点小黑人的底部,然后鼠标右键点目的地点的中游。
点完右键后。程序会自行算出两点期间相距与时间。然后就跳一步了。这一个从未什么技巧问题

3。淌假使半自动版本,这反第一步,你拿到屏幕截图后。要分析出小黑人的职务
自家这边的话。就用了EmguCV (OpenCV的.net调用)。
我们得以用到OpenCV的沙盘匹配。MatchTemplate方法
模板的话。随便找一张屏幕截图,用PS把小黑人扣出来。保存为图片就可以了
MatchTemplate会找出配合最高的点。然后交给坐标,这样,我们就能够算出小黑人的中坚地方了

图片 7图片 8

var tempGrayPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Template", "Current.png");

            var tempGrayImg = new Image<Rgb, byte>(tempGrayPath);

            var match = img.MatchTemplate(tempGrayImg, TemplateMatchingType.CcorrNormed);

            double min = 0, max = 0;
            Point maxp = new Point(0, 0);//最好匹配的点
            Point minp = new Point(0, 0);
            CvInvoke.MinMaxLoc(match, ref min, ref max, ref minp, ref maxp);
            Console.WriteLine(min + " " + max);
            CvInvoke.Rectangle(img, new Rectangle(maxp, new Size(tempGrayImg.Width, tempGrayImg.Height)), new MCvScalar(0, 0, 255), 3);

            var startPoint = new Point();
            startPoint.X = maxp.X + (int)(tempGrayImg.Width / 2.0);
            startPoint.Y = maxp.Y + tempGrayImg.Height - 2;
            CvInvoke.Rectangle(img, new Rectangle(startPoint, new Size(1, 1)), new MCvScalar(0, 0, 0), 3);

View Code

图片 9

4。目的地方总括,
这也是程序最复杂的有的了,
本人的落实步骤为
1:先把图纸裁剪到只保留中间的1/3行之有效分析区域
2:看小黑人在屏幕的右边仍然右手,那么目的就会在相反的区域。这样我们就足以把目的区域的图纸剪切下来

图片 10图片 11

////裁剪查找区域
            ////原图片1/3以下,小黑人以上
            var newImgStart = imgHeightSplit;
            var newImgEnd = maxp.Y + tempGrayImg.Height;
            var newImgHeight = newImgEnd - newImgStart;
            Rectangle rect = new Rectangle(0, newImgStart, img.Width, newImgHeight);

            CvInvoke.cvSetImageROI(sourceImg, rect);
            var newImg = new Image<Rgb, byte>(sourceImg.Width, newImgHeight);
            CvInvoke.cvCopy(sourceImg, newImg, IntPtr.Zero);



            ////看小黑人在程序的左边还是右边
            ////如果在左边,那目标点就在图片的右边
            bool targetInLeft = true;
            if (maxp.X < imgWidthCenter) targetInLeft = false;

            Rectangle halfRect;
            if (targetInLeft)
                halfRect = new Rectangle(0, 0, imgWidthCenter, newImgHeight);
            else
                halfRect = new Rectangle(imgWidthCenter, 0, imgWidthCenter, newImgHeight);

            CvInvoke.cvSetImageROI(newImg, halfRect);
            var halfImg = new Image<Rgb, byte>(imgWidthCenter, newImgHeight);
            CvInvoke.cvCopy(newImg, halfImg, IntPtr.Zero);

View Code

图片 12

 

5。然后大家透过像素分析,找到对象的终端
规律是:第一个点与后一个点相比,看变化大小
假若生成大小超越一个值。就认为是目标位了(跳一跳背景是潜移默化的)
这里是方块点。顶点就是一个点。当固然目的为圆体的时候
这顶度也能有多少个像素的Y轴都是一样的。那么大家要把有多少个一样的找出来。取中间地方,算为终极
图片 13

图片 14图片 15

Point topPoint = new Point();
            for (int i = 0; i < halfImg.Rows; i++)
            {
                for (int j = 0; j < halfImg.Cols - 1; j++)
                {
                    var cur = halfImg[i, j];
                    var next = halfImg[i, j + 1];
                    if (Math.Abs(RgbHelp.GetDiff(cur, next)) > 2)
                    {
                        var x = 2;
                        next = halfImg[i, j + x];
                        while (Math.Abs(RgbHelp.GetDiff(cur, next)) > 2)
                        {
                            x++;
                            next = halfImg[i, j + x];
                        }
                        topPoint.Y = i;
                        topPoint.X = j + (int)(x / 2.0);
                        break;
                    }
                }
                if (!topPoint.IsEmpty) break;
            }
            CvInvoke.Rectangle(halfImg, new Rectangle(topPoint, new Size(1, 1)), new MCvScalar(0, 0, 255), 3);

            ////这个顶点在原图中的位置
            var oldTopX = topPoint.X;
            if (!targetInLeft) oldTopX += imgWidthCenter;
            var oldTopY = topPoint.Y + imgHeightSplit;
            var oldTopPoint = new Point(oldTopX, oldTopY);
            CvInvoke.Rectangle(img, new Rectangle(oldTopPoint, new Size(1, 1)), new MCvScalar(0, 0, 255), 3);

View Code

图片 16

 

找到了有关的点。总括小黑人与对象的相距就不是难题了
接下来就是发送跳的指令,一个手续就水到渠成了
图片 17

图片 18