我们都知道,有大约五种方式可以获得一个坐标位置,他们分别是new point、PointToClient、PointToScreen,Location及控件鼠标事件传递的坐标。为了了解绘图坐标,我们事先来设计一个显示GDI+绘图坐标的窗体,将坐标值显示在上面。

一、绘图环境准备预备知识

1、预备知识和说明

(1)、绝对和相对坐标

在winform中,我们有两种坐标值,一种是绝对坐标值(即以屏幕左上角为原点的坐标系计算),一种是相对坐标值(即以控件容器的左上角为原点的坐标系)

(2)、区域说明

如下图,一般我们将屏幕从大到小分为:
屏幕区:(包涵所有,屏幕的坐标就是绝对坐标)
窗体区:窗体区主要针对的是窗体,因为窗体虽然也是容器但他有边框和标题栏
工作区:所有的控件和容器真正可以绘图的区域,我们也可以称之为绘图区

值得注意的是,工作区的最左边等于窗体的最左边,因为窗体有边框。同理,工作区的最顶端不是窗体的最顶端,因为窗体有标题栏。关于这点,在后面的按钮中绘图我们就能看到他们的存在。

2、演示窗体设计

3组label(每组四个)、一个Button和一个timer(或者用form的mousoMove事件也可),并且记得将timer的enable设置为TRUE,否则不会功能。

其中:
C(x,y)表示client坐标,使用PointToClient对鼠标位置进行转换
S(x,y)表示screen坐标,使用PointToScreen对鼠标位置进行转换
A(x,y)表示绝对坐标,也就是屏幕坐标,直接使用鼠标坐标
B(x,y)表示控件内坐标(这里就是button1),使用的是控件的鼠标事件MouseEventArgs中的location
已将这个测试模型上传,感兴趣的童鞋可以下下来自己测试和学习

3、为time添加tick事件代码

添加代码后可以随时更新位置坐标,代码如下:

 spt = PointToClient(MousePosition); label1.Text = spt.X.ToString();       //client的x坐标label2.Text = spt.Y.ToString();        //client的y坐标cpt = PointToScreen(MousePosition);label3.Text = cpt.X.ToString();label4.Text = cpt.Y.ToString();         label5.Text =MousePosition.X.ToString();//screen的X坐标label6.Text =MousePosition.Y.ToString();//screen的Y坐标

同时,我们还为按钮的MouseMove事件添加代码以更新坐标

label7.Text = e.Location.X.ToString();
label8.Text = e.Location.Y.ToString();

启动之后,我们可以看到四个坐标的变化,

4、 让窗体在指定位置启动

为了让我们四组坐标有精确的对比性,我们必须让窗体在指定的位置启动,否则无法通过精确的数字掌握他们之间的联系和区别,这里我们通过代码让窗体在屏幕的100,100位置启动,代码如下:

 this.StartPosition = FormStartPosition.Manual;this.Location = new Point(100, 100);

我们从下图可以看出,client的坐标为0,0,screen的坐标是100,100.所以,这里我们可以明确一件事,PointToClient获取的是父容器的client,不包含标题栏几边框的厚度。窗体的父容器就是屏幕了。

通过上面的代码我们发现,窗体启动位置是在屏幕的100,100处。
而且,通过计算我们可以看出,ax-cx=109,而form的x坐标为100,所以,我们得出边框厚度为9,同理,可以算出标题栏高度为38;

二、五种获取坐标的区别

1、控件鼠标事件坐标

我们常常可以通过鼠标事件MouseMove的MouseEventArgs获取控件内鼠标所在位置的坐标,当然,这个坐标是以控件的左上角为原点的。代码如下:

 private void button1_MouseMove(object sender, MouseEventArgs e){Point pt = e.Location;label1.Text = pt.X.ToString();label2.Text = pt.Y.ToString();}

2、Location(父容器中的坐标定位器)

上面我们就用到了鼠标事件的Location属性,我们来看看这个鼠标事件的location舒心给的描述:

//A System.Drawing.Point that contains the x- and y- mouse coordinates, in pixels,
//relative to the upper-left corner of the control.

很清楚,它返回的就是鼠标所在控件左上角到鼠标的x和y坐标差值,也就是按照控件左上角为原点的坐标系。这个location对于任何控件或者容器都有这个属性,比如我们的按钮:
控件Location属性的描述是:

//Gets or sets the coordinates of the upper-left corner of the control
//relative to the upper-left corner of its container.

