您的位置:首页 > 编程语言 > C语言/C++

利用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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息