65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 带有重复功能的日历闹钟类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (31投票s)

2005年7月13日

CPOL

8分钟阅读

viewsIcon

294661

downloadIcon

5430

CAlarmClock 是一个可以生成重复异步闹钟的 C++ 类

引言

我最近开始开发我应用程序中需要的一个部分,该部分需要一个由时钟驱动的调度器,以便在一天、一周或一个月的特定时间执行用户需要的各种任务。因此,我决定尽可能使这个时钟日历闹钟组件具有可移植性,因为我想在两个不同的应用程序中使用它。由于我尝试使这个新类至少能在两个不同的程序中使用,所以将其在线共享并没有增加多少额外工作。希望其他人也能从中受益。

这个C++类具有以下功能,以便允许用户

  • 设置一个闹钟,它将在稍后异步地提醒应用程序。
  • 使用任意时间间隔指定一个重复闹钟,每N天一次、每周的特定几天一次,或每月特定的一天一次。
  • 保证闹钟精度在几毫秒之内,并且所有后续重复闹钟都具有相同的精度。

背景

为了测量时间,CAlarmClock类使用FILETIME结构。据称它在100纳秒内是精确的,但计算机硬件不允许测量如此小的单位。实际上精度远小于100纳秒,但无论精度如何,它只与实际进行时间测量的代码一样精确。我不是Windows API中各种最精确延迟函数方面的专家。我知道有两种测量时间的方法:对我来说非常流行的是SetTimer()方法,以及Windows多媒体API函数timeSetEvent()。据我所读(在文档中),timeSetEvent()非常精确。我选择使用timeSetEvent作为主计时循环的时间基准。

Using the Code

Visual Studio项目需要进行以下更改才能使用CAlarmClock

  • 属性 | 链接器 | 输入 | 附加依赖项中,为发布和调试版本在列表中添加名为"Winmm.lib"的库。
  • 属性 | C/C++ | 代码生成 | 运行时库应设置为使程序使用多线程运行时。包含的示例应用程序分别使用/MD和/MDd进行发布和调试版本。
  • #include "AlarmClock.h".
  • 将以下文件添加到您的项目中:"AlarmClock.cpp"和"AlarmClock.h"。

CAlarmClock类具有以下public成员函数

  • SetAlarm()将调用者的闹钟日期和时间存储到对象的内部闹钟变量中,并将闹钟内部设置为ARMED状态并开始计时。当到达闹钟日期和时间时,将触发调用者的触发器。这些函数有九种重载。可以接受三种不同的输入类型的时间:FILETIME(本地)、SYSTEMTIME(本地)和整数。在各种重载中使用了三种信号类型:回调、窗口消息和事件。
  • SetAlarmAndWait()的作用类似于Windows的Sleep()函数。它可能不适用于长时间延迟,但它可以使对象在某些情况下更易于使用。一旦闹钟开始,就没有办法中止它,因为线程被占用以等待闹钟触发。但是,如果您确实想这样使用,另一个线程可以调用Abort()。我建议不要使用SetAlarmAndWait(),除非是需要短时间延迟的最基本计时循环。此外,重复闹钟不能与此函数一起使用。
  • Abort()停止当前闹钟并将对象置于休眠状态。
  • IsArmed()允许调用者询问时钟是否已设置并处于武装状态,还是处于空闲状态并等待发出另一个SetAlarm()。如果闹钟已设置但尚未到达闹钟时间,IsArmed将返回TRUE。在所有闹钟重复触发并耗尽后,IsArmed最终将返回FALSE
  • SetRepeat()提供了安排一次或多次自动重复所需的信心。请参阅REPEAT_PARMS结构(在AlarmClock.h中)以了解如何指定重复闹钟事件。为了防止CAlarmClock对象混淆,设置REPEAT_PARMS的方式有严格的要求。因此,SetRepeat()会验证REPEAT_PARMS参数的内容。注意:最好在调用SetAlarm()之前调用SetRepeat(),但这不是必需的。我没有测试过在SetAlarm()之后调用SetRepeat()是否有效,但只要您的闹钟时间不过期,它就应该有效。
  • ResetRepeats()在未来重复闹钟发生之前取消它们。下一个已经计划重复的闹钟仍将在到达闹钟时间时发生。Abort()是取消尚未发生的计划闹钟的唯一方法。
  • GetAlarmTime()允许您从对象检索下一个闹钟时间。当对象处于ARMED状态时(请参阅IsArmed),结果FILETIME的内容才有效。
  • 函数:以下SetRepeat_xxx函数取代了SetRepeat函数。使用这些新函数消除了了解REPEAT_PARMS结构的需要。出于兼容性原因,仍然支持SetRepeatGetRepeat,但建议使用(下文)新的SetRepeat_xxx函数。
  • SetRepeat_Interval()取代了SetRepeat(),允许程序员设置基于间隔的重复闹钟:(天、小时、分钟、秒和毫秒)。
  • SetRepeat_Monthly()取代了SetRepeat(),允许程序员设置基于月份某一天的重复闹钟。
  • SetRepeat_Weekdays()取代了SetRepeat(),允许程序员设置基于一个或多个工作日的重复闹钟。

    注意:所有SetRepeat_xxx函数都有一个参数(long nCount),该参数表示要使用的重复次数。零表示永远重复。

  • SetRepeatUntil()
  • GetRepeatUntil()

    注意RepeatUntil函数优先于重复计数和永远重复设置。换句话说,调用RepeatUntil将导致忽略重复计数和永远重复设置。要替换计数,请再次使用新的计数或零(表示永远)调用SetRepeat_xxxx