基本和鼠标事件的Location属性一样,也是以控件或容器自身的左上角为原点的坐标值:

  Graphics btg = button1.CreateGraphics();Point pt = button1.Location;btg.DrawRectangle(new Pen(Color.Green), new Rectangle(pt.X ,pt.Y,  100, 40));

上面的代码是在按钮上绘制一个矩形,但完全有可能绘制后看不到,因为button1.location是因其父容器的左上角为原点的,button1.location的值则是它与父容器左上角的绝对差值,这个值完全可能超过按钮的长宽值,所以就有可能在button1上看不到这个绘制的图形。



从上图可以看到,当我们的按钮在中央的时候,绘制的矩形在中央,按钮在左上角是绘制的矩形在按钮的左上角,按钮在右上角时绘制的矩形在按钮的右上角。

很显然,它返回的是一个点,一个什么点呢?是它在父容器对应位置的相对坐标点,也就是说,当这个控件在父容器的左上角,我们得到的就是这个控件左上角的点,当这个控件在父容器的右上角,我们得到的就是这个控件在父容器中右上角位置的一个点。

最终效果是不是好似一面反应按钮与窗体位置关系的镜子。

这其实用控件的left和top属性获得的控件在父控件中的location坐标值是一样的;

 Graphics btg = button1.CreateGraphics();Point pt = new Point( button1.Left,button1.Top);btg.DrawRectangle(new Pen(Color.Green), new Rectangle(pt.X ,pt.Y,  100, 40));

3、new point默认为相对坐标

我们注意观察就会发现,一开始我们定义窗体启动时就采用的new point给定窗体启动位置的坐标,系统直接按照屏幕的100,100启动了窗体。我们再次实验,在窗体上加上一个按钮button,也为其设定坐标,代码如下:

this.button1.Location = new Point(100, 0);

得到的结果是,按钮顶到了窗体工作区的顶部(标题栏的下面),难道这说明,new point应该就是父容器的内部坐标?

为了验证这一结论,我们在按钮上绘图,也采用new point看看,代码如下:

 Graphics btg = button1.CreateGraphics();
Point pt=new Point(0,0);
btg.DrawRectangle(new Pen(Color.Green), new Rectangle(pt.X ,pt.Y,  100, 40));

当然,我们直接给矩形的起点坐标输入0,0也是一样的结果:

 Graphics btg = button1.CreateGraphics();btg.DrawRectangle(new Pen(Color.Green), new Rectangle(0, 0, 100, 40));


我们绘制的绿色矩形起点就是按钮的左上角(0,0)。所以,new point本身不代表是父容器还是子容器坐标。要看我们采用由谁创造出来的Graphic。所以,一般我们在控件内绘制图形时完全没有必要使用PointToClient、PointToScreen,如果事先知道坐标直接使用new point来实例化一个坐标更合适。

在窗体实例中设置控件及本身的Location时使用的new point都只是一组数据,这些数据原理上是要传给窗体的graphic来绘制控件的,所以说,new point本身是一个相对坐标,相对于Graphic原点的坐标值。

4、PointToClient()绝对坐标差

前面我们在显示鼠标的client坐标的时候就使用过PointToClient,得到的效果是使用了这个函数后比鼠标本身的位置对应x和y均减少了一个数值,这个数值一也就是窗体在屏幕中的起点位置的x和y坐标的值。

 mpt = PointToClient(MousePosition);label1.Text = mpt.X.ToString();         //client的x坐标label2.Text = mpt.Y.ToString();        //client的y坐标

我们发现,从现实的坐标数据看,cx=x-1001,cy=y-1001,这个我们在后面,这里的100,100分别是窗体启动的x和y坐标值。
那么,是不是每使用一次PointToClient就会减少一个父容器的屏幕位置对应的x和y的的值呢?还是只是将一个绝对对坐标点转化为指定父容器控件的相对坐标(client点)呢?我们也可以在按钮中试试,我们将鼠标坐标转化为按钮控件的坐标,看看与按钮中得到的鼠标事件中的鼠标位置是否一样,代码如下:

spt = button1.PointToClient(MousePosition);
label1.Text = spt.X.ToString();
label2.Text = spt.Y.ToString();
private void button1_MouseMove(object sender, MouseEventArgs e)
{label7.Text = e.Location.X.ToString();label8.Text = e.Location.Y.ToString();}


果然,C(x,y)与B(x,y)完全一致。
现在可以下结论了,PointToClient的作用的确是将一个绝对对坐标点转化为指定父容器控件的相对坐标(client点),这个绝对坐标一般通过鼠标位置来获得。(注意:前面我们说了,location获得的坐标都是相对父容器原点的相对坐标)

