65.9K
CodeProject 正在变化。 阅读更多。
Home

适用于 Visual Studio 2005 IDE 的 HJAddin,包含代码模板

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (6投票s)

2006年10月23日

CPOL

11分钟阅读

viewsIcon

40280

downloadIcon

397

HJAddin 为 VS2005 用户带来了代码模板功能,并提供了一个使用 C++ 和 ATL 编写插件的框架。

Sample Image - HJAddinIntro.jpg

引言

HJAddin 是 Visual Studio 2005 IDE 的一个插件。它为 VS2005 用户带来了流行的代码模板功能,添加了一组有用的自定义按钮和菜单,最重要的是,为希望编写自己插件的其他开发者提供了一个使用 C++ 和 ATL 开发插件的框架。

成功安装后,该插件的外观如下

CodeTmplMenu

HJAddinIntro

是不是很漂亮?

安装和卸载

如果我们自己构建了该插件,VS 2005 IDE 将会注册该类并在注册表中写入信息,使用AddIn.rgs。这使得彻底卸载变得困难,而不是更容易。为了确保能够彻底卸载以便部署干净的版本,请按照以下步骤操作:

  1. 卸载过程:如果我们至少安装过一次,或者在我们想要部署产品的机器上构建过它,那么卸载过程就会变得复杂。
    1. 首先,关闭所有 Visual Studio 2005 IDE 实例;
    2. 其次,运行程序setup.exe。如果这是该程序第一次执行,请转到步骤 2;否则,选择卸载插件;
    3. 最后,运行包含的Uninstall.vbs* 三次;听起来很奇怪,是吧?
  2. 安装过程:很简单,只需按照屏幕上的说明进行操作。

*Uninstall.vbs 是 Ivo Beltchev 的 VB 脚本,包含在他的“VSHelper - Visual Studio IDE enhancements”作品中。非常感谢他。

成功安装后,将在注册表中创建条目

HKEY_CLASSES_ROOT\CLSID\{306AB7FD-9FF3-4491-81D6-BB5687D81F70}

如下面的图片所示

Sample screenshot

支持的命令

  • 代码模板按钮 CodeTmpl,支持 关键字 替换和非常基础的格式化,允许我们插入从预定义代码模板文件中提取的代码片段。目前我们仅支持 C/C++ 代码模板。原因是,在我们人类发明的所有编程语言中,我们只会一种,而恰好是 C++。也就是说,我们仍然计划在不久的将来支持所有其他流行语言。
  • 资源管理器按钮 Explorer,允许我们打开解决方案文件夹。它是一个双状态按钮,如果按住 Ctrl 单击它,它将打开活动文档所在的文件夹,即使该文档不属于解决方案。
  • 命令提示符按钮 CmdPrompt,允许我们打开命令提示符。当处理简单的控制台程序时,这可能很有用。
  • H2Cpp 按钮 H2Cpp,允许我们在源文件(.cpp.c)和头文件(.h)之间切换。Microsoft 在光标位于源文件时提供了一个右键单击命令“转到头文件”,但反之则不行。此按钮提供了双向跳转。
  • 切换行号按钮,允许我们切换行号显示。这也是一个双状态按钮:如果按住 Ctrl 单击它,它将切换自动换行。
  • 全屏按钮,允许我们在全屏模式下查看文件。
  • 生成解决方案按钮,允许我们生成解决方案。其功能与 F7 类似。
  • 无调试启动按钮,允许我们编译、链接然后运行程序而不进行调试。这与 Ctrl + F5 相同。

请注意,我们只编写了前四个按钮的代码。对于其余按钮,我们基本上只是执行 DTE 本身的命令。例如,要切换自动换行状态,我们执行命令“Edit.ToggleWordWrap”,如下所示:

BOOL bCtrl = ((UINT)GetAsyncKeyState(VK_CONTROL) > 1); // control 

if( bCtrl) // if Ctrl-click
{
    m_pDTE->ExecuteCommand(CComBSTR("Edit.ToggleWordWrap"), CComBSTR(""));
    return S_OK;
}

代码模板功能

我们改编了 Michael Taylor 的作品来支持代码模板的关键字。功劳归于他,尽管代码已经发生了巨大变化。