此类的所有函数都不是虚拟的。如果需要进行子类化,只需在基类中将函数声明为virtual即可。我将其全部保留为非虚拟的,因为我还没有理由自己从这个类派生一个类。

如果需要多个闹钟,请创建多个CAlarmClock对象,并根据需要将它们全部保存在数组中。

关注点

所有时间单位都被视为本地时间,而不是UTC。如果需要UTC,则需要修改类以处理该格式,并且如果要了解本地时间,则需要将其转换为本地时间。这引出了一个问题:如果这个日历闹钟在不同于我的时区(PST -8)的地方工作不正常,请告诉我,以便我可以在我的开发机上设置为该时区并尝试修复它。或者,更好的做法是,进行修改并提供代码给我,以便我可以更新文章中的代码。

该类使用一个工作线程来处理时间测量。工作线程将使用调用者的选择(事件、消息或回调)在闹钟事件期间通知调用者。如果您选择使用回调函数来处理闹钟事件,您需要确保您的回调函数不会花费太多时间,以便工作线程能够恢复时间测量。您不应从回调函数中调用CAlarmClock类的任何成员函数,因为这会导致死锁。

我的Visual Studio .NET 2003 Pro版本中存在一个bug,它会导致DateTimePicker控件在每次我对资源编辑器中的对话框进行任何编辑时,都会自动切换回其默认的日期模式而不是时间模式。现在,控件将在运行时更新,通过修改OnInitDialog中的控件样式位来强制其成为时间格式。

REPEAT_PARMS结构中的永远重复标志优先于重复计数器。

测试应用程序在可执行文件所在的路径(或快捷方式的启动路径)中生成一个日志文件。日志文件用于帮助测试闹钟重复。实时测试日历闹钟单元需要很长时间,因此我别无选择,只能在测试中采取捷径,并对所有内容是否有效做出一些假设。如果您发现任何问题,请给我发电子邮件,或者如果您觉得问题足够严重需要告诉大家,您可以随意在本文章的论坛上发布此bug。

新功能

  • 除了“永远重复”和“重复n次”之外,还有一个新的重复结束条件。它是“重复直到日期”。REPEAT_PARMS中的tRepeatUntil字段表示您希望重复结束的日期/时间。该字段是一个名为UFT的联合体,可以轻松地从FileTime转换为ULONGLONG。初始化此字段的一种方法是使用SYSTEMTIME结构作为模板。填写空白,然后调用SystemTimeToFileTime并将结果存储在REPEAT_PARMS::tRepeatUntil中,或者您现在可以调用SetRepeatUntil(新的,如下所述)并将其传递给SYSTEMTIME的指针。
  • 我添加了一些新函数,消除了手动填写REPEAT_PARMS的需要。它们是:SetRepeat_IntervalSetRepeat_MonthySetRepeat_WeekdaysSetRepeatUntil。现在,您只需调用其中一个即可设置重复参数,CAlarmClock类将自行管理REPEAT_PARMS的内部副本。如果需要,您仍然可以像以前一样使用SetRepeat函数。
  • REPEAT_PARMS被更改,因此消除了Repeat_Days模式。要重复天数,现在您可以使用Repeat_Interval模式并填写REPEAT_PARMS::dd字段(或调用等效函数SetRepeat_Interval)。

历史

  • 05/07/12: 文章提交
  • 06/03/17: 添加了功能并修复了一些bug
© . All rights reserved.