非窗口实现定时器的方法

时间:2023-01-08 00:14:26

定时器在视窗系统 的程式中的作用不可忽略,也随处可见。设定一个时间间隔每0.5秒或1秒钟刷新一次时钟,这样就能完成一个简单的电子钟程式。在不同的编程工具中定时器的用法也不同,Visual C++中也给我们提供了实现这种功能的方法,而且方法不只一种。在窗口类中是使用定时器比较简单,用SetTimer()设置了定时器之后,并在Class Wizard中添加了WM_TIMER消息映射后,你就能在映射函数OnTimer()中添加代码实现,来定时完成你的任务,而且还支持任意多个定时器,这种方法大家可能都会用。不过在非窗口的类中,使用定时器就没那么简单了,在类消息映射中就未找到OnTimer()方法了,类中也没有hWnd这个属性,SetTimer()也不能象原来那样使用了,下面给出了一种既不破坏类的完整性的同时又能巧妙的使用定时器的方法。

一、实现方法

  在非窗口类中使用定时器,需要了解的知识比较多。首先非窗口类中没有消息映射,也没有象CWnd类具有的SetTimer()方法来设置定时器。没有消息映射,就只能靠我们自己定义的回调函数来处理定时器的消息,因此大家有必要了解一下回调函数的概念。因为回调函数只能用全局函数或静态成员函数来实现,而为了维持类的完整性,又需求使用类的静态成员函数来作为回调函数,所以我们又需要了解一下静态数据成员和静态成员函数的性质。又因为定时器是在我们的程式中产生的,这又需要来管理定时器,所以又用到了映射表类CMap,因此介绍一下CMap的简单用法也是必不可少的。

  所谓回调函数就是按照一定的形式由研发人员定义并编写实现内容,当发生某种事件时由系统或其他函数来调用的函数。使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己编写的一个函数(也就是回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,也就是某种事情发生的时候,利用传递的函数地址调用回调函数,这时研发人员能利用这个机会在回调函数中处理消息或完成一定的操作。回调函数只能是全局函数,或是静态函数,因为这个函数只是在类中使用,所以为了维护类的完整性,我们用类的静态成员函数来做回调函数。

  在C语言中,声明一个数据为静态类型,意味着该变量的生存周期是静态的,即在程式的开始时即分配,到程式终止时才释放。但在C++中,声明一个类中的成员为静态类型,则意味着该类的所有实例只有该成员的一个拷贝。也就是说,不管应用程式中创建了这个类的多少个对象,其静态成员只有一个副本,该副本为这个类的所有对象实例所共享,而对于非静态成员,每个类对象实例都有自己的拷贝。例如一个公司职员类的定义如下: 

class CPerson
{
 public:
  CString szName;
  static CString szCompanyName;
  CPerson();
  virtual ~CPerson();
};


  对于同一家公司员工,每个人都有不同的姓名,不过他们的公司名字是相同的,所以就能用一个静态类型来保存,这样所有的员工都共享这个公司名称,只要一位员工更新了公司名称,则所有员工的公司名称就被更新了。

  静态成员被当作该类类型的全局对象,能把一个静态数据成员和静态成员函数当成全局变量和函数那样去存储和访问,但又被隐藏在类的内部,并且清晰地和这个类相联系但又不是全局对象,同全局对象相比,使用静态成员有两个优势: 

  (1) 静态成员没有进入程式的全局名字空间,他属于类,他的名字只在类的范围内有效,因此不存在和程式中其他全局名字冲突的可能性。

  (2) 能实现信息隐藏,并能保持类的完整性,能是private(私有的)成员、public(公有的)成员或protected(保护的)成员,而全局对象不能。

  使用静态数据成员能节省内存,因为他是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是相同,但他的值是能更新的。只要对静态数据成员的值更新一次,就能确保所有对象都能够访问到被更新后的值,这样能提高效率和节省内存空间。 

  在类中将一个成员变量声明为静态的,和声明普通变量的唯一差别就是在其定义前加一个static,象上面的例子中那样声明:static CString szCompanyName;静态数据成员显式初始化和一般数据成员初始化不同。静态数据成员显式初始化的格式如下:

  <数据类型><类名>::<静态数据成员名>=<值>

  对于上面的例子这样初始化:CString CPerson::szCommpanyName = "天极网";

  这表明:

  (1) 初始化在类体外进行,而前面不加static,以免和一般静态变量或对象相混淆。

  (2) 初始化时不加该成员的访问权限控制符private,public等。

  (3) 初始化时使用作用域运算符来标明他所属类,因此,静态数据成员是类的成员,而不是对象的成员。 
  在类的成员函数中能直接引用该类的静态数据成员,而不必使用成员访问操作符。不过在非成员函数中,我们必须以两种方式之一访问静态数据成员。

  (1) 使用成员访问操作符。

  例如:me是CPerson的一个实例,在非成员函数中能这样应用其中的静态数据成员:CString TheCommpanyName = me.CommpanyName;

  (2) 因为类静态数据成员只有一个拷贝,所以他不一定要通过对象或指针来访问。方法二就是用被类名限定修饰的名字直接访问他。当我们不通过类的成员访问操作符访问静态数据成员时,必须指定类名及紧跟其后的域操作符,因为静态成员不是全局对象,所以我们不能在全局域中找到他。如:CString TheCommpanyName = CPerson::CommpanyName;

  顺便说一句静态数据成员更有两个特点:一是静态数据成员的类型能是其所属类,而非静态数据成员只能被声明为该类的对象的指针或引用;二是静态数据成员能被作为类成员函数的缺省实参,而非静态成员不能。

  静态成员函数的声明和普通函数的唯一差别就是在前面加一个static。通常,当前对象的地址(this)是被隐含地传递到被调用的非静态成员函数的。静态成员函数具有类的范围,同非静态成员函数相比,静态成员函数没有this参数,因此他不能访问一般的数据成员,而只能访问静态数据成员、枚举或嵌套类型和其他的静态成员函数。这样使用静态成员函数在速度上能比全局函数有少许的增长,他不仅没有传递this指针所需的额外的花费,而且更有使函数在类内的好处。如果静态成员函数中要引用非静态成员时,可通过对象来引用。我们能用成员访问操作符点(.)和箭头(->)为一个类对象或指向类对象的指针访问静态成员函数,也能用限定修饰名直接访问静态成员函数,而无需声明类对象。

  静态成员函数遵循约束条件如下:(1) 不能用成员选择符(.或->)访问非静态成员;(2) 不能说明为虚函数;(3) 不能和有相同参数类型的非静态成员函同名;(4) 不能声明为const或volatile;(5) 出目前类体外的函数定义不指定关键字static。

  映射表类(CMap)是MFC集合类中的一个模板类,也称作为"字典",就像一种只有两列的表格,一列是关键字,一列是数据项,他们是一一对应的。关键字是唯一的,给出一个关键字,映射表类会非常快找到对应的数据项。映射表的查找是以哈希表的方式进行的,因此在映射表中查找数值项的速度非常快。举个例子来说吧,公司的所有职员都有一个工号和自己的姓名,工号就是姓名的关键字,给出一个工号,就能非常快的找到相应的姓名。映射类最适用于需要根据关键字进行快速检索的场合,我们的程式中就用映射表来保存计时器标志值和类实例指针,用计时器的标志值作为关键字。 

  从上面的叙述能看出来,在类中静态成员函数只能引用静态数据成员和静态成员函数,怎么才能让静态成员函数也能引用非静态的成员函数和成员变量呢?这也是我们后面将会用到的。

  分析一下静态成员函数和非静态成员函数的差别,我们会发现非静态成员函数之所以能访问所有的成员函数和成员变量,是因为他有个隐含的参数this,访问成员函数和成员变量的时候,实际上是在前面添加了个引用的符号"this->",所以我们就能试着将this这个指针作为静态成员函数的一个参数传递进去,这样不就能在静态成员函数中访问所有的成员函数和成员变量了吗?下面给出一个实现的例子:

Person.h文件如下:

class CPerson 
{
 public:
    CString szMotto;      //该实例的一句座右铭
                CPerson* pThis;      //用于保存该实例的指针
                 void GetMotto();      //非静态成员函数,弹出该实例的座右铭 
    static void GetMottoStaic(CPerson* pPerson);//静态成员函数,弹出该实例的座右铭
                CPerson();
                virtual ~CPerson();
};

  Person.cpp文件如下:#include "stdafx.h"

#include "Person.h"
CPerson::CPerson()
{
 pThis = this;            this这个指针作为静态成员函数的一个参数传递进去
}

CPerson::~CPerson()
{}

void CPerson::GetMotto()
{
 AfxMessageBox(szMotto);
}

void CPerson::GetMottoStaic(CPerson* pPerson)
{
 pPerson->GetMotto();
}

  在需要的地方就能如下访问静态成员函数:

m_Person.szMotto = "我的座右铭是:这是由静态函数访问非静态函数的结果!";
m_Person.GetMottoStaic(m_Person.pThis);

  其实这个例子在实际上是没有什么意义的,这样做的目的只是为了演示怎么实现这个方法而已。

  视窗系统提供了定时器,帮助我们编写定期发送消息的程式。定时器一般通过一下两中方式通知应用程式间隔时间已到。

  ⑴ 给指定窗口发送WM_TIMER消息,也就是下面的给出在窗口类中使用的方法。

  ⑵ 调用一个应用程式定义的回调函数,也就是在非窗口类中使用方法。

  在窗口类中使用定时器比较简单。如果我们想让这个窗口上放置一个电子钟,这样我们必须每1秒或0.5秒钟去更新显示显见。按照下面的步骤,就能完成这个电子钟程式,并且知道怎么在窗口类中使用定时器:

  首先做在我们新建项目的主窗口上添加一个Label控件,用来显示时间。接着

  ⑴ 用函数SetTimer设置一个定时器,函数格式如下: UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer) (HWND, UINT, UINT, DWORD));

  这个函数是CWnd类的一个成员函数,其参数意义如下:nIDEvent: 为设定的定时器指定的定时器标志值,设置多个定时器的时候,每个定时器的值都不同,消息处理函数就是通过这个参数来判断是哪个定时器的。这里我们设定为1;nElapse: 指定发送消息的时间间隔,单位是毫秒。这里我们设定为1000,也就是一秒;lpfnTimer: 指定定时器消息由哪个回调函数来执行,如果为空,WM_TIMER将加入到应用程式的消息队列中,并由CWnd类来处理。这里我们设定为NULL。最后代码如下:SetTimer(1,1000,NULL);

  ⑵ 通过Class Wizard给主窗口类添加一个WM_TIMER消息的映射函数,默认为OnTimer(UINT nIDEvent);

  ⑶ 然后我们就能在OnTimer(UINT nIDEvent)的函数实现中添加我们的代码了。参数nIDEvent就是我们先前设定定时器时指定的标志值,在这里我们就能通过他来差别不同的定时器,而作出不同的处理。添加的代码如下: 

