Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

HJAddin for Visual Studio 2005 IDE with code template included

0.00/5 (No votes)
23 Oct 2006 1  
HJAddin brings back the code template for VS2005 users, and presents a framework for writing an add-in using C++ with ATL.

Sample Image - HJAddinIntro.jpg

Introduction

HJAddin is an add-in for the Visual Studio 2005 IDE. It brings back the popular code template to VS2005 users, adds a bunch of useful custom buttons and menus, and most importantly, provides other developers, who wish to write their own add-ins, with a framework for developing add-ins using C++ with ATL.

After successful installation, the add-in looks like:

CodeTmplMenu

HJAddinIntro

Isn't that gorgeous?

Installation and Un-installation

If we have built the add-in ourselves, the VS 2005 IDE will register the class and write to the registry using AddIn.rgs, which makes a clean installation harder, not easier. To make sure the un-installation is complete so that a clean copy can be deployed, follow the steps below:

  1. Un-installation process: it gets complicated after we've installed it at least once, or we've built it on a machine to which we want to deploy the product.
    1. First, we close all instances of the Visual Studio 2005 IDE;
    2. Second, we run the program setup.exe. If this is the very first time the program is executed, go to step 2; otherwise choose to remove the add-in;
    3. Finally, we run the included Uninstall.vbs* three times; sounds bizarre, huh?
  2. Installation process: it is easy, just follow the instructions on the screen.

*Uninstall.vbs is Ivo Beltchev's VB script included in his work for "VSHelper - Visual Studio IDE enhancements". Thanks go to him.

A successful installation will create the registry item:

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

like in the picture below:

Sample screenshot

Supported Commands

  • Code template button CodeTmpl, with keywords replacement and very primitive formatting support, which lets us insert code snippets extracted from a predefined code template file. We currently support C/C++ code templates only. The thing is that, among all the languages human beings invented for programming, we speak one of them, and that one language happens to be C++. That said, we still plan to support all other popular languages in the near future.
  • Explorer button Explorer, which lets us open the solution folder. It is a two-state button in the sense that if we Ctrl-click it, it opens the containing folder for the active document, even if it does not belong to the solution.
  • Command prompt button CmdPrompt, which lets us open a command prompt. This may be useful when we work on a simple console program.
  • H2Cpp button H2Cpp, which lets us switch between the source file (.cpp or .c) and the header file (.h). Microsoft provides a right-click command for "Go To Header File" if the cursor is in a source file, but not the other way. This button provides us a round-trip.
  • Toggle line numbers button, which lets us toggle line number display. It is also a two-state button: if we Ctrl-click it, it toggles word wrap.
  • Full screen button, which lets us view a file in full screen mode.
  • Build solution button, which lets us build our solution. It works similar to F7.
  • Start without debugging button, which lets us compile, link, and then run a program without debugging. It is the same as Ctrl + F5.

Note that we've written code for the first four buttons only. For the rest, we basically just execute the DTE's own commands. For example, to toggle the word-wrap state, we execute the command "Edit.ToggleWordWrap", as follows:

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

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

The Code Template Functionality

We adapted Michael Taylor's work to support key words for the code template. Credit goes to him, although the code has changed dramatically.

The functionality of the code template has not changed at all since its introduction by Darren Richard in 1998 or even earlier. We still would like to describe it here for either the completeness of this documentation or for the ease and happiness of new comers. Assuming the template file has no syntax errors, the CodeTmpl button works as follows:

  • When the button is clicked, it reads the template file and translates the file into a menu.
  • When a menu item is clicked, it translates the corresponding code fragment for that menu item and inserts the code fragment at an appropriate place.
  • When the button is Ctrl-clicked, it adds another menu item called "Open HJCodeTmpl.txt" at the bottom.
  • CodeTmplCtrlClick

  • When the "Open HJCodeTmpl.txt" menu item is clicked, it opens the template file located at the installation folder, in the VS2005 IDE, viewing it as a C++ source file. At this point, the user can do some in-place or on-the-fly editing. After finishing modifications for the file and saving it to the disk, the user Ctrl-clicks the button again to make the modifications effective immediately.

Supported Keywords and Formatters

Currently, we are supporting the following keywords. They have been fully tested, and we think they are robust.

Keywords Example use Results
SOLUTION The solution is #%SOLUTION%#. The solution is HJAddin.sln.
PROJECT The project is #%PROJECT%#. The project is HJAddin
FILE The file is #%FILE%#. The file is source1.cpp.
FILEFULLNAME The full file name is #%FILEFULLNAME%#. The full file name is d:\Cpp\HJAddin_VS2005\0.8c\source1.cpp.
PATH The path is #%PATH%#. The path is d:\Cpp\HJAddin_VS2005\0.8c\.
FILENAME_ONLY The file name (without extension) is #%FILENAME_ONLY%#. The file name (without extension) is source1.
EXT_ONLY The file name extension is #%EXT_ONLY%#. The file name extension is cpp.
NOW Now is #%NOW%#. Now is 10/23/2006 00:41:45.
DATE The date is #%DATE%# The date is 10/23/2006
TIME The time is #%TIME%# The time is 00:41:45

We plan to add more keywords, such as AUTHOR, as time allows.

The following formatters are supported for this release of HJAddin, and we tend to think they are robust.

Formatters Example use Results or explanation
W n #<W 35 Hai Jin># expands or truncates the string between #< and ># to have exactly length 35 --- either padding spaces right to the string if it is not wide enough or truncating it if it is too wide.
U #<U Hai Jin># make "Hai Jin" upper-case so that it becomes "HAI JIN"
L #<L Hai Jin># make "Hai Jin" lower-case so that it becomes "hai jin"
%$% Description: %$% Description: I --- this means the cursor or caret will be located at "I"

