MS Outlook 风格日期编辑控件/日期选择器






4.92/5 (13投票s)
模拟 MS Outlook 的日期编辑/选择控件。
屏幕截图
我如何得到这个解决方案
我目前正在开发的项目是数据录入密集型的,我利用这个机会对我们正在开发的应用进行了广泛的可用性测试。 我从这次经历中学到了很多,尽管我认为我能接触到的用户样本有些有限。
在用户测试过程中,我发现用户在使用应用程序时遇到了许多问题。 用户遇到的一个问题是输入日期和时间。 最初,该应用程序使用了一个掩码编辑控件。 我为不同的用户尝试了各种掩码,基本上发现任何用户都可能以多种可能的方式输入日期。 我本可以将其视为一个培训问题,但由于此应用程序将在全国(美国)范围内发布,这不是一个好的选择。 我尝试了日期时间选择器控件,却发现这大大减慢了数据录入的速度。
我开始查看其他应用程序以及它们如何允许输入日期/时间。 我专注于 MS Outlook,因为它具有广泛的吸引力(或至少广泛的分发和使用)。 我发现 MS Outlook 允许以几乎任何有效格式输入日期。 我还发现它还允许一些有趣的日期输入情况。 尝试在 MS Outlook 的日期输入控件中输入“从现在起 2 周”,还尝试使用“圣诞节”或“圣诞节前 2 周”。 令我惊讶的是,Outlook 正确计算了日期。 它还处理了一些日期计算情况,例如“从现在起 2 周 + 3 天”。
我在网上寻找一个具有这种功能的控件,但徒劳无功。 由于我的日期输入问题很重要,我花了一天时间尝试模拟 MS Outlook 的功能。 这是这项努力的结果。
我的解决方案
我的应用程序是一个 MFC 应用,所以我决定派生一个类自 COleDateTime 并重新实现 ParseDateTime 函数来扩展其功能。 我创建了一个 COleDateTimeEx
类。 这个类实现了 MS Outlook 控件的许多功能(虽然不是全部)。 一旦我实现了这个,我就编写了一个 CEdit 派生的类(CFPSDateTimeCtrl
)来利用 COleDateTimeEx
类。 CFPSDateTimeCtrl
控件允许用户以开放式的方式输入日期,然后在控件失去焦点时,将输入的日期重新格式化为特定的格式。
它的功能
核心文本解析器完全包含在 COleDateTimeEx
类中。 这个类可以处理普通 COleDateTime 类可以解析的所有情况,以及以下情况。
日期情况 MMDDYY 或 YYMMDD 示例:091601,010123 MMDDYYYY 示例:09162001 “今天”或“现在” “昨天” “前天” “明天” “后天” “下周” 今天起 7 天 “上周” 7 天前 “圣诞节”或“xmas”或 “x-mass”或“xmass”或“x mass” 当年 12 月 25 日(如果在此日期之前)或次年 12 月 25 日。 “平安夜”或“xmas eve”或“x-mas eve”或“x-mass eve”或“xmass eve”或“x mass eve” 当年 12 月 24 日(如果在此日期之前)或次年 12 月 24 日。 * 其他已处理的节日:新年、除夕、独立日、复活节、耶稣受难日、圣灰星期三、退伍军人节、阵亡将士纪念日、劳动节、哥伦布日、感恩节和总统日。 “上一个[星期几]” 示例:“上周日”或“上周二”等。 “下一个[星期几]” 示例:“下周日”或“下周二”等。 “[X] 天前” 注意:也适用于周、月和年 “[X] 天后” 注意:也适用于周、月和年 “[X] [星期几] 在 [月份]” 示例:“12 月的第 2 个星期日”、“1 月的第 1 个星期二”等。 “[X] 天后 [N]” 或 “[X 天后 [N]” 示例:“圣诞节后 2 天”、“2002 年 12 月 1 日后 2 天”。
注意:也适用于周、月和年。“[X] 天前 [N]” 示例:“圣诞节前 2 天”、“2001 年 12 月 1 日前 2 天”。
注意:也适用于周、月和年。“在 [X] 天内” 示例:“12 天内”、“30 天内”等。
注意:也适用于周、月和年。“[X] [星期几]Ago” 示例:“2 周前的星期日”、“4 周前的星期五” “[X] [星期几]From Now” 示例:“从现在起 2 周的星期日”、“从现在起 4 周的星期五” 简单的日期算术情况。 示例:“圣诞节 + 2 天”、“新年 + 2 天”等。
示例:“万圣节 - 2 周”等。时间情况 “中午” 下午 12:00 “午夜” 凌晨 12:00 “XXX”或“XXXX” 示例:“430”、“515”、“1625”、“0710”等。 “X”或“XX” 示例:“6”、“7”、“11”等。 “[X] 点钟” 示例:6 点
注意:也适用于“oclock”和“o clock”“[X] 分钟后”或“[X] 小时后” 示例:“2 小时后”、“15 分钟后”等。 “[X] 分钟前”或“[X] 小时前” 示例:“2 小时前”、“15 分钟前”等。 “[X] 分钟到 [XXX]” 或 “[X] 分钟至 [XXX]” 或“[X] 至 [XXX]” 或“[X] 到 [XXX]” 示例:“15 分钟到中午”、“20 分钟至 6 点”、“10 分钟至 1300” “[X] 分钟后 [XXX]” 或“[X] 分钟后 [XXX]” 或“[X] 后 [XXX]” 或“[X] 过 [XXX]” 示例:“6 点后 15 分钟”、“中午过 20 分钟” “半过 [XXX]” 示例:“6 点半”
无法做到的事情
MS Outlook 控件允许进行复杂的日期计算(例如“从现在起 2 天 + 2 周 + 1 个月”)。 我的实现不处理这些(尽管支持一些有限的算术)。
更重要的是,我的类仅限于英语。 为了支持其他语言,需要编写一个全新的解析函数。 由于我的应用程序不需要这个,我没有尝试这样做。
此类不完全支持 UNICODE,尽管我认为使其支持 UNICODE 并不困难。 同样,由于我的应用程序不需要这个,我没有尝试这样做。
如何使用
使用此代码非常简单。 如果您只想使用解析器而不是编辑控件,则只需使用 COleDateTimeEx
代替 COleDateTime。 ParseDateTime 函数已重新实现,以处理新情况。
如果您想使用编辑控件,最简单的方法是:
1. 像平常一样设置对话框/窗体资源,并在您想要日期编辑控件的位置放置一个编辑控件。
2. 将 IDB_DATEPICKER_BUTTON
位图(来自演示项目)添加到您的项目中。
3. 向您的对话框/视图/控件类添加一个类型为 CFPSDateTimeCtrl
的成员。
MyDialog.h // // a member of type CFPSDateTimeCtrl to be attached to an edit control CFPSDateTimeCtrl m_wndDate; CFPSDateTimeCtrl m_wndTime;
4. 在 OnInitDialog
(或 OnInitialUpdate
或 OnCreate
)函数中调用 AttachEdit
成员函数来子类化现有编辑控件。 您可能还想调用 SetParserOption
和 SetDisplayFormat
函数。 默认的解析器选项尝试日期和时间解析。 默认显示格式为 %m%/d/%Y。
MyDialog.cpp // BOOL CMyDialog::OnInitDialog() { ... whatever else m_wndDate.SetShowPickerButton(TRUE); m_wndDate.AttachEdit(this, IDC_DATE); m_wndDate.SetParserOption(VAR_DATEVALUEONLY); m_wndDate.SetDisplayFormat("%m/%d/%Y"); m_wndTime.AttachEdit(this, IDC_TIME); m_wndTime.SetParserOption(VAR_TIMEVALUEONLY); m_wndTime.SetDisplayFormat("%I:%M %p"); }
就是这样。
我发现它很容易使用,并且至今用户还没有抱怨过。 然而,我使用此代码的应用程序目前处于“后期 alpha”测试阶段。 因此,随着时间的推移,可能会发现一些问题。 如果发现问题,我将尽快更正代码并将更改发布到 CodeProject。
代码
代码的注释不多,但我认为注释足以解释代码。 基本上,COleDateTimeEx
类中有两个关键函数,您可能想查看一下。 这些是“EvaluateInputTextDate”和“EvaluateInputTextTime”函数。 这些函数包含日期和时间情况的实际解析算法。 警告:这些函数基本上是上面列出的情况的硬编码集合。 正如我之前提到的以及在代码中,我没有时间为解析日期文本开发一个基于规则的引擎。
注释
此代码并非完美,也不适合所有情况,但至少对我的项目来说,它受到了非常好的评价(尤其是最终用户)。 我很想听听您对我的实现或代码的任何评论。 谢谢。
2002 年 1 月 26 日引入的更改
添加了日期选择器下拉支持。 有关其外观,请参见上面的屏幕截图。 用于日历的控件可以与此控件分开使用。 我在 CP 上发布了一篇仅包含此控件和演示项目的文章。 更多详情请参见该文章。
日期选择器控件的工作方式与 MS Outlook 日期选择器控件的工作方式非常相似。
我注意到日期选择器选项存在一个问题。 当用户下拉日历控件时,用户有可能点击某些 UI 元素(框架控件),日历控件不会自动隐藏。 在我的应用程序中这没有问题,所以我还没有尝试纠正这个问题。 如果您对此问题有解决方案,请告知我。
2002 年 1 月 16 日引入的更改
添加了方便在代码中使用的类方法。
IsValid() | |
IsSunday() | |
IsMonday() | |
Is[XXX] 其中 XXX 是星期几的名称 | |
IsWeekDay() | |
IsWeekendDay() | |
IsJanuary() | |
IsFebruary() | |
Is[XXX] 其中 XXX 是月份的名称 | |
IsLeapYear() | |
以前,特殊时间解析器总是将数字输入解释为从早上 7 点开始的小时。 例如,如果您输入 8,解析器将其视为上午 8 点,但如果您输入 6,则将其识别为下午 6 点。
Daebi(请参阅下面的讨论串)建议并提供了允许控制此功能的选项的代码。 我结合了这些代码(并进行了一些更改以使代码更统一),使其能够正常工作。 您现在可以使用 SetHourOption 和 SetHourThreshold 函数来控制时间解析器如何处理小时数字。 有关更多详细信息,请参阅代码。
一些用户报告说在输入带 AM/PM 指示符的时间时遇到问题。 这是因为他们输入了“A.M.”或其他变体,而不是“AM”。 标准解析器不理解这些。 我已修改代码以检查 AM、PM 变体并相应地处理它们。
2002 年 1 月 9 日引入的更改
改进了空格处理。
现在支持“next”、“this next”、“last”和“this last”前缀的通用用法。 这允许以“next easter”、“next christmas”、“last christmas”、“last 4th of july”的形式进行数据输入。
添加了对年份后缀情况的支持,如“easter 2005”、“halloween 2005”等。
在 CFPSDateTimeCtrl 类中添加了方法。
SetDate
GetDate
最后,添加了对快捷方式的支持。 有许多内置的快捷方式,但可以以编程方式删除或修改它们。 当前支持的快捷方式如下。
T | 今天 |
N | 现在 |
Y | 昨天 |
TM | 明天 |
NW | 下周 |
NM | 下个月 |
LW | 上周 |
LM | 上个月 |
DY | 前天 |
DT | 后天 |
X | 圣诞节 |
XE | 平安夜 |
E | 复活节 |
GF | 耶稣受难日 |
AW | 圣灰星期三 |
NY | 新年 |
NYE | 除夕 |
J4 | 7 月 4 日 |
MD | 阵亡将士纪念日 |
LD | 劳动节 |
VD | 退伍军人节 |
CX | 哥伦布日 |
TH | 感恩节 |
PD | 总统日 |
更新
2002-01-07A | 更正了演示 zip 文件,以包含 \res 目录中的文件 |
2002-01-07B | 感谢 Richard Jones 指出依赖 ParseDateTime 和区域设置的缺陷。修改代码以使用 SetDate 而不是 ParseDateTime。似乎解决了问题。将使用多种区域设置进行测试。 |
01-09-2002 | 感谢 Rick Crone 指出需要快捷方式。(有关更多信息,请参阅文章)。此外,Rick 指出了需要更好的空格管理和日期算术算法的增强。 感谢 Pål K Tønder 指出需要通用的“next”和“last”前缀关键字。 添加了对快捷方式的支持。 添加了对涉及节日和预设日期的各种“next”/“last”情况的支持。 添加了对“easter 2005”等情况的支持。 向 CFPSDateTimeCtrl 添加了 Get/SetDate 成员。 |
01-09-2002 | 感谢 Rick Crone 指出需要快捷方式。(有关更多信息,请参阅文章)。此外,Rick 指出了需要更好的空格管理和日期算术算法的增强。 感谢 Pål K Tønder 指出需要通用的“next”和“last”前缀关键字。 添加了对快捷方式的支持。 添加了对涉及节日和预设日期的各种“next”/“last”情况的支持。 添加了对“easter 2005”等情况的支持。 向 CFPSDateTimeCtrl 添加了 Get/SetDate 成员。 |
01-11-2002 | 感谢 daebi 指出时间解析逻辑存在问题。 我已按照说明纠正了时间解析逻辑。 |
01-16-2002 | 感谢 daebi 指出时间截止使用的问题。 daebi 提供了代码以允许各种控件选项用于时间解析(请参阅代码中的说明)。 进一步增强了时间解析,以处理用户输入“A.M.”(及变体)而不是“AM”的情况。 添加了复制构造函数。 添加了 IsValid 方法。 添加了许多其他方便使用的类方法。 (例如 IsSunday()、IsJanuary() 等)。 |
01-26-2002 | 我添加了对日期选择器“模式”的支持。 此功能在控件右侧显示一个按钮,类似于 Outlook。 当用户单击按钮时,会在控件下方显示一个迷你月历控件(类似于标准的日期选择器)。 这个迷你日历控件模拟了 MS Outlook 的日期选择器控件。 迷你日历控件可以独立于日期时间控件使用。 我已将此迷你日历控件的代码和演示项目单独发布在此文章之外。 有关使用迷你日历控件的详细信息,请参阅该文章。 |