(转载)Office CommandBar和Control
原文出处:http://hi.baidu.com/huodongtian/blog/item/ae3aa40126c89705728da573.html
Word 2007在外观上和Word 2003比,改动很大。一个叫Ribbon的控件容器取代了过去Office版本中的菜单和工具栏。在Word 2003中,我们可以使用VBA, VSTO, Office Automation等等各种各样的技术,在菜单或者工具栏上添加自定义的按钮,实现我们想要的功能。C#版本的Automation代码,大致如下:
1 //Initial and show Word Application
2
3 Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
4
5 wordApp.Visible = true;
6
7
8
9 //Create a Command Bar
10
11 Office.CommandBar commandBar = wordApp.CommandBars.Add("My Bar",
12
13 Office.MsoBarPosition.msoBarTop, false, true);
14
15 commandBar.Visible = true;
16
17
18
19 //Add a Command Bar button
20
21 Office.CommandBarButton btn = commandBar.Controls.Add(Office.MsoControlType.msoControlButton,
22
23 missing, missing, missing, true) as Office.CommandBarButton;
24
25 btn.Caption = "My Button";
26
27 btn.Tag = "MyButton";
28
29 btn.Style = MsoButtonStyle.msoButtonCaption;
30
31 btn.Click += new _CommandBarButtonEvents_ClickEventHandler(btn_Click);
32
33
34
35 void btn_Click(CommandBarButton Ctrl, ref bool CancelDefault)
36
37 {
38
39 MessageBox.Show("My button is clicked!");
40
41 }
代码很简单,需要注意的地方是,button的Tag属性一定要赋值,还有添加button方法的最后一个参数设置为true,否则,当Word关闭的时候,添加的button将留在Word的工具栏上,下次打开Word会添加新的button,button越来越多,这会让人想砸电脑。(可以通过删了Normal.dot把这些残留button删掉,当然还有其它方法)
以上的代码在Word 2003里跑,毫无疑问,工作正常。但是如果在只安装了Word 2007的机器上跑呢?Word 2007是没有Menu和Toolbar的,只有一个叫Ribbon的东西,Ribbon里面又有很多的Tab,Tab里面再分Group,Group里才是各种各样的控件。测试的结果是,在2007里面,以上的代码运行完全正常!看到的结果是,在Ribbon上多了一个叫Add-Ins的Tab,Tab里是Custom Toolbars组,最里面就是我们添加的My Button按钮了。可见微软兼容了2007的Ribbon和过去版本中的Toolbar。事实上,对于CommandBar在Word对象模型中的结构,2007和2003没有什么大的区别,这也是上面的代码可以在两个Word版本中正常工作的原因。我们看到的Ribbon,只不过基于xml的方式,将工具栏中的控件重新组织并展现出来。如果用VSTO做过Ribbon的扩展开发,就会发现,其实Ribbon的实现,就是用一个xml文件去描述,如何将各个控件显示出来,Label是什么,图片是什么,描述是什么,以及响应事件的回调函数又是什么。这个话题比较远,我以后还会写其他的文章来介绍。
在做Office扩展开发的时候,我们很多很多很多情况下会,必须和工具栏上的按钮打交道。这样,知道每个工具栏,还有按钮的名字就很重要。微软提供了Office2007的System Control ID列表供下载,这极大地方便了程序员。(下载的地址:http://www.microsoft.com/downloads/details.aspx?FamilyID=4329D9E9-4D11-46A5-898D-23E4F331E9AE&displaylang=en ) 这些列举了所有控件ID的excel文件,极其有用,但是也有它的局限性,它是针对于Ribbon的。所以在用xml扩展Ribbon的时候,这是一个很好的参考手册。但是当我们想直接通过CommandBar来实现一些功能的时候,很多控件的名字是对不上号的。
自己动手,丰衣足食,写一个很简单,却又很有用的小程序,把Office 2007的CommandBar和它里面的控件都挖出来,看看它们的真面目:
1 //Create Word Application
2
3 Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
4
5
6
7 //Create a new txt file to record controls' list
8
9 StreamWriter sw = System.IO.File.CreateText(@"C:\Word Command Bar Control List.txt");
10
11
12
13
14
15 //loop through wordApp.CommandBars to get all CommandBars
16
17 foreach (Office.CommandBar cb in wordApp.CommandBars)
18
19 {
20
21 sw.WriteLine(cb.Name);
22
23 //loop through each CommandBar's Controls collection to get all controls
24
25 foreach (Office.CommandBarControl cbc in cb.Controls)
26
27 {
28
29 sw.WriteLine("\t" + cbc.Caption);
30
31 }
32
33 }
上面的代码在C盘生成了一个文本文件,结构式的显示出了所有的CommandBar的名字,还有他们所包含的控件的名字。一共16K,不可能贴在这儿了。得到这些CommandBar和Control的名字有什么用呢?下面举两三个例子来看看,都是在论坛上被问到的问题:
1. 在Office2007里面,我想通过CustomTaskPane(VSTO SE提供的一个新功能,可以将Winform的UserControl填充到Word内的一个板上,实现功能的扩展,该blog上将有文章介绍)上的一个按钮,来最小化Word的Ribbon,无论Ribbon当前的状态怎样。Word 2007的对象模型中,ActiveWindow提供了一个叫ToggleRibbon的方法,但是它只是将Ribbon的状态转换,最小化时最大化,最大化时最小化。怎么办?
自然的想法是要去判断Ribbon现在的状态是什么样的。但是找遍了整个WOM(Word对象模型),都找不到任何属性,指示了现在Ribbon的状态。绝望之中,我loop CommandBars,发现最后一个CommandBar叫Ribbon。原来Ribbon也是CommandBars中的一员啊!把这个CommandBar的高矮胖瘦属性读出来一看,果然,最大化的时候Height属性值为147,最小化的时候值为56。哈哈,问题解决了,主要代码如下。
In ThisAddIn.cs
1 private static Office.CommandBars cbs = null;
2
3
4
5 private void ThisAddIn_Startup(object sender, System.EventArgs e)
6
7 {
8
9 this.CustomTaskPanes.Add(new UserControl1(),"test").Visible = true;
10
11 cbs = this.Application.CommandBars;
12
13 }
14
15
16
17 public static Office.CommandBar returnRibbon()
18
19 {
20
21 foreach (Office.CommandBar cb in cbs)
22
23 {
24
25 if (cb.Name == "Ribbon")
26
27 {
28
29 return cb;
30
31 }
32
33 }
34
35 return null;
36
37 }
In CustomTaskPane’s UserControl1.cs
1 private void button1_Click(object sender, EventArgs e)
2
3 {
4
5 int i = ThisAddIn.returnRibbon().Height;
6
7 if (i == 147)
8
9 {
10
11 Globals.ThisAddIn.Application.ActiveWindow.ToggleRibbon();
12
13 }
14
15 }
2. 客户说,他用VSTO开发了一个文档级应用的Excel文件。在这个Excel文件的工具栏上加了一个按钮,按钮的功能是调用SaveCopyAs方法,将当前的Workbook存一个备份到指定的目录下。但,如果用户将其中的一个Worksheet删除掉,然后直接调用SaveCopyAs(),新存的Workbook打开时报错,代码不被装载了。这个问题和这儿的主题关系不大,详细地解释见:http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2220299&SiteID=1 这里就不多说了。总之,就是因为SaveCopyAs前,当前的Workbook没有保存导致。
自然的想法,SaveCopyAs之前,用代码帮助用户保存一下Workbook。使用Workbook.Save()是不行的,因为它只保存Workbook的内容,不更新Workbook内嵌的manifest。于是我建议说,用SendKeys.Send(“^s”)给Excel发个组合键,让Excel保存当前的文件。他回道:我的程序需要地区差别友好化,MSDN上说了,在Local Setting不一样的情况下,SendKeys.Send会有不可预测的行为。说得也是!虽然根据我们平时的经验,英文版和中文版的Office,保存热键都是Ctrl+S,应该不会存在问题。但是如果键盘不是美式键盘布局呢,也许会出错!这个方法确实有问题。想了想,还是CommandBar来帮忙。打开刚刚生成的文本文件,会看到Save按钮的真名叫&Save,位于Standard工具栏里。得到它的句柄,调用Execute()就搞定了。
this.Application.CommandBars["Standard"].Controls["&Save"].Execute();
3. 第三个例子是今天遇到的新问题。某人想在Word的第一页的页眉插入一个图片,在第一页的页脚插入一条横线。而第一页的页眉页脚,要求和后面页的页眉页脚不同。这个看似很容易办到,因为WOM中提供了一些属性让我们设置,还有添加页眉页脚。他用的代码如下:
1 PageSetup.DifferentFirstPageHeaderFooter = -1;
2
3 Sections[1].Headers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterFirstPage].Shapes.AddPicture(@"C:\Image\abc.jpg", ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
4
5 Sections[1].Footers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterFirstPage].Shapes.AddLine(35f, 500f, 400, 500f, ref missing);
代码看上去完全没有问题,但是很奇怪,跑起来就和预期。页眉的图片插在wdHeaderFooterFirstPage,但是页脚的横线插在了wdHeaderFooterPrimary上。所以现象是:第一页看不见页脚的横线,按Ctrl+Enter多建几个空白页面,会发现后面的页面都会有横线页脚。但,如果代码是在页脚中写入Text或者插入图片,又工作正常,就是用代码不能把横线插进去!下午调试了2个多小时,也没找到为什么。嘿,最后只能替他找个workaround,绕开这个问题。
介于,不通过代码,我们可以很轻松的人工完成上述目的,人工完成无非就是点button。自然的想法,又是CommandBar和它的Control们登场的时候了。在刚刚生成的文本文件搜索Header,会发现所有这个过程中要用到的button,按照顺序去调用它们的Execute方法就可以了。
1 this.Application.ActiveDocument.Sections.First.PageSetup.DifferentFirstPageHeaderFooter = -1; //Set Page to make Firstpage’s Header and Footer different
2
3 //Edit the Header
4
5 this.Application.CommandBars["Header Area Popup"].Controls["Edit &Header"].Execute();
6
7 this.Application.Selection.InlineShapes.AddPicture(@"C:\test.jpg", ref missing, ref missing, ref missing);
8
9 this.Application.CommandBars["Header and Footer"].Controls["&Close"].Execute();
10
11 //Edit the Footer
12
13 this.Application.CommandBars["Footer Area Popup"].Controls["&Edit Footer"].Execute();
14
15 Word.InlineShape line = this.Application.Selection.InlineShapes.AddHorizontalLineStandard(ref missing);
16
17 this.Application.CommandBars["Header and Footer"].Controls["&Close"].Execute();
恩,说了这么多,相信CommandBar的强大性已经逐渐显示出来了。尤其是第三个例子,表现得极为明显。可以看出,几乎所有手动能干的事情,不使用WOM,都可以用CommandBar和它的Control们替我们做。虽然,通常情况下,我不建议,甚至反对这么干,因为效率很低。但是,当遇到WOM有些信息不告诉我们,如1,或者WOM出现诡异Bug的时候,如3,CommandBar是不是让生活美好了很多呢?
原文出处:http://hi.baidu.com/huodongtian/blog/item/ae3aa40126c89705728da573.html
|