数字时钟程序,制作的出发点是因为参考程序太简单了,又想起了一个抖音挺火的数字时钟,后就开始创作这个小程序,这个数字时钟程序我也不是凭空捏造出来的,我参考的是一个windows小程序,也是时钟例子,绘制一个时分秒的时钟样式。
本期的文章就介绍下数字时钟的制作过程,介绍的内容还是以未加缓存和动画的制作过程为主。
我已经讲述了如何利用windows的API对窗口进行移动和追加菜单并对菜单功能进行实现,以及定时器的开启和使用。
开启了定时器之后,在WM_TIMER消息处进行时钟的绘制
在绘制之前我们需要了解下字体,因为数字时钟利用的是字体的旋转
我封装了一个绘制旋转 n° 的函数,函数如下
- //绘制倾斜字体
- void DrawText(HDC hdc, int x, int y, int org, wchar_t szText[])
- {
- SetTextAlign(hdc, TA_BOTTOM | TA_LEFT); //修改锚点
- HFONT ft = nullptr; //字体句柄
- ft = CreateFont(0, 0, org * 10, 0, 100, false, false, false, GB2312_CHARSET, 0, 0, 0, 0, NULL);
- SelectObject(hdc, ft);
- TextOut(hdc, x, y, szText, wcslen(szText));
- DeleteObject(ft);
- }
在这里我们需要创建一个旋转 n° 的字体,创建规则如下
- hdc = GetDC(hWnd);
- //默认系统字体
- HFONT ft = nullptr; //字体句柄
- ft = CreateFont(0, //字符的高度,如果给0,按默认高度来设置
- 0, //字符的宽度,如果给0,按高度横纵比来匹配宽度
- 0, //文字的倾斜角度,10表示1度
- 0, //基线的倾斜角度
- 100, //字体的粗细,取值0-900,400以下为细体,700以上为粗体
- false,//斜体,取值false,true
- false,//下划线,取值false,true
- false,//水平线,取值false,true
- GB2312_CHARSET,//字符集
- 0,//输出精度
- 0,//裁剪精度
- 0,//字体的输出质量
- 0,//字体的间距和系列
- NULL);//字样名
- SelectObject(hdc, ft);
将旋转角度设置成函数参数,从而控制字体的旋转角度,然而在我封装的那段函数里面有修改锚点操作,这是为什么呢
如果不进行该操作的话,字体旋转的锚点在左上角,旋转得到的字体不是预期的,对制作360°旋转的数字时钟达不到效果,我们需要设置锚点为左下角
就这样封装好一个可以在某个DC的x,y位置上绘制倾斜角度为org的字符串szText,这样我们就准备好了绘制数字时钟的基本条件
在WM_TIMER消息内
获取客户区信息
我们首先要得到DC和客户区大小,因为我们需要在窗口上完整的显示数字时钟,这需要数字时钟随客户区的大小改变而改变
- HDC hdc = GetDC(hwnd);
- RECT rt;
- GetClientRect(hwnd, &rt); //获取客户区大小
- SetBkMode(hdc, TRANSPARENT);//设置文字背景透明
设置坐标轴和坐标原点
- //设置坐标轴
- int cx = rt.right;
- int cy = rt.bottom;
- SetViewportExtEx(hdc, cx, cy, NULL);
- //设置原点
- SetViewportOrgEx(hdc, rt.right / 2, rt.bottom / 2, NULL);
获取系统时间
- //获取系统时间
- wchar_t szTime[100];
- SYSTEMTIME currentTime;
- GetLocalTime(¤tTime);
定义变量
- //定义相关变量
- int i = 0;
- int initOrg = 0; //定义初始旋转角度
- int minSize = 0; //数字时钟最小半径
- int date_x = 0; //时间显示的x坐标
在数字时钟制作过程中我的依据是通过一个圆圈的旋转使得x坐标轴上的时间为系统时间,上述变量中的变量initOrg是月份或者日期的第一个时间点的旋转角度
变量data_x是时间绘制的横向坐标,使得月份,日期,时,分,秒在不同的圆周内
其中minSize控制着data_x,使得数字时钟随客户区的大小改变而改变
确定时钟最小半径
- //确定时钟最小半径
- if (rt.bottom > rt.right)
- {
- minSize = rt.right / 2;
- }
- else
- minSize = rt.bottom / 2;
通过之前得到的客户区大小从而确定数字时钟的最小半径,其实这里定义成最大半径更好理解,不知道为什当时脑袋犯抽写成了最小半径,不想改了
这些准备工作完成之后就是真真的绘制环节
首先绘制年份,要将年份绘制在最中间,在之前已经将坐标原点设置在客户区的中间了,所以我们需要进行的操作就是
绘制年份
- //绘制年
- wsprintf(szTime, L"%d 年", currentTime.wYear);
- DrawText(hdc, -30, 0, 0, szTime);
这个很简单通过将之前获取的时间写入到szTime里面,然后绘制在坐标(-30,0)处
绘制月份
- //绘制月
- i = 1;
- date_x = minSize / 6;
- initOrg = -(currentTime.wMonth * 360 / 12) + 360 / 12;
- for (int org = initOrg; org < initOrg + 360; org += (360 / 12))
- {
- wchar_t tempArr[MAX_PATH];
- double temp = 3.1415926 * 2 * org / 360;
- int x = date_x * cos(temp);
- int y = -date_x * sin(temp);
- wsprintf(tempArr, L"%d", i++);
- DrawText(hdc, x, y, org, tempArr);
- }
- DrawText(hdc, date_x + 20, 0, 0, L"月");
这个月份的绘制就有点东西了啊,用到了数学里面的东西,首先月份是从1月开始,所以初始化i = 1;我将 年、月、日、时、分、秒绘制在6个同心圆处,相当于将minSize 6 等分,使得数字时钟看起来对称又有层次感
然后初始旋转角度initOrg是通过 当前的月份 * 每个月份的度数,至于后面为什么又要加上360/12,也就是30,是因为月份是从1开始的,当currentTime.wMonth = 1;时,初始旋转角度initOrg应该为0,也就是不旋转。
然后就是一个圆的循环,度数org从0增加到360°绘制月份,步长为 360/12 也就是30°,然后就是循环体内确定x,y坐标,就是圆圈上12个点的坐标,这里就是三角关系了,头文件加上math.h,定义变量temp将角度单位转化为弧度制,x,y坐标的计算方法就真的是纯数学了,不懂的可以去问初中数学老师。
后面的代码和这个月份的都是一个样的,CV,然后修改一些变量就可以了,我源代码里面写的还算清楚,可以参考下。