switch(nIDEvent)
{
 case 1:
  CTime m_SysTime = CTime::GetCurrentTime();
  SetDlgItemText(IDC_STATIC_TIME,m_SysTime.Format("%Y年%m月%d日 %H:%M:%S"));
  break;
}

  代码中的IDC_STATIC_TIME就是我们先前添加的Label控件的ID。至此,我们的电子钟的程式就完成了。

  在非窗口类中使用定时器就要用到前面我们介绍到的所有知识了。因为是无窗口类,所以我们不能使用在窗口类中用消息映射的方法来设置定时器,这时候就必须要用到回调函数。又因为回调函数是具有一定格式的,他的参数不能由我们自己来决定,所以我们没办法利用参数将this传递进去。可是静态成员函数是能访问静态成员变量的,因此我们能把this保存在一个静态成员变量中,在静态成员函数中就能使用该指针,对于只有一个实例的指针,这种方法还是行的通的,由于在一个类中该静态成员变量只有一个拷贝,对于有多个实例的类,我们就不能用区分了。解决的办法就是把定时器标志值作为关键字,类实例的指针作为项,保存在一个静态映射表中,因为是标志值是唯一的,用他就能快速检索出映射表中对应的该实例的指针,因为是静态的,所以回调函数是能访问他们的。
  二、编程步骤 

  1、 启动Visual C++6.0,生成一个基于对话框的应用程式,将该程式命名为"TimeDemo";

  2、 按照程式界面效果图设计对话框,具体设置参见代码部分;

  3、 先通过Class Wizard创建一个非窗口类,选择Generic Class类类型,类名称为CMyTimer,该类的作用是每隔一段时间提醒我们做某件事情,然后用这个类创建三个实例,每个实例以不同的时间间隔提醒我们做不同的事情。

  4、 添加代码,编译运行程式。

   三、程式代码

