Office中国论坛/Access中国论坛
标题:
【示例】WORD下实现多窗口TASKPAN
[打印本页]
作者:
faunus
时间:
2014-3-22 16:08
标题:
【示例】WORD下实现多窗口TASKPAN
示例代码包含一个应用程序WordDocEditTimer,它维护着Word文档的一个编辑次数列表。本节将详细解释这个应用程序的代码,因为该应用程序演示了前面介绍的所有内容,还包含一些有益的提示。
这个应用程序的一般操作是只要创建或加载了文档,就启动一个链接到文档名称上的计时器。如果关闭文档,该文档的计时器就暂停。如果打开了以前计时的文档,计时器就恢复。另外,如果使用Save As把文档保存为另一个文件名,计时器就更新为使用新文件名。
这个应用程序是一个Word应用程序级的插件,使用一个定制任务面板和一个ribbon菜单。
ribbon菜单包含一个按钮和一个复选框,按钮用于开关任务面板,复选框用于暂停当前活动的文档的计时器。包含这些控件的组添加到Home ribbon选项卡的最后。任务面板显示一组活动的计时器。
效果如下:
[attach]53641[/attach]
作者:
faunus
时间:
2014-3-22 16:10
计时器通过DocumentTimer类来维护:
public class DocumentTimer
{
public Word.Document Document { get; set; }
public DateTime LastActive { get; set; }
public bool IsActive { get; set; }
public TimeSpan EditTime { get; set; }
}
复制代码
这段代码保存了对Microsoft.Office.Interop.Word. Document对象的一个引用、总编辑时间、计时器是否激活,以及它上一次激活的时间。ThisAddIn类维护这些对象的一个集合,这些对象与文档名关联起来:
public partial class ThisAddIn
{
private Dictionary < string, DocumentTimer > documentEditTimes;
复制代码
作者:
faunus
时间:
2014-3-22 16:17
因此,每个计时器都可以通过文档引用或文档名来定位。这是必要的,因为文档引用可以跟踪文档名的变化(这里没有可用于监控文档名变化的事件),文档名允许跟踪关闭、再次打开的文档。
ThisAddIn类还维护一个CustomTaskPane对象列表(如前所述,Word中的每个窗口都需要一个CustomTaskPane对象):
private List < Tools.CustomTaskPane > timerDisplayPanes;
复制代码
插件启动时,ThisAddIn_Startup()方法执行了几个任务。首先它初始化两个集合:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
// Initialize timers and display panels
documentEditTimes = new Dictionary < string, DocumentTimer > ();
timerDisplayPanes = new
List < Microsoft.Office.Tools.CustomTaskPane > ();
复制代码
作者:
faunus
时间:
2014-3-22 16:18
接着通过ApplicationEvents4_Event接口添加几个事件处理程序:
// Add event handlers
Word.ApplicationEvents4_Event eventInterface = this.Application;
eventInterface.DocumentOpen += new Microsoft.Office.Interop.Word
.ApplicationEvents4_DocumentOpenEventHandler(
eventInterface_DocumentOpen);
eventInterface.NewDocument += new Microsoft.Office.Interop.Word
.ApplicationEvents4_NewDocumentEventHandler(
eventInterface_NewDocument);
eventInterface.DocumentBeforeClose += new Microsoft.Office.Interop.Word
.ApplicationEvents4_DocumentBeforeCloseEventHandler(
eventInterface_DocumentBeforeClose);
eventInterface.WindowActivate += new Microsoft.Office.Interop.Word
.ApplicationEvents4_WindowActivateEventHandler(
eventInterface_WindowActivate);
复制代码
作者:
faunus
时间:
2014-3-22 16:18
这些事件处理程序用于监控文档的打开、创建和关闭,并确保ribbon上的Pause复选框保持最新状态。后一个功能是使用WindowsActivate事件跟踪窗口的激活状态来实现的。
在这个事件处理程序中,最后一个任务是开始监控当前文档,把定制的任务面板添加到包含文档的窗口中:
// Start monitoring active document
MonitorDocument(this.Application.ActiveDocument);
AddTaskPaneToWindow(this.Application.ActiveDocument.ActiveWindow);
}
复制代码
作者:
faunus
时间:
2014-3-22 16:19
MonitorDocument()实用方法为文档添加一个计时器:
internal void MonitorDocument(Word.Document Doc)
{
// Monitor doc
documentEditTimes.Add(Doc.Name, new DocumentTimer
{
Document = Doc,
EditTime = new TimeSpan(0),
IsActive = true,
LastActive = DateTime.Now
});
}
复制代码
作者:
faunus
时间:
2014-3-22 16:19
这个方法仅为文档创建了一个新的DocumentTimer对象。DocumentTimer引用文档,其编辑次数是0,且是在当前时间激活的。接着把这个计时器添加到documentEditTimes集合中,并关联到文档名中。
AddTaskPaneToWindow()方法把定制任务面板添加到窗口中。这个方法首先检查已有的任务面板,确保窗口中还没有任务面板。Word中的另一个古怪的特性是如果在加载应用程序后,立即打开一个旧文档,默认的Document1文档就会消失,且不触发关闭事件。在访问包含任务面板的文档窗口时,这可能导致异常,所以该方法还检查表示是否出现该异常的ArgumentNullException:
private void AddTaskPaneToWindow(Word.Window Wn)
{
// Check for task pane in window
Tools.CustomTaskPane docPane = null;
Tools.CustomTaskPane paneToRemove = null;
foreach (Tools.CustomTaskPane pane in timerDisplayPanes)
{
try
{
if (pane.Window == Wn)
{
docPane = pane;
break;
}
}
catch (ArgumentNullException)
{
// pane.Window is null, so document1 has been unloaded.
paneToRemove = pane;
}
}
复制代码
作者:
faunus
时间:
2014-3-22 16:20
如果抛出了一个异常,就从集合中删除错误的任务面板:
// Remove pane if necessary
timerDisplayPanes.Remove(paneToRemove);
复制代码
如果窗口中没有任务面板,这个方法就添加一个:
// Add task pane to doc
if (docPane == null)
{
Tools.CustomTaskPane pane = this.CustomTaskPanes.Add(
new TimerDisplayPane(documentEditTimes),
"Document Edit Timer",
Wn);
timerDisplayPanes.Add(pane);
pane.VisibleChanged +=
new EventHandler(timerDisplayPane_VisibleChanged);
}
}
复制代码
作者:
faunus
时间:
2014-3-22 16:21
添加的任务面板是TimerDisplayPane类的一个实例。稍后介绍这个类。它添加时使用的名称是Document Edit Timer。另外,在调用CustomTaskPanes.Add()方法后,还为得到的CustomTaskPane的VisibleChanged事件添加了一个处理程序,这样在第一次显示任务面板时,可以刷新显示:
private void timerDisplayPane_VisibleChanged(object sender, EventArgs e)
{
// Get task pane and toggle visibility
Tools.CustomTaskPane taskPane = (Tools.CustomTaskPane)sender;
if (taskPane.Visible)
{
TimerDisplayPane timerControl = (TimerDisplayPane)taskPane.Control;
timerControl.RefreshDisplay();
}
}
复制代码
作者:
faunus
时间:
2014-3-22 16:21
TimerDisplayPane类有一个RefreshDisplay()方法,它在上面的代码中调用。这个方法刷新timerControl对象的显示。
接着的代码确保监控所有的文档。首先创建新文档时,调用eventInterface_New- Document()事件处理程序,调用MonitorDocument()和前面介绍过的AddTaskPaneTo- Window()方法监控文档。
private void eventInterface_NewDocument(Word.Document Doc)
{
// Monitor new doc
MonitorDocument(Doc);
AddTaskPaneToWindow(Doc.ActiveWindow);
复制代码
作者:
faunus
时间:
2014-3-22 16:22
新文档在计时器运行时启动,此时这个方法还清除了ribbon菜单中的Pause复选框。这是通过一个实用方法SetPauseStatus()实现的,该方法在ribbon中定义:
// Set checkbox
Globals.Ribbons.TimerRibbon.SetPauseStatus(false);
}
复制代码
作者:
faunus
时间:
2014-3-22 16:22
关闭文档之前,调用eventInterface_DocumentBeforeClose()事件处理程序。这个方法冻结了文档的计时器,更新了总编辑时间,清除了Document引用,删除了文档窗口中的任务面板(使用稍后介绍的RemoveTaskPaneFromWindow()方法),之后关闭窗口。
private void eventInterface_DocumentBeforeClose(Word.Document Doc,
ref bool Cancel)
{
// Freeze timer
documentEditTimes[Doc.Name].EditTime += DateTime.Now
- documentEditTimes[Doc.Name].LastActive;
documentEditTimes[Doc.Name].IsActive = false;
documentEditTimes[Doc.Name].Document = null;
// Remove task pane
RemoveTaskPaneFromWindow(Doc.ActiveWindow);
}
复制代码
作者:
faunus
时间:
2014-3-22 16:23
打开文档时,调用eventInterface_DocumentOpen()方法。该方法完成了许多工作,因为在监控文档之前,这个方法必须查看计时器的名称,确定文档是否已有计时器:
private void eventInterface_DocumentOpen(Word.Document Doc)
{
if (documentEditTimes.ContainsKey(Doc.Name))
{
// Monitor old doc
documentEditTimes[Doc.Name].LastActive = DateTime.Now;
documentEditTimes[Doc.Name].IsActive = true;
documentEditTimes[Doc.Name].Document = Doc;
AddTaskPaneToWindow(Doc.ActiveWindow);
}
复制代码
作者:
faunus
时间:
2014-3-22 16:23
如果还没有监控文档,就为文档配置一个新监控器:
else
{
// Monitor new doc
MonitorDocument(Doc);
AddTaskPaneToWindow(Doc.ActiveWindow);
}
}
复制代码
作者:
faunus
时间:
2014-3-22 16:24
RemoveTaskPaneFromWindow()方法用于从窗口中删除任务面板。其代码首先检查特定的窗口中是否有任务面板:
private void RemoveTaskPaneFromWindow(Word.Window Wn)
{
// Check for task pane in window
Tools.CustomTaskPane docPane = null;
foreach (Tools.CustomTaskPane pane in timerDisplayPanes)
{
if (pane.Window == Wn)
{
docPane = pane;
break;
}
}
复制代码
作者:
faunus
时间:
2014-3-22 16:24
如果找到了任务面板,就调用CustomTaskPanes.Remove()方法删除它。还要从任务面板引用的本地集合中删除它。
// Remove document task pane
if (docPane != null)
{
this.CustomTaskPanes.Remove(docPane);
timerDisplayPanes.Remove(docPane);
}
}
复制代码
作者:
faunus
时间:
2014-3-22 16:24
这个类中的最后一个事件处理程序是eventInterface_WindowActivate(),在激活窗口时调用它。这个方法获得活动文档的计时器,选中ribbon菜单中的复选框,以更新文档的复选框:
private void eventInterface_WindowActivate(Word.Document Doc,
Word.Window Wn)
{
// Ensure pause checkbox in ribbon is accurate, start by getting timer
DocumentTimer documentTimer =
documentEditTimes[this.Application.ActiveDocument.Name];
// Set checkbox
Globals.Ribbons.TimerRibbon.SetPauseStatus(!documentTimer.IsActive);
}
复制代码
作者:
faunus
时间:
2014-3-22 16:25
ThisAddIn的代码还包含两个实用方法。第一个方法ToggleTaskPaneDisplay()用于设置CustomTaskPanes.Visible属性,为当前活动的文档显示或隐藏任务面板。
internal void ToggleTaskPaneDisplay()
{
// Ensure window has task window
AddTaskPaneToWindow(this.Application.ActiveDocument.ActiveWindow);
// toggle document task pane
Tools.CustomTaskPane docPane = null;
foreach (Tools.CustomTaskPane pane in timerDisplayPanes)
{
if (pane.Window == this.Application.ActiveDocument.ActiveWindow)
{
docPane = pane;
break;
}
}
docPane.Visible = !docPane.Visible;
}
复制代码
作者:
faunus
时间:
2014-3-22 16:25
上述代码中的ToggleTaskPaneDisplay()方法由ribbon控件上的事件处理程序调用,如后面所述。
最后,该类有另一个从ribbon菜单中调用的方法,它允许ribbon控件暂停或恢复文档的计时器:
internal void PauseOrResumeTimer(bool pause)
{
// Get timer
DocumentTimer documentTimer =
documentEditTimes[this.Application.ActiveDocument.Name];
if (pause & & documentTimer.IsActive)
{
// Freeze timer
documentTimer.EditTime += DateTime.Now - documentTimer.LastActive;
documentTimer.IsActive = false;
}
else if (!pause & & !documentTimer.IsActive)
{
// Resume timer
documentTimer.IsActive = true;
documentTimer.LastActive = DateTime.Now;
}
}
}
复制代码
作者:
faunus
时间:
2014-3-22 16:26
这个类定义中的其他代码是Shutdown的空事件处理程序以及VSTO为关联Startup和Shutdown事件处理程序而生成的代码。
接着布置项目中的ribbon,即TimerRibbon,如图所示。
作者:
faunus
时间:
2014-3-22 16:26
这个ribbon包含一个RibbonButton、一个RibbonSeparator、一个RibbonCheckBox和一个DialogBoxLauncher。按钮使用大显示样式,其OfficeImageId设置为StartAfterPrevious,显示如图40-13所示的钟表图像。(这些图像在设计期间不可见)。ribbon使用TabHome选项卡类型,其内容追加到Home选项卡上。
ribbon有3个事件处理程序,每个处理程序都调用前面介绍的ThisAddIn中的一个实用方法:
private void group1_DialogLauncherClick(object sender,
RibbonControlEventArgs e)
{
// Show or hide task pane
Globals.ThisAddIn.ToggleTaskPaneDisplay();
}
private void pauseCheckBox_Click(object sender, RibbonControlEventArgs e)
{
// Pause timer
Globals.ThisAddIn.PauseOrResumeTimer(pauseCheckBox.Checked);
}
private void toggleDisplayButton_Click(object sender,
RibbonControlEventArgs e)
{
// Show or hide task pane
Globals.ThisAddIn.ToggleTaskPaneDisplay();
}
复制代码
作者:
faunus
时间:
2014-3-22 16:26
ribbon还包含自己的实用方法SetPauseStatus(),如前所述,该方法由ThisAddIn中的代码调用,以选中复选框或取消复选框的选中。
internal void SetPauseStatus(bool isPaused)
{
// Ensure checkbox is accurate
pauseCheckBox.Checked = isPaused;
}
复制代码
作者:
faunus
时间:
2014-3-22 17:30
这个解决方案中的另一个组件是任务面板中使用的TimerDisplayPane用户控件,这个控件的布局如图
作者:
faunus
时间:
2014-3-22 17:30
这个控件包含一个按钮、一个标签和一个列表框--这些都是很普通的显示控件,也可以用更漂亮的WPF控件替代它们。
该控件的代码保存了对文档计时器的一个本地引用,该引用在构造函数中设置:
public partial class TimerDisplayPane : UserControl
{
private Dictionary < string, DocumentTimer > documentEditTimes;
public TimerDisplayPane()
{
InitializeComponent();
}
public TimerDisplayPane(Dictionary < string, DocumentTimer >
documentEditTimes) : this()
{
// Store reference to edit times
this.documentEditTimes = documentEditTimes;
}
复制代码
作者:
faunus
时间:
2014-3-22 17:30
按钮事件处理程序调用RefreshDisplay()方法刷新计时器的显示:
private void refreshButton_Click(object sender, EventArgs e)
{
RefreshDisplay();
}
复制代码
作者:
faunus
时间:
2014-3-22 17:31
RefreshDisplay()方法也从ThisAddIn中调用,如前所述。考虑到该方法的任务,这是一个相当复杂的方法,它还检查被监控文档的列表,与已加载文档的列表比较,并解决出现的问题。这段代码在VSTO应用程序中常常是必不可少的,因为COM Office对象模型的接口偶尔不能像期望的那样工作。这里的规则是防御式编码。
该方法首先清除timerList列表框中的当前计时器列表:
internal void RefreshDisplay()
{
// Clear existing list
this.timerList.Items.Clear();
复制代码
作者:
faunus
时间:
2014-3-22 17:31
接着检查监控器。这个方法迭代Globals.ThisAddIn.Application.Documents集合中的每个文档,确定文档是被监控、未被监控、或被监控了但在上次刷新时改变了文件名。
要找出被监控的文档,只需比较当前的文档名和键的documentEditTimes集合中的文档名:
// Ensure all docs are monitored
foreach (Word.Document doc in Globals.ThisAddIn.Application.Documents)
{
bool isMonitored = false;
bool requiresNameChange = false;
DocumentTimer oldNameTimer = null;
string oldName = null;
foreach (string documentName in documentEditTimes.Keys)
{
if (doc.Name == documentName)
{
isMonitored = true;
break;
}
复制代码
作者:
faunus
时间:
2014-3-22 17:31
如果文档名不匹配,就比较文档引用,以检测对文档名的修改,如下面的代码所示:
else
{
if (documentEditTimes[documentName].Document == doc)
{
// Monitored, but name changed!
oldName = documentName;
oldNameTimer = documentEditTimes[documentName];
isMonitored = true;
requiresNameChange = true;
break;
}
}
}
复制代码
作者:
faunus
时间:
2014-3-22 17:32
对于未监控的文档,需要创建一个新的监控器:
// Add monitor if not monitored
if (!isMonitored)
{
Globals.ThisAddIn.MonitorDocument(doc);
}
名称改变的文档需要通过用于旧文档的监控器重新关联起来:
// Rename if necessary
if (requiresNameChange)
{
documentEditTimes.Remove(oldName);
documentEditTimes.Add(doc.Name, oldNameTimer);
}
}
复制代码
作者:
faunus
时间:
2014-3-22 17:32
调整了文档编辑计时器后,生成一个列表。代码还会检测引用的文档是否加载了,对于没有加载的文档,把IsActive属性设置为false,暂停该文档的计时器。这也是防御性编程方式:
// Create new list
foreach (string documentName in documentEditTimes.Keys)
{
// Check to see if doc is still loaded
bool isLoaded = false;
foreach (Word.Document doc in
Globals.ThisAddIn.Application.Documents)
{
if (doc.Name == documentName)
{
isLoaded = true;
break;
}
}
if (!isLoaded)
{
documentEditTimes[documentName].IsActive = false;
documentEditTimes[documentName].Document = null;
}
复制代码
作者:
faunus
时间:
2014-3-22 17:32
对于每个监控器,把一个列表项添加到列表框中,其中包含了文档名和总编辑时间:
// Add item
this.timerList.Items.Add(string.Format("{0}: {1}", documentName,
documentEditTimes[documentName].EditTime +
(documentEditTimes[documentName].IsActive ?
(DateTime.Now - documentEditTimes[documentName].LastActive) :
new TimeSpan(0))));
}
}
}
复制代码
作者:
faunus
时间:
2014-3-22 17:33
这就完成了这个例子中的代码。这个例子说明了如何使用ribbon和任务面板控件,如何维护多个Word文档中的任务面板。
作者:
Amas
时间:
2014-3-22 22:32
[attach]53642[/attach]
自己试了试,基本成功。
环境:Win8.1 + VS2013 + Office2013
可能是代码版本问题,我在VS2013稍许修改了几处,基本能运行。因水平有限,只有理解到这个程度,所以出现莫名其妙错误时不要骂我哟。
作者:
faunus
时间:
2014-3-23 16:22
Amas 发表于 2014-3-22 22:32
自己试了试,基本成功。
环境:Win8.1 + VS2013 + Office2013
可能是代码版本问题,我在VS2013稍许 ...
做得非常之好,写个教程吧..
欢迎光临 Office中国论坛/Access中国论坛 (http://www.office-cn.net/)
Powered by Discuz! X3.3