无敌的Word CommandBar和它的Control们

                Word 2007在外观上和Word 2003比,改动很大。一个叫Ribbon的控件容器取代了过去Office版本中的菜单和工具栏。在Word 2003中,我们可以使用VBA, VSTO, Office Automation等等各种各样的技术,在菜单或者工具栏上添加自定义的按钮,实现我们想要的功能。C#版本的Automation代码,大致如下:

            //Initial and show Word Application

            Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();

            wordApp.Visible = true;

 

            //Create a Command Bar

            Office.CommandBar commandBar = wordApp.CommandBars.Add("My Bar",

                Office.MsoBarPosition.msoBarTop, false, true);

            commandBar.Visible = true;

 

            //Add a Command Bar button

            Office.CommandBarButton btn = commandBar.Controls.Add(Office.MsoControlType.msoControlButton,

                missing, missing, missing, true) as Office.CommandBarButton;

            btn.Caption = "My Button";

            btn.Tag = "MyButton";

            btn.Style = MsoButtonStyle.msoButtonCaption;

            btn.Click += new _CommandBarButtonEvents_ClickEventHandler(btn_Click);

 

            void btn_Click(CommandBarButton Ctrl, ref bool CancelDefault)

            {

                MessageBox.Show("My button is clicked!");

            }

代码很简单,需要注意的地方是,buttonTag属性一定要赋值,还有添加button方法的最后一个参数设置为true,否则,当Word关闭的时候,添加的button将留在Word的工具栏上,下次打开Word会添加新的buttonbutton越来越多,这会让人想砸电脑。(可以通过删了Normal.dot把这些残留button删掉,当然还有其它方法)

以上的代码在Word 2003里跑,毫无疑问,工作正常。但是如果在只安装了Word 2007的机器上跑呢?Word 2007是没有MenuToolbar的,只有一个叫Ribbon的东西,Ribbon里面又有很多的TabTab里面再分GroupGroup里才是各种各样的控件。测试的结果是,在2007里面,以上的代码运行完全正常!看到的结果是,在Ribbon上多了一个叫Add-InsTabTab里是Custom Toolbars组,最里面就是我们添加的My Button按钮了。可见微软兼容了2007Ribbon和过去版本中的Toolbar。事实上,对于CommandBarWord对象模型中的结构,20072003没有什么大的区别,这也是上面的代码可以在两个Word版本中正常工作的原因。我们看到的Ribbon,只不过基于xml的方式,将工具栏中的控件重新组织并展现出来。如果用VSTO做过Ribbon的扩展开发,就会发现,其实Ribbon的实现,就是用一个xml文件去描述,如何将各个控件显示出来,Label是什么,图片是什么,描述是什么,以及响应事件的回调函数又是什么。这个话题比较远,我以后还会写其他的文章来介绍。

