最近在学习一个时钟显示程序,觉得其中有好多值得细细体会、分析的地方。为了记住这些精妙之处,我把自己的这些理解和体会一并写在这个博客里,以备自己以后查询,也希望给正在学习这个程序的网友一个参考。
先上这个程序的运行效果图:
一、映射模式与时钟的“时针”、“分针”和“秒针”的摆动显示
时钟运行显示时间时,时、分和秒针在程序代码中摆动时,程序代码是如何绘制才符合时钟显示的运行规律的呢?这部分代码,是我学习这个程序时最让我难以理解的地方。程序中绘制时、分、秒针摆动的代码主要涉及两个函数:
1、绘制时针、分针的函数:
void CMainWindow::DrawHand (CDC* pDC, int nLength, int nScale,int nDegrees, COLORREF clrColor)
2、绘制秒针的函数:
void CMainWindow::DrawSecondHand (CDC* pDC, int nLength, int nScale,int nDegrees, COLORREF clrColor)
两个函数都需要解决以下几个问题:
1、时、分、秒针本身(外观形状)如何绘制?
2、怎样根据当时的时间(时、分、秒)把时针、分针和秒针绘制并指向对应时间点上呢?
经过仔细思考分析,我终于理解到了其中的思路:
首先,解决绘图的坐标系和绘图映射模式问题。
由于时钟显示时,钟的针是以圆心为轴,顺时针方向旋转摆动显示的。为了计算方便,有必要把坐标原点确定在时钟圆盘的中心,坐标系也要调整成x轴向右为正,y轴向上为正的模式。要实现这个目的,涉及到vc++绘图映射模式的设置。以下是代码实现:
CRect rect;
GetClientRect(&rect);
CClientDC dc(this);
dc.SetMapMode(MM_ISOTROPIC);
dc.SetWindowExt(1000, 1000);
dc.SetViewportExt(rect.Width(), -rect.Height());
dc.SetViewportOrg(rect.Width() / 2, rect.Height() / 2);
由于使用了MM_ISOTROPIC自定义映射模式, 它使得在横、纵两个方向的缩放比例相同。就是不管窗口的水平、垂直两个方向的长度如何变化,程序总是会自动调整并按短的一边(水平和垂直短的一边)为准显示,保持横、纵比例相同。
程序中把显示的逻辑窗口设置为(1000,1000)的区域,与之对应的视窗区域却是(rect.Width(),-rect.Height())。尽管窗口的水平和垂直两个方向的长度可能不同,由于映射模式为MM_ISOTROPIC模式,所以程序会自动调整显示。由于还设置了视窗中心为新的坐标原点(dc.SetViewportOrg(rect.Width() / 2, rect.Height() / 2);),所以,(1000,1000)的逻辑区域总是会被显示到视窗区域的中心部分——一个正方形的区域,以短的一边(水平方向或垂直方向短的一边)为边长。看看下面程序运行的几种效果图即可知道了:
需要强调说明的是:SetViewportExt ()第二个参数为负值,因此逻辑坐标系在视窗显示时,相当于:原点位于视窗中心上,x轴向右为正,y轴向上为正。如下图所示:
明白这一点很重要,后面时钟的针的绘制显示才能正确理解。
其次,解决时钟的时、分和秒针的指向摆动问题。
时针一圈分12个小时,有12个刻度,每摆动一个刻度,角度为360/12=30度;
分钟一圈分60分,有60个刻度,每摆动一个刻度,角度为360/60=6度;
秒针一圈分60秒,有60个刻度,每摆动一个刻度,角度为360/60=6度。
摆动角度的规律很明显,但如何绘制一定时间对应的时针、分针和秒针呢?这里当然要用到“三角函数”了。不过且慢,三角函数中使用的角,是以x轴正方向为起始沿逆时针或顺时针旋转而成,而在本程序中,时钟针的起点以y轴正方向为起始,沿顺时针方向而成。假设以y轴正方向为起始,沿顺时针方向旋转的角度为a,针尖所在点为A,从原点到针A点的长度为r。则A点的坐标:(r*cos(a),r*sin(a))是完全不正确的,因为它不是我们平常所用的三角函数的那种角度,起始边不一样。怎么办呢?看下图:
由上图可知:不管角度a位于哪一个象限,都可以相当于:--a这个以x轴正方向开始的,沿顺时针方向形成的角的三角函数值,根据三角函数公式,即可推出A点坐标为(sin(a),cos(a))。(以上坐标是 r 假设为1的值,程序中r实际为450)这样,时针分针秒针的针尖点的坐标就根据角度得到了。代码实现为