一、功能介绍
运行界面如图所示,可以显示时间和日期。
图1 时钟运行效果图
二丶实现方法
2.1EasyX图形库简介
EasyX 是针对 C++ 的图形库,可以帮助 C/C++ 初学者快速上手图形和游戏编程。
比如,可以基于 EasyX 图形库很快的用几何图形画一个房子,或者一辆移动的小车,可以编写俄罗斯方块、贪吃蛇、黑白棋等小游戏,可以练习图形学的各种算法,等等。
许多人学编程都是从 C 语言入门的,而现状是:
1. 有些学校以 Turbo C 为环境学习 C 语言,只是 Turbo C 实在太老了,复制粘贴都很不方便。
2. 有些学校直接拿 VC 来讲 C 语言,因为 VC 的编辑和调试环境都很优秀,并且 VC 有适合教学的免费版本。可惜在 VC 里面只能做一些文字性的练习题,想画条直线或一个圆都很难,例如需要注册窗口类、建消息循环等等,初学者会受严重打击的。初学编程想要绘图就得用 TC,很是无奈。
3. 还有计算机图形学,这门课程的重点是绘图算法,而不是 Windows 编程。所以,许多老师不得不用 TC 教学,因为 Windows 绘图太复杂了,会偏离教学的重点。新的图形学的书有不少是用的 OpenGL,可是门槛依然很高。
所以,我们想给大家一个更好的学习平台,就是 VC 方便的开发平台和 TC 简单的绘图功能,于是就有了这个 EasyX 库。如果您刚开始学 C 语言,或者您是一位教 C 语言的老师,再或者您在教计算机图形学,那么这个库一定会让您兴奋的。
EasyX图形库下载链接:EasyX Graphics Library for C++
2.2程序思路
本程序在VisualStudio2022 IDE上开发,程序总体思路是,用系统自带的函数调用当前时间和日期,返回值为一个结构体,通过计算时间与表盘指针角度关系算出需要绘制的直线两点坐标,然后通过EasyX绘制出当前时间的各个表针,记得每绘制一次,就需要清屏一次,因为绘制出来的线条自己不会消失,而清屏就是用背景色覆盖掉原先绘制出的线条。
2.3各个部分实现原理及代码
2.3.1时间获取函数localtime_s
在头文件<time.h>中定义 | | |
---|---|---|
struct tm * localtime(const time_t * time); | (1) | |
struct tm * localtime_s(const time_t *限制时间,struct tm *限制结果); | (2) | (自C11以来) |
1)以struct tm格式将历元以来的给定时间(time_t
指向的值time
)转换为以本地时间表示的日历时间。结果存储在静态存储器中,并返回指向该静态存储器的指针。
2)与(1)相同,只是该函数使用用户提供的存储result
结果,并且在运行时检测到以下错误并调用当前安装的约束处理函数:
用法,先创建一个tm结构体,用于存放最终获取的时间和日期,再创建一个time_t类的变量,用于存放获取到的系统时间的一种数据格式,然后调用localtime_s(tm* , time_t* )函数,即可把当前时间存放到tm结构体内。
例:
struct tm t; //tm结构指针,tm位于time库函数中的数据类型
time_t now; //声明time_t类型变量
time(&now); //获取系统日期和时间
localtime_s(&t, &now); //获取当地日期和时间,保存到tm类型的结构体内
int year = t.tm_year + 1900;
int mon = t.tm_mon + 1;
int day = t.tm_mday;
2.3.2画表盘
绘制表盘分为画圆和画刻度线,绘制刻度线分为秒分针刻度线和时针刻度线,绘制原理如图所示,只需在指定的角度方向上绘制起点和终点的连线即可得到一段刻度线。
图2 表盘绘制坐标介绍图
函数如下,ANG代表与12 /0点的夹角α,逆时针遍历一周即可画出所有刻度R1,R2为起点、终点坐标距离圆心的距离,由极坐标知识可得下面函数。
void pos(float R1, float R2, float ANG)//绘制表盘刻度线段,
{
float X1, X2, Y1, Y2;
X1 = CENTER_X + R1 * cos(ANG);
X2 = CENTER_X + R2 * cos(ANG);
Y1 = CENTER_Y + R1 * sin(ANG);
Y2 = CENTER_Y + R2 * sin(ANG);
line(X1, Y1, X2, Y2);
}
下列代码分别为秒刻度和时刻度的绘制,秒刻度将2π弧度分为60份,每一份角度等于2π/60,每次CLK_ANG的增量为2π/60弧度。当CLK_ANG小于2π时,一直自增角度并绘制刻度线,当满足转满一周时,退出绘制。
while (CLK_ANG < 2 * PI)
{
setlinecolor(WHITE); //RGB(255, 23, 228)
pos(0.95 * CLK_R, CLK_R, CLK_ANG);
CLK_ANG = CLK_ANG + 2 * PI / 60;
}
CLK_ANG = 0;//角度清零
while (CLK_ANG < 2 * PI)
{
setlinecolor(RGB(242, 9, 9));
pos(0.9 * CLK_R, CLK_R, CLK_ANG);
CLK_ANG = CLK_ANG + 2 * PI / 12;
}
2.3.3计算表针末端坐标值
表针末端的坐标值总满足以表心为圆心,表针长度为半径的圆的方程,设表盘中心为极点,表针长度为r,表与12点夹角为ANG通过极坐标可知任意夹角下的表针末端坐标为X=X0+R*cos(ANG),Y=Y0+R*sin(ANG),(X0,Y0)为圆心坐标。代码如下:
void point_drw(float LENGTH, float ANG)//绘制表针函数
{
float X, Y;
X = CENTER_X + LENGTH * cos(ANG);
Y = CENTER_Y + LENGTH * sin(ANG);
line(CENTER_X, CENTER_Y, X, Y);
}
2.3.4主程序体
主程序体分为三部分内容,第一部是do while循环体,循环体内主要是通过判断是否为0点0分0秒,如果是则刷新一次日期输出到屏幕指定位置,如果不是则退出循环,执行第二部分,第二部分为绘制时针,秒针,分针的程序,先通过time函数获取时间,然后转换为角度并绘制。转换角度时,由于time修改的内容为24小时格式,需要判断是否大于12点,如果大于12点需要手动把当前时间减去12小时,变位12小时格式,再计算时针角度。绘制完这一秒的表针状态,如果直接绘制下一秒的表针,那么就会导致上一秒的表针还在显示,所以需要手动绘制黑色(窗口背景色)的表针来覆盖上一次绘制的表针。
一些库函数函数功能简介:
settextcolor(COLORREF color);这个函数用于设置当前文字颜色。
outtextxy(int x,int y,LPCTSTR str);这个函数用于在指定位置输出字符串。
BeginBatchDraw();这个函数用于开始批量绘图。执行后,任何绘图操作都将暂时不输出到绘图窗口上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出。
FlushBatchDraw();这个函数用于执行未完成的绘制任务。
_stprintf_s();关于字符串输出,相见EasyX帮助文档,附上一个应用示例:
// 输出数值 1024,先将数字格式化输出为字符串(自适应字符集)
TCHAR s[5];
_stprintf(s, _T("%d"), 1024); // 高版本 VC 推荐使用 _stprintf_s 函数
while (1)
{
do
{
settextcolor(RGB(255, 23, 130));
_stprintf_s(s1, _T("小时钟V2.1"));
_stprintf_s(s2, _T("%04d年%02d月%02d日"), year, mon, day);
outtextxy(25, 25, s1);
settextcolor(WHITE);
outtextxy(Width / 2 - 50, High / 2 + 170, s2);
} while (t.tm_hour==0&& t.tm_min==0&& t.tm_sec==0);//每天0点0时0分刷新日期绘制
setlinecolor(RGB(255, 23, 228));//设置针线条颜色
point_drw(SEC_LENGTH, SEC_ANG);//绘制秒针头线段
point_drw(SEC_LENGTH * 0.2, SEC_ANG + PI);//绘制秒针尾巴线段
setlinecolor(RGB(4, 231, 255));
point_drw(MIN_LENGTH, MIN_ANG);
point_drw(MIN_LENGTH * 0.3, MIN_ANG + PI);
setlinecolor(GREEN);
point_drw(HOR_LENGTH, HOR_ANG);
point_drw(HOR_LENGTH * 0.25, HOR_ANG + PI);
FlushBatchDraw();
//Sleep(5);//延时防闪烁
setcolor(BLACK);//以下绘制为覆盖绘制,用黑色覆盖掉上一秒指针轨迹
point_drw(SEC_LENGTH, SEC_ANG);
point_drw(MIN_LENGTH, MIN_ANG);
point_drw(HOR_LENGTH, HOR_ANG);
point_drw(SEC_LENGTH * 0.2, SEC_ANG + PI);
point_drw(MIN_LENGTH * 0.3, MIN_ANG + PI);
point_drw(HOR_LENGTH * 0.25, HOR_ANG + PI);
time(&now);//取当前时间存放到啊now指针
localtime_s(&t, &now);//将now指针内容存到t结构体
SEC_ANG = t.tm_sec * 2 * PI / 60 - PI / 2;//计算秒针角度
MIN_ANG = t.tm_min * 2 * PI / 60 - PI / 2;//计算分针角度
if (t.tm_hour <= 12)//如果不超过12点,就正常计算
{
HOR_ANG = (t.tm_hour + (float)t.tm_min / (float)60) * 2 * PI / 12 - PI / 2;//计算0-12点时针角度
settextcolor(WHITE);
_stprintf_s(s3, _T("上午%02d时%02d分%02d秒"), t.tm_hour, t.tm_min, t.tm_sec);
outtextxy(Width - 50, High + 50, s3);
}
else//如果超过12点,计算时针角度时需要把当前时间-12,变换为12小时制
{
HOR_ANG = (t.tm_hour + (float)t.tm_min / (float)60 - 12) * 2 * PI / 12 - PI / 2;
settextcolor(RGB(4, 231, 255));
_stprintf_s(s3, _T("下午%02d时%02d分%02d秒"), t.tm_hour, t.tm_min, t.tm_sec);
outtextxy(Width - 150, 25, s3);
}
}
2.4完整代码
#include <graphics.h>
#include <conio.h>
#include <string.h>
#include <time.h>
#include<math.h>
#define Width 512 //窗口宽
#define High 512 //窗口高
#define PI 3.1415926
#define CENTER_X Width / 2 //表盘中心X坐标
#define CENTER_Y High / 2 //表盘中心Y坐标
#define SEC_LENGTH (float)High/3.75 //秒针长度
#define MIN_LENGTH (float)High/5.25 //分针长度
#define HOR_LENGTH (float)High/7.5 //时针长度
#define CLK_R (float)High / 3.2 //表盘半径
void pos(float R1, float R2, float ANG);//计算
void point_drw(float LENGTH, float ANG);
void fangkuang(int length);
void TimeInit(void);
void DrawClockScale(void);
inline void welcome();
int main()
{
struct tm t; //tm结构指针,tm位于time库函数中的数据类型
time_t now; //声明time_t类型变量
time(&now); //获取系统日期和时间
localtime_s(&t, &now); //获取当地日期和时间,保存到tm类型的结构体内
TCHAR s1[20], s2[50], s3[30];//用于存放输出到绘图窗口的字符串,详见EasyX帮助文档
int year = t.tm_year + 1900;
int mon = t.tm_mon + 1;
int day = t.tm_mday;
float SEC_ANG = 0;//SEC_ANG、MIN_ANG、HOR_ANG分别为秒针、分针、时针初始角度
float MIN_ANG = 0;
float HOR_ANG = 0;
initgraph(Width, High); //初始化绘图窗口
welcome();
#if 0
for (int i = Width / 2; i > 10; i -= 6)
{
setlinecolor(RGB(255, 23, i)); //设置线条颜色,将Blue设为i变量,随着时间渐变
fangkuang(i); //绘制矩形 i为矩形边长,渐变增大
Sleep(20); //延时
cleardevice(); // 清屏
}
#endif
setlinecolor(WHITE); //此部分为画出表盘外部白色边框
setlinestyle(PS_SOLID, 2);
setlinecolor(RGB(242, 9, 9)); //RGB(255, 23, 228)
fangkuang(10);
fangkuang(20);
setlinecolor(WHITE);
circle(CENTER_X, CENTER_Y, CLK_R);
DrawClockScale();//画刻度
settextcolor(RGB(255, 23, 130));
_stprintf_s(s1, _T("小时钟V2.1"));
_stprintf_s(s2, _T("%04d年%02d月%02d日"), year, mon, day);
outtextxy(25, 25, s1);
settextcolor(WHITE);
outtextxy(Width / 2 - 50, High / 2 + 170, s2);
BeginBatchDraw();//开始批量绘图,绘图期间所有线条均在缓冲区,
while (1)
{
setlinecolor(WHITE);
point_drw(SEC_LENGTH, SEC_ANG);
point_drw(SEC_LENGTH * 0.2, SEC_ANG + PI);
setlinecolor(RGB(4, 231, 255));
point_drw(MIN_LENGTH, MIN_ANG);
point_drw(MIN_LENGTH * 0.3, MIN_ANG + PI);
setlinecolor(RGB(242, 9, 9));
point_drw(HOR_LENGTH, HOR_ANG);
point_drw(HOR_LENGTH * 0.25, HOR_ANG + PI);
FlushBatchDraw();
Sleep(5);
setcolor(BLACK);
point_drw(SEC_LENGTH, SEC_ANG);
point_drw(MIN_LENGTH, MIN_ANG);
point_drw(HOR_LENGTH, HOR_ANG);
point_drw(SEC_LENGTH * 0.2, SEC_ANG + PI);
point_drw(MIN_LENGTH * 0.3, MIN_ANG + PI);
point_drw(HOR_LENGTH * 0.25, HOR_ANG + PI);
time(&now);
localtime_s(&t, &now);
SEC_ANG = t.tm_sec * 2 * PI / 60 - PI / 2;
MIN_ANG = t.tm_min * 2 * PI / 60 - PI / 2;
if (t.tm_hour <= 12)
{
HOR_ANG = (t.tm_hour + (float)t.tm_min / (float)60) * 2 * PI / 12 - PI / 2;
settextcolor(WHITE);
_stprintf_s(s3, _T("上午%02d时%02d分%02d秒"), t.tm_hour, t.tm_min, t.tm_sec);
outtextxy(Width - 50, High + 50, s3);
}
else
{
HOR_ANG = (t.tm_hour + (float)t.tm_min / (float)60 - 12) * 2 * PI / 12 - PI / 2;
settextcolor(RGB(4, 231, 255));
_stprintf_s(s3, _T("下午%02d时%02d分%02d秒"), t.tm_hour, t.tm_min, t.tm_sec);
outtextxy(Width - 150, 25, s3);
}
}
EndBatchDraw();
closegraph(); // 关闭绘图窗口
return 0;
}
void DrawClockScale(void)
{
float CLK_ANG = 0;
while (CLK_ANG < 2 * PI)
{
setlinecolor(WHITE); //RGB(255, 23, 228)
pos(0.95 * CLK_R, CLK_R, CLK_ANG);
CLK_ANG = CLK_ANG + 2 * PI / 60;
}
CLK_ANG = 0;
while (CLK_ANG < 2 * PI)
{
setlinecolor(RGB(242, 9, 9));
pos(0.9 * CLK_R, CLK_R, CLK_ANG);
CLK_ANG = CLK_ANG + 2 * PI / 12;
}
}
void pos(float R1, float R2, float ANG)//绘制表盘刻度线段,
{
float X1, X2, Y1, Y2;
X1 = CENTER_X + R1 * cos(ANG);
X2 = CENTER_X + R2 * cos(ANG);
Y1 = CENTER_Y + R1 * sin(ANG);
Y2 = CENTER_Y + R2 * sin(ANG);
line(X1, Y1, X2, Y2);
}
void point_drw(float LENGTH, float ANG)//绘制表针函数
{
float X, Y;
X = CENTER_X + LENGTH * cos(ANG);
Y = CENTER_Y + LENGTH * sin(ANG);
line(CENTER_X, CENTER_Y, X, Y);
}
void fangkuang(int length)//绘制方框 ,参数为方框边长
{
rectangle(length, length, Width - length, High - length);
}
void welcome()
{
setlinestyle(PS_SOLID, 4); //用于设置当前设备画线样式。PS_SOLID 线形为实线。 宽度 4
for (int i = Width / 2; i > 10; i -= 6)
{
setlinecolor(RGB(255, 23, i)); //设置线条颜色,将Blue设为i变量,随着时间渐变
fangkuang(i); //绘制矩形 i为矩形边长,渐变增大
Sleep(20); //延时
cleardevice(); // 清屏
}
cleardevice();
}