The following two formatters are in testing mode, and are considered not robust:

Formatters Example use Results or explanation
%BEGINDOC% %BEGINDOC% Moves the insertion or editing point to the begging of the current active document.
%NSF% %NSF% Disable the smart format functionality temporarily.

The Template File

Setting up a good code template file is no easy task, so I would like to spend some time here. The users may want to read through the included template file "HJCodeTmpl.txt", but that may take an hour or two out of their precious time. For simplicity, we use the following graph and a menu item presented in the template file to show how things are done:

The code template button looks like:

MessageBox

while the corresponding text in the template file is:

#[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);
}#

]#

Framework for Adding Named Commands, Buttons, and Menu/Menu Items

The process for adding a command is: add a named command and update the UI upon connection (CConnect::OnConnection), update the query status to make it visible (CConnect::QueryStatus), and then execute it based on the user's request (CConnect::Exec). The job to update the QueryStatus is very easy, while the one for Exec is where our real work lies. However, we are not going to talk about those two, we focus on OnConnection instead.

The tasks for modifying OnConnection are: add named commands, create our toolbar and add buttons to it, and create our menu and add menu items to it. To make the tasks easy to follow, we implement a few helpers. Let us begin with AddCommand2.

The declaration of AddCommand2 is:

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

so that all we need to do is either, Option #1 --- if we want to add a named command with a custom bitmap:

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

or Option #2 --- if we want to add a named command with a built-in bitmap instead:

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

For Option #1, a satellite DLL is required to hold the bitmaps. For more information about adding customized bitmaps, check the Deployment Project section below.

I would like to make several comments for the included AddToMyMenu(...), AddToMyToolbar(...), GetMenuPosition(...), CreateMyMenu(...), and CreateMyToolbar(...) helpers.

  • The GetMenuPosition function returns the position of existing menus, such as "Tools" and "Help";
  • AddToMyMenu and AddToMyToolbar do not mean we can only add items to our own menus and toolbars, they can actually add items to existing menus such as "Build" and "Window" and existing toolbars such as "Standard" and "Text Editor";
  • We can use CreateMyMenu and CreateMyToolbar to create as many new menus and toolbars as we want. But, we don't want to mess up the IDE too much, do we? As a matter of fact, I do not create a customized toolbar for my own version of this add-in; instead, I append all the buttons to the "Text Editor" toolbar.

Finally, let us describe the framework to make OnConnection work just right, as a pseudo-code below:

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

If we follow the framework described above, things won't go wrong. If troubles do pop up, please check the trouble-shooting section.

The Deployment Project

The deployment project (the HJAddinSetup project in our case) deals with the installation and un-installation of our product. It copies the required files to the appropriate locations, registers the class with its unique CLSID (class ID), and writes to the Registry. A few things I want to point out are:

  1. We need to provide info for the add-in to find the bitmap resources. This is where the Registry part of the deployment project comes to help.
  2. SatelliteDll

    In this small project, we choose to support US English only, so that we can use the add-in DLL as its own satellite DLL. Specifically, we are forced to add two string Registry items SatelliteDLLName and SatelliteDLLPath:

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

    of which, [TARGETDIR] will be expanded to the installation path, such as "C:\Program Files\Hai Jin\HJAddin\".

    The approach has the advantage that we can use resource IDs such as IDB_CODETMPL, and the disadvantage is it makes our product more difficult for international support. If we need to distribute the product worldwide, it would be better to write a resource DLL for each language we support. The resource DLL approach normally does not use resource IDs, though.

  3. We need to set properties for the "Primary Output" in the deployment project correctly: mainly, we want the "Register" field to be "vsdrpCOM" so that the add-in can register itself during the installation process.
  4. Register

Trouble-shooting for Writing an Add-in

After consuming a cup of coffee and sitting down to hit the keyboard for the sake of dressing up the add-in, we run into many troubles. This section is devoted to trouble shooting.

  1. The issue of disappearing commands:
  2. Microsoft has proposed three ways to solve this. Detailed info can be found here. Our approach is that we check the existence of one of our commands. If it exists, then all our commands exist and hence no need to add them again; otherwise, we add them all. The approach ensures that we add the commands once and only once:

        // 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. The issue of too many toolbars with the same name:
  4. This probably happens because we add the toolbar every time the add-in is connected, or we do not clean up stuff correctly. The following code performs high quality house-cleaning:

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

    You can add the toolbar every time the add-in starts, but that will make the toolbar floating at the left-top coroner of the VS2005 IDE every time the IDE is launched. A bad solution, huh? I propose a way which lets us add the toolbar once and for all:

        // 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. The issue of disappearing customized menus:
  6. By default, any menu we add is temporary so that we have to do it again every time the add-in is connected.

        // 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

Acknowledgements

In developing this add-in for the Microsoft Visual Studio 2005 IDE, the following people's code, articles, and books helped. I wish to express my deep gratitude towards them.

Their names and the related work are:

  • Darren Richard's original code for the Code Template add-in for Visual C++ 5.0;
  • Michael Taylor's update to the Code Template add-in for Visual C++ (which works for Visual Studio 6) with keyword enhancements;
  • Eddie Velasquez 's Code Template Add-In for Visual Studio 7.0 (2002) and 7.1 (2003). The add-in is implemented in C#, and supports various kinds of languages, such as VB, C/C++, and C#;
  • Ivo Beltchev's article and code for "VSHelper - Visual Studio IDE enhancements". His code works for both VS 7.1 and VS 8.0. The deployment project helped a lot for the installation of HJAddin;
  • Joseph M. Newcomer for his wonderful article on "CString Management". I copied one of his conversion functions to convert from BSTR strings to CString strings.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here