也就是说,PointToClient一般都给MousePosition使用了。

上面所说如果你都搞懂了,那么我提个问题,假如,假如我们没有new point(0,0),我们如何获得控件的原点,比如窗体的原点:

Point pt = this.PointToClient(this.Location);

我们可以这么用,为什么呢?能想明白不?
也就是说,以下两段代码效果一样:

Graphics btg = button1.CreateGraphics();
Point pt = this.PointToClient(this.Location);
Size clientSize = this.ClientSize;
int left = (this.Size.Width - clientSize.Width) / 2;      //左右的边框长度
int top = (this.Size.Height - clientSize.Height) - left;  //下边框和左右边框长度一样
btg.DrawRectangle(new Pen(Color.Green), new Rectangle(pt.X + left, pt.Y + top, 200, 100));
 Point pt = button1.Location;btg.DrawRectangle(new Pen(Color.Green), new Rectangle(0, 0, 200, 100));

5、PointToScreen()

首先我们来看看描述,意识就是将一个cilent点转化为一个Screen点。

这么说,那就是意味着最好传入一个相对坐标给它作为参数,这类参数还真是很好找,那就是Location。
这次我们将按钮鼠标事件中的location传给PointToScreen来使用:

 private void button1_MouseMove(object sender, MouseEventArgs e){Point pt=button1.PointToScreen(e.Location);label7.Text = pt.X.ToString();label8.Text = pt.Y.ToString();}


我们看到,的确将MouseEventArgs的Location转化为了绝对坐标值(屏幕坐标)。按钮内的B(x,y)的值和绝对坐标A(x,y)完全一致。

一般不建议将鼠标位置传入PointToScreen作为参数,当我们把鼠标位置的PointToClient改成PointToScreen时,我们发现两个坐标之差变成了鼠标坐标和窗体左上角坐标之和了

 mpt = PointToScreen(MousePosition);label1.Text = mpt.X.ToString();label2.Text = mpt.Y.ToString();


此时cx=sx+1001,cy=sy+1001;

我们发现,当使用PointToScreen是,会把屏幕坐标的值加入当前坐标,每使用一次就加一次,为了区别x坐标和y坐标的变化区别,我们将窗体启动坐标修改为(100,200)我们来看看使用两次后的代码:
窗体启动位置代码

this.Location = PointToClient(new Point(100, 100));

tick事件内代码:

 mpt = PointToScreen(PointToScreen(MousePosition));label1.Text = mpt.X.ToString();label2.Text = mpt.Y.ToString();


此时cx=sx+1002,cy=sy+2002;

6、小应用

(1)、求Location

通过上面的分析测试,我们发现PointToScreen的原理其实就是迭代获取指定控件及其父控件的绝对位置,有几层找几层,然后把这些控件的相对位置累加在一起就自然是要转换点的绝对坐标了,迭代几次就看父控件有多少层了。
既然这样,我们当然就可以使用PointToScreen来获得控件本身的location,下面的代码和控件的Location属性的数据是完全一致的。

button1.PointToScreen(new Point(0,0));

上述代码不仅仅求得点与button1的相对坐标,求完还求button1与form1的相对坐标,(form是最外层了,他的相对坐标和觉得坐标一样)最后可得到绝对坐标。

(2)、求两点的距离

其实PointToClient的原理就是将两个觉得位置做差,也就是求相对位置的函数,那我们就可以用来求距离。

//button1相对于button2的坐标,也就是两控件的距离Point pt =  button1.PointToClient(button2.PointToScreen(new Point(0, 0)));MessageBox.Show("Y distance:" + pt.Y.ToString());

完全两点的距离,就需要用到两点间的距离公式啦。这里只是求Y的距离做演示。

掌握了绘图坐标,我们在绘图的路上会少走一些弯路,我们将继续前行。