/////////////////////////////////////////// MyTimer.h: interface for the CMyTimer class.
#if !defined(AFX_MYTIMER_H__D97674D1_B221_49CD_9637_4CBA8C3180CE__INCLUDED_)
#define AFX_MYTIMER_H__D97674D1_B221_49CD_9637_4CBA8C3180CE__INCLUDED_
#include <afxtempl.h>
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class CMyTimer;
typedef CMap<UINT,UINT,CMyTimer*,CMyTimer*> CTimerMap;
class CMyTimer 
{
 public:
    void SetMyTimer(UINT nElapse,CString sz);//设置定时器,nElapse表示时间间隔,sz表示要提示的内容

    void KillMyTimer();//销毁该实例的定时器

    UINT m_nTimerID;//保存该实例的定时器标志值

    CString szContent;//静态数据成员要提示的内容 

    static CTimerMap m_sTimeMap;//静态数据成员,映射表类,用于保存所有的定时器信息

    static void CALLBACK MyTimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime);//静态成员函数,用于处理定时器的消息


  CMyTimer();
  virtual ~CMyTimer();
};
#endif 

///////////////////////////////////// MyTimer.cpp: implementation of the CMyTimer class.
#include "stdafx.h"
#include "TimerDemo.h"
#include "MyTimer.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
CTimerMap CMyTimer::m_sTimeMap;

