4.2 时钟制作
4.2.1 时钟制作设计方法
模拟时钟设计相对于数字时钟要复杂很多,不但要考虑时间问题,还要考虑时针、分针、秒针的角度变化以及重新绘制等问题。与其他编程工具不同,在LabWindows/CVI 中,没有线类对象与控件,只是在Canvas 控件中存在画线属性,钟表指针绘制完成后,经过下一个时刻需要移动一个角度,则需要重新绘制一个新的指针,旧指针应当及时擦除,因此,时钟指针绘制相对于其他开发工具难度较大。
类似于其他开发工具如Visual Basic 用Line 控件显示水平线、垂直线与斜线,在Canvas 控件中可以绘制各种类型的线。对于指针式钟表而言,需要设置指针坐标的起点X1、Y1 和终点X2、Y2,并且要实时改变指针(线)的长度、位置和倾斜角度,利用Timer 控件定时触发回调事件,查询系统时间并控制指针转动,实现时钟的动态模拟。由于Timer 控件在Windows 操作系统中并不是精确触发的,每次触发的时间间隔不能保证完全相同,因此,只能用它来做定时查询,而不能用作计时器。
对于模拟钟表指针的绘制,则需要利用一些数学算法。实际上,指针转动可以理解为圆的轨迹与角度的关系,如图4-3 所示。
经推导,设原点为(X1, Y1),半径为r,圆上任意点坐标(X2, Y2)与12 点钟方向的顺时针夹角与α角的关系为:
X2 =X1+r×sin α
Y2 =Y1−r×cos α
绘制指针时应注意,秒针采用红色线型且细长,分针为蓝色粗细居中,时针短粗为白色,在采用Canvas 控件绘制线形时,ATTR_PEN_COLOR 与ATTR_PEN_WIDTH 为必须设置的属性。此外,指针每移动一步需要重新绘制一次,前一次绘制的图形则需要擦除,擦除方式为将前一次绘制的直线再以底色(黑色)重新再在原位置绘制一次。
4.2.2 时钟制作程序设计
(1)面板设计
编写一个模拟时钟程序,在面板上显示表盘、秒针、分针、时针和中心轴,并在标题栏实时显示数字时钟。时钟底盘为Canvas 控件,设置为黑色,数字标签使用Text Message 控件,设置颜色为绿色,并设置控件透明背景,中心轴采用Decoration 控件,指针在运行时实时绘制,通过Timer 控件触发使其转动。面板设计如图4-4 所示,面板中主要控件属性设置如表4-4 所示。
图4-3 指针转动轨迹与角度的关系 图4-4 时钟制作面板
表4-4 控件属性设置表
常量名
| 控件类型
| 控件的主要属性
| PANEL
| Panel
| 标题:时钟制作回调函数:PanelCB
| CANVAS
| Canvas
| ( 时钟底盘)
| TIMER
| Timer
| 回调函数:timer
| TEXTMSG_1
| Text Message
| 默认值:1
| TEXTMSG_2
| Text Message
| 默认值:2
| TEXTMSG_3
| Text Message
| 默认值:3
| TEXTMSG_4
| Text Message
| 默认值:4
| TEXTMSG_5
| Text Message
| 默认值:5
| TEXTMSG_6
| Text Message
| 默认值:6
| TEXTMSG_7
| Text Message
| 默认值:7
| TEXTMSG_8
| Text Message
| 默认值:8
| TEXTMSG_9
| Text Message
| 默认值:9
| TEXTMSG_10
| Text Message
| 默认值:10
| TEXTMSG_11
| Text Message
| 默认值:11
| TEXTMSG_12
| Text Message
| 默认值:12
|
(2)程序源代码
//头文件声明
#include <ansi_c.h>
#include <utility.h>
#include <cvirte.h>
#include <userint.h>
#include "时钟制作.h"
static int panelHandle;
//主函数
int main (int argc, char *argv[])
{
int i;
if (InitCVIRTE (0, argv, 0) == 0)
return –1; /* out of memory */
if ((panelHandle = LoadPanel (0, " 时钟制作.uir", PANEL)) < 0)
return –1;
//设置表盘数字底色透明
for (i = 2; i < 14; i ++)
{
SetCtrlAttribute (panelHandle, i, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
}
//调用定时器回调函数
timer (panelHandle, PANEL_TIMER, EVENT_TIMER_TICK, NULL, NULL, NULL);
DisplayPanel (panelHandle);
RunUserInterface ();
DiscardPanel (panelHandle);
return 0;
}
//面板回调函数
int CVICALLBACK PanelCB (int panel, int event, void *callbackData,
int eventData1, int eventData2)
{
switch (event)
{
case EVENT_CLOSE:
QuitUserInterface (0);
break;
}
return 0;
}
//定时器
int CVICALLBACK timer (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
char *timestr;
char CurTime[50];
int Seconds;
int Minutes;
int Hours;
int sdeg;
int mdeg;
int hdeg;
double srad;
double mrad;
double hrad;
//记录表盘指针状态
static int sx2,sy2,soldx2,soldy2;
static int mx2,my2,moldx2,moldy2;
static int hx2,hy2,holdx2,holdy2;
switch (event)
{
case EVENT_TIMER_TICK:
// 获得系统时间
GetSystemTime (&Hours, &Minutes, &Seconds);
// 转换为角度
sdeg = Seconds * 6;
mdeg = Minutes * 6;
hdeg = Hours * 30 + (0.5 * Minutes);
// 设置秒针参数
// 设置Canvas 控件绘图笔宽度
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_WIDTH, 2);
soldx2 = sx2;
soldy2 = sy2;
// 转换为弧度
srad = (3.14 / 180) * sdeg;
// 擦除上一次绘制图形
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_COLOR, VAL_BLACK);
CanvasDrawLine (panelHandle, PANEL_CANVAS, MakePoint (150,150), MakePoint (soldx2,
soldy2));
// 计算指针末端位置
sx2 = 150 + 100 * sin(srad);
sy2 = 150 – 100 * cos(srad);
// 绘制新图形
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_COLOR, VAL_RED);
CanvasDrawLine (panelHandle, PANEL_CANVAS, MakePoint (150,150), MakePoint (sx2,sy2));
// 设置分针参数
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_WIDTH, 4);
moldx2 = mx2;
moldy2 = my2;
mrad = (3.14 / 180) * mdeg;
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_COLOR, VAL_BLACK);
CanvasDrawLine (panelHandle, PANEL_CANVAS, MakePoint (150,150), MakePoint (moldx2,
moldy2));
mx2 = 150 + 80 * sin(mrad);
my2 = 150 – 80 * cos(mrad);
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_COLOR, VAL_BLUE);
CanvasDrawLine (panelHandle, PANEL_CANVAS, MakePoint (150,150), MakePoint (mx2,my2));
// 设置时针参数
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_WIDTH, 6);
holdx2 = hx2;
holdy2 = hy2;
hrad = (3.14 / 180) * hdeg;
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_COLOR, VAL_BLACK);
CanvasDrawLine (panelHandle, PANEL_CANVAS, MakePoint (150,150), MakePoint (holdx2,
holdy2));
hx2 = 150 + 65 * sin(hrad);
hy2 = 150 – 65 * cos(hrad);
SetCtrlAttribute (panelHandle, PANEL_CANVAS, ATTR_PEN_COLOR, VAL_WHITE);
CanvasDrawLine (panelHandle, PANEL_CANVAS, MakePoint (150,150), MakePoint (hx2,hy2));
// 获得当前时间字符串
timestr = TimeStr ();
CurTime[0] = '\0';
strcat (CurTime, " 当前时间:");
strcat (CurTime, timestr);
// 标题栏显示当前时间
SetPanelAttribute (panelHandle, ATTR_TITLE, CurTime);
break;
}
return 0;
}
3:程序注释
①设置透明背景在程序中,采用了SetCtrlAttribute 函数的VAL_TRANSPARENT 属性将Text Message 控件的背景设置为透明。对于12 个数字标签,需要重复设置VAL_TRANSPARENT ,其代码为:
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_1, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_2, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_3, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_4, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_5, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_6, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_7, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_8, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_9, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_10, ATTR_TEXT_BGCOLOR,VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_11, ATTR_TEXT_BGCOLOR,VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_TEXTMSG_12, ATTR_TEXT_BGCOLOR,VAL_TRANSPARENT);
但是,这种设置方式重复工作量较大,代码大量冗余,容易出现错误,可以采用循环结构优化代码设计,即主函数中使用的代码:
for (i = 2; i < 14; i ++)
{
SetCtrlAttribute (panelHandle, i, ATTR_TEXT_BGCOLOR, VAL_TRANSPARENT);
}
采用此种代码方式,需要控制每个Text Message 控件的Tab Order 属性,使自动生成的“时钟制作.h”文件控件常量值遵从一定顺序,即:
#include <userint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define
PANEL
1
#define
PANEL_TEXTMSG_1
2
#define
PANEL_TEXTMSG_2
3
#define
PANEL_TEXTMSG_3
4
#define
PANEL_TEXTMSG_4
5
#define
PANEL_TEXTMSG_5
6
#define
PANEL_TEXTMSG_6
7
#define
PANEL_TEXTMSG_7
8
#define
PANEL_TEXTMSG_8
9
#define
PANEL_TEXTMSG_9
10
#define
PANEL_TEXTMSG_10
11
#define
PANEL_TEXTMSG_11
12
#define
PANEL_TEXTMSG_12
13
#define
PANEL_CANVAS
14
#define
PANEL_TIMER
15
#define
PANEL_DECORATION
16
int CVICALLBACK PanelCB(int panel, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK timer(int panel, int control, int event, void *callbackData, int eventData1, int eventData2); #ifdef __cplusplus
}
#endif
①设置方法为,首先打开“时钟制作.uir”文件使其处于编辑状态,选择菜单Edit→Tab Order..., 弹出Edit Tabbing Order 对话框,如图4-5 所示,按顺序依次点击标签,最后点击OK 按钮完成设置,选择菜单File→Save All,保存所有改动,此时头文件中Text Message 控件将更新为以上顺序,方便程序设计。
4-5 Edit Tabbing Order 对话框
② 界面显示问题
在主函数中还调用了Timer 回调函数,其实,如果不是为了界面的美观设计,也可以将以下代码去掉:
timer (panelHandle, PANEL_TIMER, EVENT_TIMER_TICK, NULL, NULL, NULL);
如果不写入此代码,界面刚显示时没有指针,过一会儿指针才显示出来,这主要是由LabWindows/CVI 函数调用机制所决定的,启动面板逻辑要高于控件逻辑。
③ GetSystemTime 函数
以数字形式获得系统时间。需要注意的是,在Windows API 中也包含GetSystemTime 函数,如果在程序中引用了windows.h 头文件而没有引用utility.h 头文件,可能会出现编译错误。函数原型为:int GetSystemTime (int *Hours, int *Minutes, int *Seconds);
*Hours:系统时间中的小时数,取值范围为0~23。
*Minutes :系统时间中的分钟数,取值范围为0~59。
*Seconds:系统时间中的秒数,取值范围为0~59。
④ TimeStr 函数返回8 字节时间字符串,格式为HH:MM:SS(时分秒)。函数原型为:
char *TimeStr (void);
返回值:返回时间字符串指针。
⑤ CanvasDrawLine 函数
在两个确定点之间绘制直线。绘制直线使用的属性包括:ATTR_PEN_COLOR 、ATTR_PEN_MODE 、ATTR_PEN_WIDTH 和ATTR_PEN_STYLE (当线的粗细大于1 个像素时忽略此设置),用SetCtrlAttribute 函数设置。函数原型为:
int CanvasDrawLine (int Panel_Handle, int Control_ID, Point Start, Point End);
Start :绘制点的起始值。Point 为结构体类型,定义为:
typedef struct
{
intx; inty;
} Point;
如果不想定义一个结构体变量,则可采用如下函数:
Point MakePoint (int x, int y);
MakePoint 函数定义一个二维点,其中参数x 和y 分别是X和Y轴坐标。
(4)运行效果图
点击工具栏中的Debug Project 按钮,程序开始运行,其效果如图4-6 所示。
4-6 运行效果图
相关阅读
《虚拟仪器技术,将“软件就是仪器”进行到底!》 |