自从 Darren Richard 在 1998 年或更早的时候推出代码模板功能以来,其功能一直没有改变。为了本文档的完整性,或者为了方便新用户,我们仍然希望在此进行描述。假设模板文件没有语法错误,CodeTmpl 按钮的工作方式如下:

  • 当点击按钮时,它会读取模板文件并将其转换为一个菜单。
  • 当点击菜单项时,它会翻译该菜单项对应的代码片段,并将代码片段插入到合适的位置。
  • 当按住 Ctrl 点击按钮时,它会在底部添加一个名为“Open HJCodeTmpl.txt”的菜单项。
  • CodeTmplCtrlClick

  • 当点击“Open HJCodeTmpl.txt”菜单项时,它会在 VS2005 IDE 中打开位于安装目录下的模板文件,并将其视为 C++ 源文件。此时,用户可以进行一些就地或即时编辑。在完成文件修改并将其保存到磁盘后,用户再次按住 Ctrl 点击按钮即可使修改立即生效。

支持的关键字和格式化器

当前,我们支持以下关键字。它们已经过全面测试,我们认为它们是稳健的。

关键词 示例用法 结果
SOLUTION 解决方案是 #%SOLUTION%#。 解决方案是 HJAddin.sln。
PROJECT 项目是 #%PROJECT%#。 项目是 HJAddin
文件 文件是 #%FILE%#。 文件是 source1.cpp。
FILEFULLNAME 完整文件名是 #%FILEFULLNAME%#。 完整文件名是 d:\Cpp\HJAddin_VS2005\0.8c\source1.cpp。
PATH 路径是 #%PATH%#。 路径是 d:\Cpp\HJAddin_VS2005\0.8c\。
FILENAME_ONLY 文件名(不含扩展名)是 #%FILENAME_ONLY%#。 文件名(不含扩展名)是 source1。
EXT_ONLY 文件名扩展名是 #%EXT_ONLY%#。 文件名扩展名是 cpp。
NOW 现在是 #%NOW%#。 现在是 2006/10/23 00:41:45。
DATE 日期是 #%DATE%# 日期是 2006/10/23
TIME 时间是 #%TIME%# 时间是 00:41:45

我们计划在有时间时添加更多关键字,例如 AUTHOR

本版本的 HJAddin 支持以下格式化器,我们认为它们是稳健的。

格式化器 示例用法 结果或解释
W n #<W 35 Hai Jin># 将 #< 和 ># 之间的字符串展开或截断到正好长度 35——如果字符串不够长则用空格填充右侧,如果太长则截断。
U #<U Hai Jin># 将“Hai Jin”转换为大写,变成“HAI JIN”。
L #<L Hai Jin># 将“Hai Jin”转换为小写,变成“hai jin”。
%$% 描述:%(美元符号)% 描述:I --- 这表示光标或插入点将位于“I”处。

以下两个格式化器处于测试模式,并且被认为不稳健:

格式化器 示例用法 结果或解释
%BEGINDOC% %BEGINDOC% 将插入或编辑点移到当前活动文档的开头。
%NSF% %NSF% 暂时禁用智能格式化功能。

模板文件

设置一个好的代码模板文件并非易事,因此我想在这里花些时间。用户可能想通读包含的模板文件“HJCodeTmpl.txt”,但这可能会花费他们一两个小时的宝贵时间。为简单起见,我们使用以下图示和模板文件中的菜单项来展示如何操作:

代码模板按钮的外观如下:

MessageBox

而模板文件中的相应文本是:

#[Message Box
#{AfxMessageBox
AfxMessageBox(_T("%$%"));
}#

#########

#[MessageBox
#{MsgBox Sub#1
MessageBox(NULL, "%$%", "", MB_OK);
}#

#{MsgBox Sub#2
MessageBox(NULL, "%$%", "", MB_ICONINFORMATION);
}#
]#

#{MsgBox #3
MessageBox(NULL, "%$%", "", MB_OK | MB_ICONINFORMATION);
}#

]#

用于添加命名命令、按钮和菜单/菜单项的框架

添加命令的过程是:添加一个命名命令并在连接时更新 UI (CConnect::OnConnection),更新查询状态使其可见 (CConnect::QueryStatus),然后根据用户请求执行它 (CConnect::Exec)。更新 QueryStatus 的工作非常简单,而 Exec 是我们真正的工作所在。然而,我们不打算讨论后两者,而是专注于 OnConnection

修改 OnConnection 的任务包括:添加命名命令,创建我们的工具栏并向其添加按钮,以及创建我们的菜单并向其添加菜单项。为了使任务易于遵循,我们实现了一些辅助函数。让我们从 AddCommand2 开始。

AddCommand2 的声明如下:

