利用Acllib写的一个桌面时钟

关于Acllib的一点介绍:

  1. Acllib是一个基于Win32API的函数库,提供了相对较为简单的方式来做Windows程序。
  2. 实际提供了⼀个.c和两个.h,可以在MSVC和Dev C++( MinGW)中使用。
  3. 纯教学用途,但是编程模型和思想可以借鉴

下面是我利用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

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
附件是ACLLib的全部文件,包括源代码和文档及例子程序。 ——MOOC浙江大学C程序设计进阶(翁恺老师) 在Win7或Win8上正常安装的Dev C++,无论是用的MinGW编译器还是TDM编译器都是可以正常使用ACLLib的,但是要注意以下几点: 1. 如果还在使用4.9.9.2的Dev C++,一定要升级到5以上,建议都升级到最新的5.10的版本; 2. 在新建项目的时候选择Windows Application类型; 3. 根据自己机器是32位还是64位来选择编译类型,如果是32位的机器选择MinGW32位方式,如果是64位的机器建议选择TDM的64位方式; 4. 在配置项目的时候,根据32位还是64位选择正确目录下的库文件来加入: 1. 32位下,库文件是: "C:/Program Files/Dev-Cpp/MinGW32/lib/libwinmm.a" "C:/Program Files/Dev-Cpp/MinGW32/lib/libmsimg32.a" "C:/Program Files/Dev-Cpp/MinGW32/lib/libkernel32.a" "C:/Program Files/Dev-Cpp/MinGW32/lib/libuser32.a" "C:/Program Files/Dev-Cpp/MinGW32/lib/libgdi32.a" "C:/Program Files/Dev-Cpp/MinGW32/lib/libole32.a" "C:/Program Files/Dev-Cpp/MinGW32/lib/liboleaut32.a" "C:/Program Files/Dev-Cpp/MinGW32/lib/libuuid.a" 2. 64位下,库文件是: C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib/libwinmm.a C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib/libmsimg32.a C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib/libkernel32.a C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib/libuser32.a C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib/libgdi32.a C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib/libole32.a C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib/liboleaut32.a C:/Program Files/Dev-Cpp/MinGW64/x86_64-w64-mingw32/lib/libuuid.a 5. 最后,如果出现“undefined reference to `TransparentBlt' ”这个错误,两个解决方案: 1. 偷懒的,打开acllib.c,找到“TransparentBlt”所在的行,把整行注释掉; 2. 打开工程配置,找到编译器选项,加入-DWINVER=0x0500。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值