用 Outlook 对象模型辅助学术日历管理

题图:在 UCSB 第一学期我的选课(待定)
题图:在 UCSB 第一学期我的选课(待定)

引言

阅读本文时需要接受以下观点:

  • Windows 桌面版的 Outlook 是最方便编程管理的邮件、人脉、日历、任务系列服务的客户端软件
  • Exchange Server 是最佳的该系列服务的后端
  • Exchange 协议是最佳的该系列服务的同步协议

本科开始的时候我决定用日历管理我的学术日历。这里有几个考虑:

  • 使用反复事件(recurring appointment)是不好的,因为有的时候课程会取消或者换时间(基于教授的安排)。修改一个事件系列(series)里面的特例是非常麻烦的。与之相对,每节课使用单独的事件修改起来会更简单。中国的一些节假日有调休,另一些是略过这一天,所以也会有修改的需要。
  • 因为生病或其他原因没有参加的课程,我会把状态从“忙”改成“空闲”并加上注记,这种情况下单独的事件也更方便。Vnox 则把取消的事件放入另一个日历里面,但是一个事件系列只能同属于一个日历,不能单独挪动一个实例(instance),必须删除一个实例再在另一个日历里面加上一个单独的事件。这种复杂的操作非常难正确实施。
  • 我需要为一个学期里的每一周都建立一个事件来标明学术周号。

VBA 写的 Outlook 宏

我忘记第一学年的时候我是怎么做的了,可能是手工管理的。至少从 2015 年 9 月开始,我使用 Outlook 对象模型来辅助管理。彼时我用 VBA 写了一个 Outlook 宏来创建课程的事件。你可以在 GitHub Gist 上 查看我的 Outlook 宏

代码中 AskForTermStartsOn 会交互式要求用户输入学期开始的日期,这个日期 必须是星期一。输入的日期会被存下来,所以不是每次都需要重新输入。另一个方法 AskForCurriculumCalendar 要求用户选择一个日历文件夹来存放日历事件。

重头戏是 CreateCourse 方法。它会先调用两个 AskFor 方法,让配置进来(如果之前已经写过,默认选项是“采用之前的设置”)。接着它提示用户输入课程的名字(对应于事件的标题)、描述(可选,对应于事件的详细信息)和地点(对应于事件的地点),然后要求用户循环输入 1-10,Mon,8:00-9:50 这种格式的字符串(分别表示上课的周数、星期几和时间),输入空字符串(或者点“取消”)来结束循环。接着该方法总结这个课程的信息,并询问用户是否要创建课程的事件,如果用户同意,则创建事件。

还有一个趣闻:在我的 Outlook.com 邮箱切换到 Exchange 之前,同步协议是 Exchange ActiveSync,使用起来有一些乱七八糟的小毛病,比如手工快速创建好几个日历事件会导致同步失败且需要删除 pst/ost 文件才能恢复(分批创建多个日历事件也会,所以当一个学期有超过三个课程的时候就会崩掉),以致于我需要先启用“离线模式”,编辑好日历之后再关闭离线模式,一股脑儿同步上去来避免问题。换到 Exchange 之后自然是大不一样,业界事实标准当然是杠杠的!

在这之前,我尝试写过一个叫做 Curriculum Scheduler 的 app,我还做了很多 UI 实现上的探索,比如:

  • 制作日期选择器、错误信息提示器、应用内 toast 通知展示器,并适配高对比度和 Tab 导航
  • 实现 Windows 8.1 内置 app 风格的应用栏“…”提示条
  • 考虑 DPI 缩放对数学上正确的公式的影响
  • 实现隐藏控件用于做焦点接力,这是因为双向绑定中传回视图模型的部分(因此也包括数据验证)是在焦点离开控件之后才发生的,而数据验证结果可以改变“下一个聚焦对象”是谁,因此需要用一个焦点接力器,先允许双向绑定回传,然后移动到恰当的位置(是“完成”按钮,还是“错误信息提示”?)

但是由于一些我当时实在是没时间、没精力、没办法对付的诡异 bug(该 bug 我只能找到工程量足够大的时候的复现方法,无法产生一个足够小的复现),我后来放弃了,走上了使用 Exchange、Outlook 的正道。以下是一些相关微博:

简洁起见,这些链接默认折叠,点击这里显示它们

你可以折叠下面的链接,点击这里折叠它们

你可以折叠上述链接,点击这里折叠它们

一次性的 PowerShell 脚本

2016 年的时候我尝试了一下用 PowerShell,见这里,这个脚本是用来创建周事件的(之前的 Outlook 宏没有实现)。但是由于当时比较懒,也没有什么新的需求,所以就没有实现,继续用的 VBA

不过后来由于我上了法语课(français),我发现 VBA 对 Unicode 支持得不好,所以还是得用 PowerShell,见 这里这里

本科后面的时间我就断断续续地用 VBA 和一次性的脚本建立课程。

较完善的 PowerShell 脚本

升学后,我产生的新的需求,而且由于需求会一直存在,所以就用 PowerShell 比较认真地写了一些脚本来管理。总结一下过去遇到的问题和解决思路:

  • VBA 的 Unicode 支持差,用 PowerShell 解决
  • 之前的脚本都是一次性的,要写一些持久能用的脚本解决
  • 之前几乎没用过“课程描述”,所以不用这个了
  • 因为退课和选课交叉进行,有的时候要删除好多个事件挺麻烦的,可以用 Exchange 日历的自定义属性记录一些数据来关联课程和日历事件
  • 之前 VBA 里输入仅在单周进行的课程不方便,现在支持“打印页数”语法来输入周数
  • 之前 VBA 写死了每周从周一开始且仅支持 20 周(清华是这样的),但是 UCSB 里一个学期的周号是 0 到 10 或 1 到 10,且每周从周日开始,新的脚本支持两种模式
  • 之前 VBA 写死了事件提示是开始前 15 分钟,是因为在清华骑车 15 分钟总是可以从宿舍到教室,但 UCSB(几乎所有美国大学)的情况和这个不一样,新的脚本支持自定义提醒时间
  • 之前 VBA 单个课程必须具有相同的标题和地点,习题课是作为单独的课程呈现的,新的脚本支持把习题课作为课程的一部分(可以重写特定一组课节的标题、地点和提醒时间)

新的脚本托管在我的 GitHub 仓库 PowerShellThingies 里面。发微博臭美肯定 也是少不了的

总结

下表总结了我实现的那些代码的功能。

方法 > VBA PowerShell 一次性 PowerShell 脚本
创建周事件 未实现 自己实现 实现
创建课程事件 实现 自己实现 实现
周输入支持 连续区间、数次添加 自己实现 “打印页数”输入格式
周数支持 仅支持 20 周(清华) 自己实现 支持任意周编号上下界
星期起点 星期一 自己实现 星期一或星期日
Unicode
交互式输入 实现 自己写代码 实现
数据保存 未实现 未实现 实现
关联事件和课程 未实现 未实现 实现

未来说不定还会有新的需求加上来,到时候再看吧。

请启用 JavaScript 来查看由 Disqus 驱动的评论。