探索 Word 2007 开发(三):管理侧栏
Written by Allen Lee
问题再现
我在上一回提到了"我的博客"侧边栏的一个问题,现在来看看到底是什么回事。首先,依次打开《探索Word 2007开发(一):我的博客》和《探索Word 2007开发(一):扩展Ribbon》两篇文章;然后,点击任意一篇文章上的My Blogs按钮。有趣的事情发生了,两篇文章上的My Blogs按钮都处于按下状态,却只有《探索Word 2007开发(一):我的博客》上出现"我的博客"侧边栏:
Figure 1
对于这个问题,Andrew Whitechapel在《The Anomalous Behavior of Custom Task Panes in Word and InfoPath》里做了解释,而Robert Green也在《Managing Task Panes in Multiple Word and InfoPath Documents》里提出一种解决方案,然而,我希望"我的博客"侧边栏具有Research侧边栏那样的效果,而不是按照Robert的方案来实现的效果。如果你不知道Research侧边栏具有怎样一种效果,我强烈建议你先去体验一番,具体的步骤可以在Robert的文章里找到。
管理侧栏
我曾不止一次同时查看多个打开的Excel窗口,一不小心关闭其中一个致使其它的都关闭了,然而,这种情况不会出现在使用Word的时候。究其原因,乃Andrew和Robert在他们各自的文章里讲述的,Word和Excel使用了不同的窗口模型。
还记得我们是怎样把"我的博客"侧边栏添加进Word窗口的吗?我们是在ThisAddIn_Startup() 方法中使用CustomTaskPaneCollection.Add() 方法把它添加进来的:
细心的你可能已经发现,我们并没有在这里为"我的博客"侧边栏指定父窗口,VSTO将会把它添加进当前窗口。如果这用在Excel,由于各个Excel窗口共享同一个"Document Frame Window",那么每个打开的Excel窗口也将共享这个"我的博客"侧边栏。但若这用在Word,情况将大不相同,第一个启动的Word窗口将独享"我的博客"侧边栏,其它的都无福消受了。怎么解决这个问题?
一种办法就是处理Microsoft.Office.Interop.Word.Application对象的NewDocument和DocumentOpen事件,使用CustomTaskPaneCollection.Add() 方法的重载版本为每个新建或打开的窗口添加"我的博客"侧边栏。当用户关闭窗口时,我们应该销毁与之关联的侧边栏,然而,正如Robert的文章所指出的,Word没有提供DocumentClosed事件,如果我们在DocumentBeforeClose事件的Event Handler里销毁侧边栏,那么当用户取消关闭窗口时,就会发现侧边栏已被莫名其妙地关闭了,这显然是不可接受的。对于这个问题,Robert的方法确实是一个不错的选择。值得提醒的是,如果添加侧边栏的代码只存在于NewDocument和DocumentOpen事件的Event Handler中,那么第一个新建或者打开的窗口都不会有侧边栏,因为这两个事件是在第一个窗口之后才触发的,于是,你需要把代码复制一份放在ThisAddIn_Startup() 方法里面。
增值服务区
另一种办法就是,当用户点击My Blogs按钮时判断当前窗口是否有与之关联的侧边栏,有则显示,无则创建并显示。而侧边栏的销毁则与上一种办法相同。
接下来,我将采用第二种办法来管理"我的博客"侧边栏,稍稍不同的是,我会把侧边栏的创建/获取和销毁等工作外包给MyBlogsPaneManager类而不是直接放在相关的Event Handler里。
MyBlogsPaneManager
对于Code #02,以下几点是需要说明的:
- GetMyBlogsPane() 方法会判断是否存在标题为"My Blogs"、父窗口为当前窗口的侧边栏,有则返回,无则创建。不难看出,该方法总是返回与当前窗口关联的侧边栏。
- CollectMyBlogsPanes() 方法负责回收废弃的侧边栏。
考验脑力区
- CollectMyBlogsPanes() 方法里的递减for循环可以改为递增for循环或者foreach吗?
之前我们在ThisAddIn类里面处理侧边栏的添加和相关事件,现在需要把这些代码迁移到BloggingRibbon类里。
首先,把MyBlogsPaneVisibleChanged() 从ThisAddIn类移到BloggingRibbon类,并做适当调整:
接着,修改MyBlogs按钮的Click事件的Event Handler:
然后,在ThisAddIn类里面处理Microsoft.Office.Interop.Word.Application.DocumentChange事件:
以及在InternalStartup() 方法里添加如下这行代码:
最后,就是删除废弃/多余的代码了。然而,现在的插件处于一个中间状态,它不完整,如果运行的话将会出现一些古怪的行为,接下来将会分析并解决这个问题。
同步状态
细心观察Figure 1,你会发现,两个窗口的My Blogs按钮都处于按下状态,起初我以为这是因为它们都关联到同一个侧边栏,而此时这个侧边栏又处于显示状态,因此它们同时处于按下状态就不足为奇了。然而,事情却不是这么简单,我创建一个单独的插件项目观察多个窗口中按钮的状态,发现如下两个事实:
- 这些按钮的按下/释放状态是共享的。当我按下其中一个窗口的My Blogs按钮并激活其它窗口时,被激活窗口的My Blogs按钮也会变成按下状态。
- 这些按钮的状态同步并非立即发生的。当我按下其中一个窗口的My Blogs按钮但保持当前窗口的活动状态,其它窗口的My Blogs按钮仍然保持释放状态。
不难看出,我们可以在Microsoft.Office.Interop.Word.Application.WindowActivate事件的Event Handler里做一些手脚:
当然,你必须在InternalStartup() 方法里面把它关联该事件:
现在,我们来看看运行效果:
Figure 2
Figure 3
如果你来回切换Figure 2的两个窗口,你会发现My Blogs按钮在每次切换时都会"闪动",这种"闪动"其实是使用WindowActivate事件修正My Blogs按钮状态的一个小小的副作用。细心的读者可能会发现,Word自带的Research侧边栏在同样的情况下也会"闪动"。
至此,侧栏问题的探讨要告一段落了。此时,有人可能会问,Word 2007在兼容MetaWeblog API上有问题,致使我们无法使用它在博客园发布带图片的文章,那么为什么我还要花费这么多精力来对它进行扩展呢?正如很多人所知道的,这个问题是由Word 2007的一个bug所致的,坊间也流传一些有效的解决办法,这恰恰说明了大家不愿放弃Word这个强大的编辑工具。下一回,我将会从Word 2007的扩展特性入手,探讨另一种可能的解决办法。