在做Office扩展开发的时候,我们很多很多很多情况下会,必须和工具栏上的按钮打交道。这样,知道每个工具栏,还有按钮的名字就很重要。微软提供了Office2007System Control ID列表供下载,这极大地方便了程序员。(下载的地址:http://www.microsoft.com/downloads/details.aspx?FamilyID=4329D9E9-4D11-46A5-898D-23E4F331E9AE&displaylang=en ) 这些列举了所有控件IDexcel文件,极其有用,但是也有它的局限性,它是针对于Ribbon的。所以在用xml扩展Ribbon的时候,这是一个很好的参考手册。但是当我们想直接通过CommandBar来实现一些功能的时候,很多控件的名字是对不上号的。

自己动手,丰衣足食,写一个很简单,却又很有用的小程序,把Office 2007CommandBar和它里面的控件都挖出来,看看它们的真面目:

            //Create Word Application

            Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();

 

            //Create a new txt file to record controls' list

            StreamWriter sw = System.IO.File.CreateText(@"C:/Word Command Bar Control List.txt");

 

 

            //loop through wordApp.CommandBars to get all CommandBars

            foreach (Office.CommandBar cb in wordApp.CommandBars)

            {

                sw.WriteLine(cb.Name);

                //loop through each CommandBar's Controls collection to get all controls

                foreach (Office.CommandBarControl cbc in cb.Controls)

                {

                    sw.WriteLine("/t" + cbc.Caption);

                }

            }

        上面的代码在C盘生成了一个文本文件,结构式的显示出了所有的CommandBar的名字,还有他们所包含的控件的名字。一共16K,不可能贴在这儿了。得到这些CommandBarControl的名字有什么用呢?下面举两三个例子来看看,都是在论坛上被问到的问题:

1. Office2007里面,我想通过CustomTaskPane(VSTO SE提供的一个新功能,可以将WinformUserControl填充到Word内的一个板上,实现功能的扩展,该blog上将有文章介绍)上的一个按钮,来最小化WordRibbon,无论Ribbon当前的状态怎样。Word 2007的对象模型中,ActiveWindow提供了一个叫ToggleRibbon的方法,但是它只是将Ribbon的状态转换,最小化时最大化,最大化时最小化。怎么办?

自然的想法是要去判断Ribbon现在的状态是什么样的。但是找遍了整个WOM(Word对象模型),都找不到任何属性,指示了现在Ribbon的状态。绝望之中,我loop CommandBars,发现最后一个CommandBarRibbon。原来Ribbon也是CommandBars中的一员啊!把这个CommandBar的高矮胖瘦属性读出来一看,果然,最大化的时候Height属性值为147,最小化的时候值为56。哈哈,问题解决了,主要代码如下。CommandBar真好,真强大!

    In ThisAddIn.cs

private static Office.CommandBars cbs = null;

 

                private void ThisAddIn_Startup(object sender, System.EventArgs e)

                {

                        this.CustomTaskPanes.Add(new UserControl1(),"test").Visible = true;

                        cbs = this.Application.CommandBars;

                }

 

                public static Office.CommandBar returnRibbon()

                {

                    foreach (Office.CommandBar cb in cbs)

                    {

                        if (cb.Name == "Ribbon")

                        {

                            return cb;

                        }

                    }

                    return null;

                }

In CustomTaskPanes UserControl1.cs

private void button1_Click(object sender, EventArgs e)

                {

                        int i = ThisAddIn.returnRibbon().Height;

                        if (i == 147)

                        {

                                Globals.ThisAddIn.Application.ActiveWindow.ToggleRibbon();

                        }

                }

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()就搞定了。CommandBar真好!真强大!

this.Application.CommandBars["Standard"].Controls["&Save"].Execute();

3. 第三个例子是今天遇到的新问题。某人想在Word的第一页的页眉插入一个图片,在第一页的页脚插入一条横线。而第一页的页眉页脚,要求和后面页的页眉页脚不同。这个看似很容易办到,因为WOM中提供了一些属性让我们设置,还有添加页眉页脚。他用的代码如下:

PageSetup.DifferentFirstPageHeaderFooter = -1;

      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);

      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方法就可以了。CommandBar真好,真强大!

this.Application.ActiveDocument.Sections.First.PageSetup.DifferentFirstPageHeaderFooter = -1;    //Set Page to make Firstpages Header and Footer different

           

                //Edit the Header

                this.Application.CommandBars["Header Area Popup"].Controls["Edit &Header"].Execute();

                this.Application.Selection.InlineShapes.AddPicture(@"C:/test.jpg", ref missing, ref missing, ref missing);

                this.Application.CommandBars["Header and Footer"].Controls["&Close"].Execute();

 

                //Edit the Footer

                this.Application.CommandBars["Footer Area Popup"].Controls["&Edit Footer"].Execute();

                Word.InlineShape line = this.Application.Selection.InlineShapes.AddHorizontalLineStandard(ref missing);

                  this.Application.CommandBars["Header and Footer"].Controls["&Close"].Execute();

恩,说了这么多,相信CommandBar的强大性已经逐渐显示出来了。尤其是第三个例子,表现得极为明显。可以看出,几乎所有手动能干的事情,不使用WOM,都可以用CommandBar和它的Control们替我们做。虽然,通常情况下,我不建议,甚至反对这么干,因为效率很低。但是,当遇到WOM有些信息不告诉我们,如1,或者WOM出现诡异Bug的时候,如3CommandBar是不是让生活美好了很多呢?

 

你可能感兴趣的:(无敌的Word CommandBar和它的Control们)