利用Acllib写的一个桌面时钟
2016-09-23 17:33
651 查看
关于Acllib的一点介绍:
Acllib是一个基于Win32API的函数库,提供了相对较为简单的方式来做Windows程序。实际提供了⼀个.c和两个.h,可以在MSVC和Dev C++( MinGW)中使用。
纯教学用途,但是编程模型和思想可以借鉴
下面是我利用acllib做的一个桌面小时钟,并且增加了一个“没用的”事件处理。
↓↓↓↓时钟长这个样子↓↓↓↓
首先是acllib的程序入口,Setup 函数:
int Setup() { // 初始化窗口参数分别是窗口名称,起始点的x,y坐标,窗口的x,y尺寸 initWindow("MyWindow", DEFAULT, DEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT); // 注册键盘事件 registerKeyboardEvent(keyEvent); // 注册时间事件 registerTimerEvent(timerEvent); // 这里是为了事件按键事件处理增加的一个标志量,后面会讲为什么 timerflag = TIMER_working; // 定时器开始工作, startTimer(0, 20); // 程序初始化完成,进入消息循环,开始工作 return 0; }
这里要提一下windows api的工作流程,大致如下图所示:
程序开始工作时就进入了消息循环(也就是setup结束),我们可以看到在消息循环中要循环调用各种事件函数,这里主要有三种事件:
1. 鼠标
2. 键盘
3. 定时器
这里要做一个时钟,那么第一个要用到的就是定时器事件,从上面的代码可以看到,定时器的工作模式就是在Setup中注册定时器事件,每次时钟“tick”一下,就发生一次定时器事件也就是调用你所注册的定时器事件函数。这里我们的函数作用是每20ms,刷新一次画面,也就是重画整个钟表。
下面看一下画钟表的函数:
void picture_a_minute(int tid) { int x, y; // 获取表中心点坐标 int cntX = getWidth()/2, cntY = getHeight()/2; // 表盘半径 int r_dial = DIAL_RADIUS; // 计算刻度大圆和小圆半径 int r = (double)r_dial/15, r2 = (double)r_dial/25; // 计算时针分针秒针的长度 int r_sec = (double)r_dial * 4 / 5, r_min = r_dial * 5 / 8, r_h = r_dial / 2; // 时分秒的中间变量 double sec, min, h; // 存储时间文本 char timetext[40]; // 绘制指针时的顶点存储变量 int second[4][2], minute[4][2], hour[4][2]; // 获取本地时间 time_t timer = time(NULL); struct tm *timel = localtime(&timer); beginPaint(); clearDevice(); // 画表盘 setBrushColor(ANTIQUE_WHITE); setPenColor(ANTIQUE_WHITE); ellipse(cntX - r_dial, cntY - r_dial, cntX + r_dial, cntY + r_dial); // 大刻度 setBrushColor(RGB(231, 61, 105)); setPenColor(RGB(231, 61, 105)); for (int j = 0; j < 12; j++) { if (!(j % 3)) { x = -r_dial * sin((double)j * 30 / 180 * PI) + cntX; y = r_dial * cos((double)j * 30 / 180 * PI) + cntY; ellipse(x - r, y - r, x + r, y + r); } } // 小刻度 setBrushColor(RGB(0, 138, 212)); setPenColor(RGB(0, 138, 212)); for (int j = 0; j < 12; j++) { if (j % 3) { x = -r_dial * sin((double)j * 30 / 180 * PI) + cntX; y = r_dial * cos((double)j * 30 / 180 * PI) + cntY; ellipse(x - r2, y - r2, x + r2, y + r2); } } // 时针 setBrushColor(GRAY); setPenColor(GRAY); timel->tm_hour; h = (double)(timel->tm_hour % 12 * 30 + timel->tm_min / 2) / 180 * PI; x = r_h * sin(h) + cntX; y = -r_h * cos(h) + cntY; hour[0][0] = x; hour[0][1] = y; hour[2][0] = cntX - (x - cntX) / 10; hour[2][1] = cntY - (y - cntY) / 10; hour[1][0] = cntX + (hour[2][1] - cntY) / 2; hour[1][1] = cntY - (hour[2][0] - cntX) / 2; hour[3][0] = cntX - (hour[2][1] - cntY) / 2; hour[3][1] = cntY + (hour[2][0] - cntX) / 2; polygon(hour, 4); // 分针 setBrushColor(GRAY); setPenColor(GRAY); min = (double)timel->tm_min * 6 / 180 * PI; x = r_min * sin(min) + cntX; y = -r_min * cos(min) + cntY; minute[0][0] = x; minute[0][1] = y; minute[2][0] = cntX - (x - cntX) / 10; minute[2][1] = cntY - (y - cntY) / 10; minute[1][0] = cntX + (minute[2][1] - cntY) / 2; minute[1][1] = cntY - (minute[2][0] - cntX) / 2; minute[3][0] = cntX - (minute[2][1] - cntY) / 2; minute[3][1] = cntY + (minute[2][0] - cntX) / 2; polygon(minute, 4); // 秒针 setBrushColor(GREEN); setPenColor(GREEN); sec = (double)timel->tm_sec * 6 / 180 * PI; x = r_sec * sin(sec) + cntX; y = -r_sec * cos(sec) + cntY; second[0][0] = x; second[0][1] = y; second[2][0] = cntX - (x - cntX) / 10; second[2][1] = cntY - (y - cntY) / 10; second[1][0] = cntX + (second[2][1] - cntY) / 4; second[1][1] = cntY - (second[2][0] - cntX) / 4; second[3][0] = cntX - (second[2][1] - cntY) / 4; second[3][1] = cntY + (second[2][0] - cntX) / 4; polygon(second, 4); endPaint(); return; }
画钟表的时候,只要注意一点,后画的会覆盖先画的点,所以可以参考实际的钟表结构,越靠上的零件越后画。
简单说一下操作流程:
1. 表盘,没啥说的一个大圆,这里用ellipse函数,指定椭圆外切矩形的左上角和右下角坐标。
2. 表盘刻度,一堆大小圆,十二等分圆弧,找到刻度圆的中心点,然后同样是画圆。
3. 指针,这里试图模仿真实指针的形状,是一个四边形(指针前后都是尖,后面的尖比较小),然后调用ploygon函数,需要以数组形式依次提供四个顶点坐标,以及坐标点个数。
4. 当前时间是,从time.h头文件调用localtime函数,获取所在电脑的时间,根据这个时间,计算三个指针的朝向。
5. 每次更新时要先清空之前的绘图,调用clearDevice函数。
之后就是怎么让钟表动起来了,我们需要一个定时器事件函数timerEvent:
void timerEvent(int tid) { picture_a_minute(tid); }
当然这个定时器要做的唯一一件事就是画表,但是为了方便以后加别的功能,还是分离出了绘图函数。
这里的tid是定时器的id,可以同时存在多个定时器,每个定时器可以有不同的“tick”间隔,推荐从0开始依次增大的id。
启动和关闭计时器:
startTimer(int tid, int tick),是启动tid定时器,每隔tick个毫秒的时间执行一次事件调用,误差在10ms以内。
cancelTimer(int tid),关闭tid计时器。
其实有了这三个函数,钟表就可以工作了,但是还没涉及到事件处理,所以增加了一个没什么用的“暂停功能”。
下面要实现的功能就是,按一下空格,钟表画面静止,再按一下,恢复正常运行。
那么我们要先在Setup函数中注册按键事件,之后我们需要一个事件函数 keyEvent:
typedef enum { TIMER_booting, TIMER_working, TIMER_shutting, TIMER_free, }TIMER_STAT; void keyEvent(int key, int event) { if (key == VK_SPACE) { if ((timerflag == TIMER_working) && (event == KEY_DOWN)) { timerflag = TIMER_shutting; cancelTimer(0); } else if ((timerflag == TIMER_shutting) && (event == KEY_UP)) { timerflag = TIMER_free; } else if ((timerflag == TIMER_free) && (event == KEY_DOWN)) { timerflag = TIMER_booting; startTimer(0, 100); } else if ((timerflag == TIMER_booting) && (event == KEY_UP)) { timerflag = TIMER_working; } } }
事件函数要求有两个参数,一个是按键代码key,另一个是事件代码event,也就是按下,或弹起。
事件的处理有个棘手的地方,我每次按下SPACE,他不是调用一次,是一直不停的调用
keyEvent(SPACE,KEY_DOWN),直到我松开,他在调用一次
keyEvent(SPACE,KEY_UP)。
为了处理这些“多余”的事件,我利用上面的四个状态码TIMER_STAT的四个enum量,标记当前计时器以及按键所处的状态。
1. TIMER_booting:正在启动,已经处理了startTimer,但是按键还没有松开,所以按键松开之前不会再处理SPACE的KEY_DOWN。
2. TIMER_working:Timer正在工作,遇到SPACE的KEY_DOWN就调用cancelTimer,使其停止工作。
3. TIMER_shutting:已经调用了cancelTimer,为了避免重复调用,同booting。只处理KEY_UP。
4. TIMER_free:Timer没在工作,遇到SPACE的KEY_DOWN就调用startTimer,使其开始工作。
这里钟表就完成了,下面是源码:
clock.c on github相关文章推荐
- 利用Flash制作一个可以自由拨动的时钟模具
- 利用NetBeans构建一个 Java 桌面数据库应用程序
- 利用UIGraphics绘制一个会走的时钟
- STM32CubeMX 1 创建一个新工程 利用时钟点亮LED KEIL5 Jlink配置
- 利用canvas绘制一个简单时钟
- 利用Flash制作一个可以自由拨动的时钟模具
- 博客园自定义之博客园公告栏添加时钟——利用canvas画出一个时钟
- 利用定时/计数器T1产生定时时钟,由P1口控制8个发光二极管,使8个提示灯依次一个一个闪动,闪动频率为10次每秒(8个灯亮一遍为一个周期),循环
- 利用Qt来实现一个时钟小程序
- 博客园自定义之博客园公告栏添加时钟——利用canvas画出一个时钟
- 利用NetBeans构建一个 Java 桌面数据库应用程序
- 利用钩子函数来注入DLL的一个具体应用:点击桌面不同图标,播放相应音符
- 利用runnable接口和Thread类 创建一个数字时钟小程序
- 利用canvas.save()、canvas.restore()、canvas.translate()、canvas.rotate()绘制一个时钟表盘
- 实验7 利用51单片机的定时器设计一个时钟
- 利用NetBeans构建一个 Java 桌面数据库应用程序
- 利用NetBeans构建一个 Java 桌面数据库应用程序
- [原]利用CSS3实现模拟一个windows7桌面的页面
- 利用canvas制作一个时钟
- 博客园自定义之博客园公告栏添加时钟——利用canvas画出一个时钟