数字时钟程序,制作的出发点是因为参考程序太简单了,又想起了一个抖音挺火的数字时钟,后就开始创作这个小程序,这个数字时钟程序我也不是凭空捏造出来的,我参考的是一个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,然后修改一些变量就可以了,我源代码里面写的还算清楚,可以参考下。