zoukankan      html  css  js  c++  java
  • 如何使用.NET开发全版本支持的Outlook插件产品(二)——完善插件

    插件项目所有代码都已经上传至

    https://github.com/VanPan/TestOutlookAdding

    勿在浮砂筑高台——定位错误

    在介绍后面的插件开发技术之前,让我们先来看看已经达到的成果:我们已经创建了第一个项目,并且也已经在Outlook里面运行起来了。

    但是一定还是有人想知道,插件到底是如何挂接到Outlook里面去的?如果我们发现插件始终无法出现,到底如何排查问题原因?

    让我们先停止向前继续开发的脚步,回过头来看看Windows和Office之间到底是如何协作启动一个Office插件的。

    在Outlook 2013中查看插件

    如果发现插件始终无法出现,怎么办?

    首先,我们先确定Outlook是否已经发现了插件,或者是否将插件禁用了。

    我们打开Outlook 2013,并且选择顶部的“文件”标签,首先在“信息”中查看“速度慢且已禁用的加载项”中有没有我们插件的名字。如果有,那就始终启用即可。

    image

    如果没有被禁用,再切换到“选项”,在打开的对话框中选择“加载项”,查看其中是否有我们插件的名字。

    image

    请注意“非活动应用程序加载项”。如果这栏里面有插件的名字。以我们的例子,我们可以在其中看到“Test Addin For Outlook”,就说明插件已经被Outlook发现,但是在加载过程中出现错误。一般情况下,都是因为x86和x64的版本号无法对应造成的,当然也有启动时出现错误导致无法加载的。对于这种情况,请先简化插件逻辑代码,保证可以启动,再逐步深入查看原因。

    在Outlook 2003中查看插件

    如果你用的是Outlook 2013,那可以在“工具”菜单栏中点击“信任中心”,在打开的对话框中也能找到类似上图的加载项。

    注册表定位

    如果这个界面里面都没有插件名称,说明Outlook根本没有发现插件的存在。此时,我们需要进入注册表查看问题的真正原因。

    我们用regedit命令打开注册表编辑器,进入以下项HKEY_CURRENT_USERSoftwareMicrosoftOfficeOutlookAddins,当然如果你的代码中声明的是LocalMachine,就应该切换到HKEY_LOCAL_MACHINESOFTWAREMicrosoftOfficeOutlookAddins,这其中需要有我们插件代码中ProgId特性类声明名称的键,在例子中,我们的插件是“TestAddinForOutlook”。

    image

    光有这个键值肯定是不够的,我们还需要通过“TestAddinForOutlook”这个名字在注册表中进行查找“项”,我们需要确定注册表中存在项HKEY_LOCAL_MACHINESOFTWAREClassesTestAddinForOutlookCLSID。可能根项不一定是HKEY_LOCAL_MACHINE,但是一定是在XXXSOFTWAREClassesTestAddinForOutlookCLSID中。当然,这个ID就是我们代码中用Guid声明的那个GUID,我们再用这个ID继续查找“项”。就应该查到类似于HKEY_CLASSES_ROOTCLSID{AFE67651-951D-4A42-8CAB-E9BF7E219DDF}的多个项,展开以后,我们就能发现真正的奥秘。

    image

    原来Outlook就是靠这种方法来查找到插件的安装路径的,简而言之,就是先看注册表中Addin内部的项,再通过项名称找到Class的CLSID,最后用CLSID唯一定位到插件文件路径。

    如果以后对插件加载还有什么无法理解的,都可以通过这个方法来查看是否在什么地方出了问题,也可以进行一些调整来确认问题是否已经解决。

    插件进阶开发

    再回到我们进行插件开发的思路中来,现在我们虽然加载了一个无比简陋的插件,但是它没有产生任何实际效果,我们没有它的任何点击事件,也无法动态改变它的标题,如果遇到一些需要实时调整的情况,那是完全不够用的。好,那我们再把RibbonUI.xml调整一下,改成下面的样子。

    <?xml version="1.0" encoding="utf-8" ?>
    <customUI onLoad="LoadAction"  xmlns="http://schemas.microsoft.com/office/2006/01/customui" >
      <ribbon>
        <tabs>
          <tab id="RibbonAddinSampleTabCS35" label="插件标签">
            <group id="group1" label="分组名">
              <button id="customButton1" size="large" onAction="ButtonAction" getLabel="GetButtonLabel"/>
            </group>
          </tab>
        </tabs>
      </ribbon>
    </customUI>

    再在项目中引入System.Windows.Forms程序集,把COMEntry类改成如下代码。

    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using NetOffice.OutlookApi.Tools;
    using NetOffice.Tools;
    
    namespace TestOutlookAddin
    {
        [COMAddin("Test Addin For Outlook", "", 3), CustomUI("TestOutlookAddin.RibbonUI.xml"), RegistryLocation(RegistrySaveLocation.CurrentUser)]
        [Guid("AFE67651-951D-4A42-8CAB-E9BF7E219DDF"), ProgId("TestAddinForOutlook")]
        public class COMEntry : COMAddin
        {
            private Office.IRibbonUI _ribbon;
            public void LoadAction(Office.IRibbonUI control)
            {
               _ribbon = control;
            }
    
            public string GetButtonLabel(NetOffice.OfficeApi.IRibbonControl control)
            {
                return "自定义
    ";
            }
    
            public void ButtonAction(NetOffice.OfficeApi.IRibbonControl control)
            {
                MessageBox.Show("Hello World");
            }
        }
    }

    再运行一下,就能看到我们把按钮的Label和点击事件都接入到代码控制里面来了。

    LoadAction是为了将整个Ribbon的对象用代码后台对象进行映射,因为Ribbon的控件无法通过 对象变量.属性 赋值来进行修改,如果要刷新UI,需要调用 _ribbon.InvalidateControl("customButton1"),这样控件会重新调用这个Button在xml中定义的各项方法来达到刷新UI的目的,因此我们可能需要在GetButtonLabel中通过一些状态来返回不同的字符串。

    需要特意说明的一点,GetButtonLabel返回的字符串里面最后都应该带回车符,因为只有这样Outlook才不会把文字变成两行,否则看起来会非常别扭。原始效果可以查看第一篇教程中的界面截图。

    除了这些,我们还能重定义按钮的图标。当然在RibbonUI.xml里面就是GetButtonImage,在代码里面,对应的函数是如下样子

    public stdole.IPictureDisp GetButtonImage(NetOffice.OfficeApi.IRibbonControl control)
            {
                return PictureConverter.IconToPictureDisp(Properties.Resources.SampleIcon2);
            }

    其中stdole是添加引用,在“程序集”——“扩展”里面可以找到,PictureConverter类代码如下

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    
    namespace TestOutlookAddin
    {
        internal class PictureConverter : AxHost
        {
            private PictureConverter() : base(String.Empty) { }
    
            static public stdole.IPictureDisp ImageToPictureDisp(Image image)
            {
                return (stdole.IPictureDisp)GetIPictureDispFromPicture(image);
            }
    
            static public stdole.Picture ImageToPicture(Image image)
            {
                return (stdole.Picture)GetIPictureFromPicture(image);
            }
    
            static public stdole.IPictureDisp IconToPictureDisp(Icon icon)
            {
                return ImageToPictureDisp(icon.ToBitmap());
            }
    
            static public stdole.Picture IconToPicture(Icon icon)
            {
                return ImageToPicture(icon.ToBitmap());
            }
    
            static public Image PictureDispToImage(stdole.IPictureDisp picture)
            {
                return GetPictureFromIPicture(picture);
            }
    
        }
    }

    其中的Image类通过添加System.Drawing程序集可以获得。

    这样,我们就可以得到一个带图标的自定义按钮了。标签和分组的名称都可以通过类似的方法进行修改。

    兼容Outlook 2003

    我们看起来已经把插件这件事做完了,但是,等一下,Ribbon好像是无法满足所有版本要求的,如果用户使用的是2007或者2003怎么办?

    那我们只能向应用程序中注入一个新增的菜单栏或者是工具栏了。再我最后发布的工程中,我们采用的是新增工具栏的方式,因为加入一个菜单栏的方法对用户来说过于干扰,而且我们还有一些随时需要根据情况变化的按钮标签,做在菜单栏上会很突兀。

    那么,如何增加工具栏呢?而且顺便一提,如果在代码里面不加任何条件增加工具栏的话,到了2013里面我们又会多一套Ribbon,因为2013是向下兼容旧版本插件的。所以,我们要先判断Office的版本号,根据不同的版本来加载不同的代码。最后,我们的COMEntry类就变成了下面的样子。

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using NetOffice.OfficeApi.Enums;
    using NetOffice.OutlookApi.Tools;
    using NetOffice.Tools;
    using OutLook = NetOffice.OutlookApi;
    using Office = NetOffice.OfficeApi;
    
    namespace TestOutlookAddin
    {
        [COMAddin("Test Addin For Outlook", "", 3), CustomUI("TestOutlookAddin.RibbonUI.xml"), RegistryLocation(RegistrySaveLocation.CurrentUser)]
        [Guid("AFE67651-951D-4A42-8CAB-E9BF7E219DDF"), ProgId("TestAddinForOutlook")]
        public class COMEntry : COMAddin
        {
    
            NetOffice.OutlookApi.Application _outlookApplication;
            private NetOffice.OfficeApi.IRibbonUI _ribbon;
    
            NetOffice.OfficeApi.CommandBarButton LogonBtn;
    
            public COMEntry()
            {
                OnStartupComplete += Addin_OnStartupComplete;
                OnConnection += Addin_OnConnection;
                OnDisconnection += Addin_OnDisconnection;
            }
    
            private void Addin_OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom)
            {
                try
                {
                    if (null != _outlookApplication)
                        _outlookApplication.Dispose();
                }
                catch (Exception exception)
                {
                    // 处理
                }
            }
    
            private void Addin_OnConnection(object app, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
            {
                try
                {
                    _outlookApplication = new OutLook.Application(null, app);
                }
                catch (Exception exception)
                {
                    // 处理
                }
            }
    
            private void Addin_OnStartupComplete(ref Array custom)
            {
                if (!_outlookApplication.Version.StartsWith("15.0") && !_outlookApplication.Version.StartsWith("14.0"))
                {
                    try
                    {
                        SetupGui();
                    }
                    catch (Exception exception)
                    {
                        // 处理
                    }
                }
            }
    
            private void SetupGui()
            {
                /* create commandbar */
                Office.CommandBar commandBar = _outlookApplication.ActiveExplorer().CommandBars.Add("工具栏名称", MsoBarPosition.msoBarTop, System.Type.Missing, true);
                commandBar.Visible = true;
    
                // add a button to the popup
                LogonBtn = (Office.CommandBarButton)commandBar.Controls.Add(MsoControlType.msoControlButton, Type.Missing, Type.Missing, Type.Missing, true);
                LogonBtn.Style = MsoButtonStyle.msoButtonIconAndCaption;
                LogonBtn.Picture = PictureConverter.IconToPicture(Properties.Resources.SampleIcon2);
                LogonBtn.Mask = PictureConverter.ImageToPicture(Properties.Resources.sampleicon2Mask);
                //LogonBtn.ClickEvent += new NetOffice.OfficeApi.CommandBarButton_ClickEventHandler(LoginBtn_ClickEvent);
            }
    
            public void LoadAction(Office.IRibbonUI control)
            {
                _ribbon = control;
            }
    
            public string GetButtonLabel(NetOffice.OfficeApi.IRibbonControl control)
            {
                return "自定义
    ";
            }
    
            public void ButtonAction(NetOffice.OfficeApi.IRibbonControl control)
            {
                MessageBox.Show("Hello World");
            }
    
            public stdole.IPictureDisp GetButtonImage(NetOffice.OfficeApi.IRibbonControl control)
            {
                return PictureConverter.IconToPictureDisp(Properties.Resources.SampleIcon2);
            }
        }
    }

    我们又在COMEntry类中添加了很多事件监听,其实主要目的就是在插件加载启动时获得Outlook Application的实例,并且在OnStartupComplete事件发生的时候判断当前Office版本号来进行不同的界面加载。Office的版本号格式是4位数字,类似于15.0.0.xxxx这样的结构,以首位数字表示大版本。15对应Office 2013,14对应Office 2010。

    SetupGui函数实现了经典界面的工具栏添加功能,其中注释的部分是监听按钮事件,因为我们保持例子的简单,就不再需要监听处理这个事件了。

    经典界面里面的按钮是可以通过对象操作来修改标签等其他信息的,只需要LogonBtn.Caption即可进行重新设置,不需要再去InvalidUI了。

    最后需要稍加注意的,是Mask这个属性。这个属性是和Picture配合使用的,主要目的是为了将按钮的图标做到区域透明效果。在经典界面里面,是不支持Icon或者PNG之类的透明图标的,如果你只是设置了一部分轮廓透明的图标作为Button的Picture,那到了显示的时候,透明的效果并不会达成。为了解决这个问题,Office引入了Mask这个方案,其实Mask就是一张黑白图片,和Picture配合,用来表示何处需要显示何处需要透明。

    下一篇,我们将会大致了解Outlook的对象模型,相互的属性,嵌入更多的自定义区域,以及一些开发上的技巧。

  • 相关阅读:
    PHP 学习轨迹
    beego 遇到的一些问题
    Fiddler 502问题
    SourceTree
    Trait
    PHP PSR 标准
    解决MySQL联表时出现字符集不一样
    Git 代码管理命令
    PHP 运行相关概念
    CentOS 7
  • 原文地址:https://www.cnblogs.com/vanpan/p/3586185.html
Copyright © 2011-2022 走看看