CMyTimer::CMyTimer()
{
 m_nTimerID = 0;
}

CMyTimer::~CMyTimer()
{}

void CALLBACK CMyTimer::MyTimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime)
{
 CString sz;
 sz.Format("%d号定时器:%s",idEvent,m_sTimeMap[idEvent]->szContent);
 AfxMessageBox(sz);
}

void CMyTimer::SetMyTimer(UINT nElapse,CString sz)
{
 szContent = sz;
 m_nTimerID = SetTimer(NULL,NULL,nElapse,MyTimerProc);
 m_sTimeMap[m_nTimerID] = this;
}

void CMyTimer::KillMyTimer()
{
 KillTimer(NULL,m_nTimerID);
 m_sTimeMap.RemoveKey(m_nTimerID);
}

//////////////////////////////////////////////////////// TimerDemoDlg.h : header file
#if !defined(AFX_TIMERDEMODLG_H__83D29A02_A119_4900_AB57_D6D011547F90__INCLUDED_)
#define AFX_TIMERDEMODLG_H__83D29A02_A119_4900_AB57_D6D011547F90__INCLUDED_
#include "Person.h" // Added by ClassView
#include "MyTimer.h" // Added by ClassView
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

/////////// CTimerDemoDlg dialog
class CTimerDemoDlg : public CDialog
{
 // Construction
 public:
  CMyTimer m_myTimer3;
  CMyTimer m_myTimer2;
  CMyTimer m_myTimer1;
  CPerson m_Person;
  CTimerDemoDlg(CWnd* pParent = NULL); // standard constructor
  //{{AFX_DATA(CTimerDemoDlg)
   enum { IDD = IDD_TIMERDEMO_DIALOG };
   // NOTE: the ClassWizard will add data members here
  /