HRESULT AddCommand2(const char* cmdFullName, const char* cmdShortName,
     const char* cmdButtonText, const char* cmdToolTip, long nBitmapID,
     VARIANT_BOOL bMSOButton = VARIANT_FALSE);

因此,我们只需要做以下任一操作,选项 #1 --- 如果我们想添加一个带有自定义位图的命名命令:

    hr = AddCommand2(m_szExplorer_FN, m_szExplorer_SN, m_szExplorer_BT,
                m_szExplorer_TT, IDB_EXPLORER);

或者选项 #2 --- 如果我们想添加一个带有内置位图的命名命令:

    hr = AddCommand2(m_szToggleLineNumbers_FN, m_szToggleLineNumbers_SN,
                m_szToggleLineNumbers_BT, m_szToggleLineNumbers_TT, 
                11, VARIANT_TRUE);

对于选项 #1,需要一个卫星 DLL 来存放位图。有关添加自定义位图的更多信息,请参见下面的 部署项目 部分。

我想对包含的 AddToMyMenu(...)AddToMyToolbar(...)GetMenuPosition(...)CreateMyMenu(...)CreateMyToolbar(...) 辅助函数做一些说明。

  • GetMenuPosition 函数返回现有菜单(如“工具”和“帮助”)的位置;
  • AddToMyMenuAddToMyToolbar 并不意味着我们只能将项添加到自己的菜单和工具栏中,它们实际上可以添加项到现有菜单(如“生成”和“窗口”)和现有工具栏(如“标准”和“文本编辑器”)中;
  • 我们可以使用 CreateMyMenuCreateMyToolbar 来创建任意数量的新菜单和工具栏。但是,我们不想过多地干扰 IDE,对吧?事实上,我并没有为我的插件版本创建自定义工具栏;相反,我将所有按钮添加到了“文本编辑器”工具栏中。

最后,让我们用伪代码描述用于使 OnConnection 正确工作的框架:

STDMETHODIMP CConnect::OnConnection(IDispatch *pApplication,
     ext_ConnectMode ConnectMode, IDispatch *pAddInInst,
     SAFEARRAY ** /*custom*/ )
{
    HRESULT hr = S_OK;

    hr = pApplication->QueryInterface(__uuidof(DTE2), (LPVOID*)&m_pDTE);
    hr = pAddInInst->QueryInterface(__uuidof(AddIn), (LPVOID*)&m_pAddInInstance);

    CComPtr<COMMANDS> pCommands;
    hr = m_pDTE->get_Commands(&pCommands);

    CComPtr<COMMAND> aCommand;
    hr = pCommands->Item(CComVariant(m_szCodeTmpl_FN), 0, &aCommand);

    // Check if one of the named commands exists.
    // If it exists, then all other named commands exist as well.
    // If not, we add them.

    if( hr != S_OK && !aCommand)
    {
        hr = AddCommand2(m_szExplorer_FN, m_szExplorer_SN, m_szExplorer_BT, 
                         m_szExplorer_TT, IDB_EXPLORER);
        // add more commands, either those with a custom
        // bitmap resource or those with built-in face
        // create the toolbar once and for all

        hr = CreateMyToolbar();
        // create more toolbars if you wish
        // add buttons to the toolbar

        hr = AddToMyToolbar(m_szCodeTmpl_FN, m_szCodeTmpl_TT);
        // add more buttons to the corresponding toolbar
        // you can add built-in command to a toolbar just as below

        hr = AddToMyToolbar("Build.BuildSolution", 
                            "Build solution");
    }

    // The menu we created is temporary so that we are forced to
    // create it every time the add-in is connected

    int n = GetMenuPosition();
    hr = CreateMyMenu(n+1);
    // create more menus

    
    // add items to the menu

    hr = AddToMyMenu(m_szExplorer_FN);
    // add more menuitems to the corresponding menu

    return S_OK;
}

如果我们遵循上述框架,事情就不会出错。如果出现问题,请检查 故障排除 部分。

部署项目

