用 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 驱动的评论。