GDI+绘图轻松入门[5]-绘图坐标的理解和应用相关推荐

  1. Matplotlib绘图快速入门

    Matplotlib绘图快速入门 文章目录 Matplotlib绘图快速入门 一.面向过程绘图 1.常用的绘图类型 (1) p l o t ( ) plot() plot():曲线图 (1)更改颜色 ...

  2. 【Qt教程】3.5 - Qt5 QPainter绘图抗锯齿、相对坐标/平移坐标系、QPainter画图片、手动调用QPainter绘图事件

    GitHub源码仓库:Qt学习例程 1. QPainter绘图抗锯齿 /// 抗锯齿测试 /// painter.drawEllipse(QPoint(100,50), 50, 50);// 设置 抗 ...

  3. html:canvas画布绘图简单入门-绘制时钟-3

    canvas示例系列: html:canvas画布绘图简单入门-1 html:canvas画布绘图简单入门-2 html:canvas画布绘图简单入门-绘制时钟-3 html:canvas画布绘图简单 ...

  4. c语言双缓冲怎么用,C语言游戏编程:GDI怎么实现双缓冲绘图去掉闪烁

    在上篇文章中将我要用 C语言重新写一个俄罗斯方块 ,使用的是GDI的绘图模式(目前正在移植到DX上去,想添加一些更好友好的动画).数据与动画分离,动画的帧率保持在30左右.但是绘图的时候画面出现了强烈 ...

  5. html:canvas画布绘图简单入门-画板-5

    canvas示例系列: html:canvas画布绘图简单入门-1 html:canvas画布绘图简单入门-2 html:canvas画布绘图简单入门-绘制时钟-3 html:canvas画布绘图简单 ...

  6. 1小时入门马克笔绘图

    在这辞旧迎新的最后一天,铃铛子给简友们拜年了,祝大家年年有余,吃嘛嘛香. 铃铛子拍摄的手绘视频 入门马克笔,我们首先要知道它的用法: 马克笔使用简介: 1.用纸一般选择吸水性差.纸质结实.表现光滑的纸 ...

  7. c语言编译笑脸,C语言快速入门——笑脸绘图程序:窗口实现

    将控制台窗口分为三部分 分割窗口事实上是通过把输入光标定位到某处,然后使用printf函数输出字符实现的.在程序中,这一功能是由help模块提供,具体由help_init函数提供实现.help模块中的 ...

  8. 30秒Python轻松入门-目录

    我之前写过几本Python.C.C++的编程图书,通过趣味游戏开发.互动艺术学编程: 然而,即使对于比较容易学的Python,还是有读者反馈说编程太难了. 基于此,给自己挖一个坑,准备出一套更容易上手 ...

  9. GetDC、ReleaseDC、CreateSolidBrush、SelectObjec、DeleteObject、Rectangle、Ellipse在窗体上进行绘图的函数和绘图步骤说明

    窗口绘图有几个步骤 1.获得窗体区域的坐标:GetClientRect 2.在绘图前先清理绘图区域:InvalidateRect 3.更新窗口,其实是直接给窗体发送WM_PAINT消息 4.获得绘图上 ...

最新文章

  1. 小评 XenServer 6.0功能
  2. Python descriptor
  3. 数据结构——树、森林和二叉树之间的转换
  4. java三大范_Java深度学习系列——数据库的三大范式
  5. 聊聊我的高效学习法,让你快速拿下算法、MySQL…每天只花半小时
  6. Vitis学习记录(三)
  7. Adobe (Acrobat)Reader 6.0以上版本支持对有特殊权限的PDF进行添加注释,填写标单以及保存的功能。...
  8. 清远机器人编程_致敬逆行者:棒棒贝贝为清远援鄂人员子女免费提供一年乐高编程课...
  9. CloudStack 中关于注册ISO模版的问题解决
  10. vue-cli3使用vue-router 使用动态路由,在刷新页面时报错
  11. 北大青鸟软件工程师ACCP4.0课程
  12. 雷电模拟器命令操作合集
  13. 仿生机制算法——细胞吸引子模型(附Matlab代码)
  14. vue使用甘特图(实现树形结构/一条数据显示双时间轴)
  15. scrapy实战项目(简单的爬取知乎项目)
  16. idea软件界面的的外观设置-----一般都有三套主题:IntelliJ Light ,Darcula,Windows供选择
  17. CENTOS上的网络安全工具(十六)容器特色的Linux操作
  18. RAS 和 NDIS 拨号模式
  19. 快来,看看spring有多烂-来自jfinaL的嘲笑
  20. move_base学习(一)之双激光差动式移动机器人导航仿真

热门文章

  1. 【American English】美式发音,英语发音,美国音音标列表及发音
  2. 取消域服务器是定期修改密码,更改域服务器用户密码
  3. LVS的Tun模式(隧道模式)的实现
  4. APP界面设计指南|APP界面设计师必备信息图
  5. String字符串转JSON对象(JSON的依赖)
  6. office 打开wps乱_wps文档用word打开排版不对怎么办
  7. 光纤网卡千兆和万兆、光口和电口之间的区别?与PC网卡、HBA卡的区别有哪些?
  8. 点云配准算法ICP及其各种变体
  9. SAP JCo 功能
  10. 大电影 剧来风 山东方言版 下载