部署项目(在我们的例子中是HJAddinSetup项目)负责我们产品的安装和卸载。它将所需文件复制到相应位置,使用唯一的 CLSID(类 ID)注册类,并写入注册表。有几点我想强调:

  1. 我们需要为插件提供信息以查找位图资源。这就是部署项目的注册表部分发挥作用的地方。
  2. SatelliteDll

    在这个小型项目中,我们选择仅支持美式英语,以便插件 DLL 可以作为自己的卫星 DLL。具体来说,我们被迫添加两个字符串注册表项 SatelliteDLLNameSatelliteDLLPath

        SateliteDLLName = ..\HJAddin.dll
        SateliteDLLPath = [TARGETDIR]

    其中,[TARGETDIR] 将被扩展为安装路径,例如“C:\Program Files\Hai Jin\HJAddin\”。

    这种方法的好处在于我们可以使用资源 ID,如 IDB_CODETMPL,而缺点是它使我们的产品更难获得国际支持。如果我们必须在全球分发产品,最好为我们支持的每种语言编写一个资源 DLL。然而,资源 DLL 方法通常不使用资源 ID。

  3. 我们需要正确设置部署项目中“Primary Output”的属性:主要是,我们希望“Register”字段为“vsdrpCOM”,以便插件在安装过程中能够自行注册。
  4. Register

编写插件的故障排除

喝完一杯咖啡坐下来敲键盘,为了打扮插件,我们遇到了很多麻烦。本节专门用于故障排除。

  1. 命令消失的问题
  2. Microsoft 提出了三种解决此问题的方法。详细信息可以在 这里找到。我们的方法是检查我们的一个命令是否存在。如果存在,那么我们的所有命令都存在,因此无需再次添加;否则,我们一次性添加所有命令。该方法确保我们只添加一次命令。

        // Check if one of the named commands exists.
        // If it exists, then all other named commands exist as well.
        // If not, we add them.
    
        if( hr != S_OK && !aCommand)
        {
            hr = AddCommand2(m_szExplorer_FN, m_szExplorer_SN, 
            m_szExplorer_BT, m_szExplorer_TT, IDB_EXPLORER);
            // add more commands
        }
  3. 同名工具栏过多的问题
  4. 这很可能是因为每次插件连接时都添加工具栏,或者我们没有正确清理。以下代码执行高质量的清理工作:

        // delete all previously installed toolbars with name toolbarName
        // note that we delete it until we cannot delete it anymore
        // using a do...while loop
    
        CComQIPtr<_CommandBars> pCommandBars;
        pCommandBars = pDisp;
        do {
            hr = pCommandBars->get_Item(CComVariant(toolbarName), 
                                       (CommandBar**)&pDisp);
            if(pDisp)
                pCommands->RemoveCommandBar(pDisp);
        }
        while(hr == S_OK);

    您可以在插件每次启动时添加工具栏,但这将在每次启动 IDE 时使工具栏浮动在 VS2005 IDE 的左上角。这是一个糟糕的解决方案,是吧?我提出了一种方法,可以让我们一劳永逸地添加工具栏:

        // Check if one of the named commands exists.
        // If it exists, then all other named commands exist as well.
        // If not, we add them.
    
        if( hr != S_OK && !aCommand)
        {
            // add the commands    
            
            // create the toolbar once and for all
    
            hr = CreateMyToolbar();
    
            // add buttons to the toolbar
    
            hr = AddToMyToolbar(m_szCodeTmpl_FN, m_szCodeTmpl_TT);
            // add more buttons if you like
    
        }
  5. 自定义菜单消失的问题
  6. 默认情况下,我们添加的任何菜单都是临时的,因此每次连接插件时我们都必须重新添加它。

        // The menu we created is temporary so that we are forced to
        // create it every time the add-in is connected
    
        int n = GetMenuPosition();
        hr = CreateMyMenu(n+1);
        
        // add items to the menu
    
        hr = AddToMyMenu(m_szExplorer_FN);
        // add more menuitems

致谢

在为 Microsoft Visual Studio 2005 IDE 开发此插件的过程中,以下人员的代码、文章和书籍提供了帮助。我谨向他们致以最深的谢意。

他们的姓名和相关工作是:

  • Darren Richard 为 Visual C++ 5.0 编写的 Code Template 插件的原始代码;
  • Michael Taylor 为 Visual C++(适用于 Visual Studio 6)更新的 Code Template 插件,增加了关键字增强功能;
  • Eddie Velasquez 的 Code Template Add-In for Visual Studio 7.0 (2002) 和 7.1 (2003)。该插件是用 C# 实现的,支持 VB、C/C++ 和 C# 等多种语言;
  • Ivo Beltchev 关于“VSHelper - Visual Studio IDE enhancements”的文章和代码。他的代码适用于 VS 7.1 和 VS 8.0。部署项目对 HJAddin 的安装帮助很大;
  • 感谢 Joseph M. Newcomer 关于“CString Management”的精彩文章。我复制了他的一项转换函数,用于将 BSTR 字符串转换为 CString 字符串。
© . All rights reserved.