Click here to Skip to main content
15,891,828 members
Articles / Desktop Programming / MFC
Article

Finding an add-in toolbar in Visual Studio

Rate me:
Please Sign up or sign in to vote.
3.88/5 (8 votes)
18 Aug 20013 min read 109.3K   32   10
Adding GUI support in Visual Studio Add-ins can be difficult. This class makes the task a lot easier.

Introduction

Many people who try to write some advanced feature to Visual Studio find that it has very poor support for user GUI interfaces, more correctly it may be said there is no support. Some people nay ask: “Why do you say this? In Visual Studio you can create wonderful dialogs with any interface you want”. Ok! You are right, but if I want to add something which must act like standard Visual Studio window then the problem of GUI interface become more real.

From my original article I will start a group of articles were I will write actual working code, which can help many add-on writers in his work.

Let's begin

This article will introduce to you way how to find an add-on toolbar window in Visual Studio.

Let's describe the problem of such operation.

Problem: How to find toolbar in Visual Studio?

Answer: As a unique identifier we have dialog control ID number which must be unique in the current window.

Let's look using the Spy++ window and find the control ID. But we still need a handle to a Visual Studio window.

The code below can help us to find the Visual Studio Main Window:

HWNDhWnd = ::GetActiveWindow();
HWNDhDesktopWnd = ::GetDesktopWindow();

while( hWnd && hWnd != hDesktopWnd )
{
  m_hDevStudio = hWnd;
  hWnd = ::GetParent(hWnd);
}

as a result we will have a handle to Visual Studio.

Secondly we need to find the toolbars parent in Visual Studio. The selected window in the first image is a toolbars parent window. (With one exception: The parent to all docked toolbars windows!).

In the second image you can see the control ID of such a window.

Image 1

Image 2

And here we have expanded list of the Toolbar's parent window:

Image 3

So the problem of finding a toolbar's parent is solved by the following code:

Where m_mapTool is std::map<long, HWND>;

m_hToolBarsParent = ::GetDlgItem( m_hDevStudio, DEF_TOOLBARS_PARENT_CID );

...

  HWNDhTmp = NULL;
  longlId  = DEF_TOOLBAR_ADDON_CID;
  
  do
  {
    hTmp = ::GetDlgItem( m_hToolBarsParent, lId );
    if( hTmp != NULL )
      m_mapTool.insert( TIdMap::value_type( ::GetDlgCtrlID( hTmp ), hTmp ) );

    lId++;

    // If no addon toolbars found
    if( ( lId - DEF_TOOLBAR_ADDON_CID > m_lMaxId ) && ( hTmp == NULL ) )
      break;
  } while( 1 );

And from that moment we have only troubles:

  • How to find our window using that window?
  • How to find undocked windows?

After one week of attempts to solve such a problem I found some very interesting data in the windows registry by path:

HKEY_CURRENT_USER\\

#define DEF_ADDON_REGISTRY_CID"Software\\Microsoft\\DevStudio\\6.0\\Layout"

In the image you can see a very interesting value in registry, they start from “Window1” and second part of the value name is a control ID of all add-on's toolbars which were registered in Visual Studio. It's a real treasure! In binary data of such values I found text with the names of toolbars buttons (look at the second image). It's a pity but the stucture of the data not documented by Microsoft that is why I take only text part of data from registry and try to find in it name of own add-on method. In some cases in the registry can be found one, two or more values with such stings (if add-on was not correctly removed from Visual Studio previously). That is why I needed a more enhanced check of existing toolbars in Visual Studio. (very interesting that add-on's toolbars have control ID started from 0x7801 value till ...??? ).

Image 4

Image 5

The second part of problem (about not docked windows) can be solved by such code:

where m_mapFloat is std::map<long, HWND>;

HWNDhTmp = ::GetDesktopWindow();

hTmp = ::GetWindow( hTmp, GW_CHILD );

do
{
  hTmp = ::GetWindow( hTmp, GW_HWNDNEXT );
  if( ::GetParent( hTmp ) ==  m_hDevStudio )
  {
    HWNDhSec = ::GetWindow( hTmp, GW_CHILD );
    if( hSec != NULL )
    {
      HWNDhTest = ::GetWindow( hSec, GW_HWNDNEXT );
      if( hTest == NULL )
      {
        longlSecID = ::GetDlgCtrlID( hSec );
        if( ( lSecID - DEF_TOOLBAR_ADDON_CID > 0 ) &&
            ( lSecID - DEF_TOOLBAR_ADDON_CID <= m_lMaxId ) )
          m_mapFloat.insert( TIdMap::value_type( ::GetDlgCtrlID( hSec ),
             hSec ) );
      }
    }
  }
} while( hTmp != NULL );

Combining values from registry and founded windows in system we can found needed to us handle and use it…

Here is class written by me, which combine all code from top and gives us easy way to get HANDLE of our toolbar window.

As a bonus class can find all active and not active add-on's in Visual Studio, find status bar and toolbars parent window HANDLE's.

Class must be run after Visual Studio creation (we can't call FindToolbarCID function in DSAddIn::OnConnect function).

Class Sources:

#ifndef _TODO_ADDON_WINDOWS_HACK_
#define _TODO_ADDON_WINDOWS_HACK_
 
#include <vector>
#include <map>
#include <string>
#include <winreg.h>
 
//////////////////////////////////////////////////////////////////////////
// Predefined types 
 
typedefstd::stringstring;
 
typedefstd::map<long, HWND> TIdMap;
typedefTIdMap::iteratorTIdIter;
 
typedefstd::vector<string> TStrArray;
typedefTStrArray::iteratorTStrIter;
 
typedefstd::map<long, string> TValMap;
typedefTValMap::iteratorTValIter;
 
//////////////////////////////////////////////////////////////////////////
//  
 
#define DEF_TOOLBAR_ADDON_CID0x7801
#define DEF_TOOLBARS_PARENT_CID0x3e8
#define DEF_STATUSBAR_CID0xe801
 
#define DEF_ADDON_REGISTY"Software\\Microsoft\\DevStudio\\6.0\\AddIns"
#define DEF_ADDON_REGISTY_SUB"Software\\Microsoft\\DevStudio\\6.0\\AddIns\\"
#define DEF_ADDON_REGISTRY_CID"Software\\Microsoft\\DevStudio\\6.0\\Layout"
 
#ifndef MAX_STRING_LENGTH
  #define MAX_STRING_LENGTH8192
#endif 
 
//////////////////////////////////////////////////////////////////////////
// Hack class wich find toolbar window in DevStudio
 
classCToDoWindowHack
{
  public:
    CToDoWindowHack( );
    ~CToDoWindowHack( );
 
    voidReset( void );
 
    HWNDFindDevStudioWindow( void );
    HWNDFindToolBarsParent( void );
    HWNDFindStatusBar( void ); 
    
    TStrArray &GetAllAddons( void );
    TStrArray &GetActiveAddons( void );
    
    LONGFindToolbarCID( stringstrUniqName );  
    
    // if functions return 0 then call FindToolbarCID first
    LONGGetToolbarCID( void ) const { returnm_lCIDToolbar; };
    HWNDGetToolbarHWND( void ) const { returnm_hToolBar; };
 
  protected:
    BOOLFindFloat( void );
    BOOLFindTool( void );
    longFindInMaps( void );
    
  private:
    HWND        m_hDevStudio;
    HWND        m_hToolBarsParent;
    HWND        m_hStatusBar;
    TStrArray   m_arrAll;
    TStrArray   m_arrActive;
 
    TIdMap      m_mapTool;
    TIdMap      m_mapFloat;
    TValMap     m_mapValueNames;
    TIdMap      m_mapId;
    long        m_lMaxId;
 
    long        m_lCIDToolbar;
    HWND        m_hToolBar;
};
 
//////////////////////////////////////////////////////////////////////////
// Constructor
 
CToDoWindowHack::CToDoWindowHack()
  : m_hDevStudio( NULL )
  , m_hToolBarsParent( NULL )
  , m_hStatusBar( NULL )
  , m_lCIDToolbar( 0 )
  , m_hToolBar( NULL )
  , m_lMaxId( 0 )
{
  
}
 
//////////////////////////////////////////////////////////////////////////
// Destructor
 
CToDoWindowHack::~CToDoWindowHack()
{
  Reset();
}
 
//////////////////////////////////////////////////////////////////////////
// Function reset all value found by class before (called by destructor)
 
voidCToDoWindowHack::Reset( void )
{
  m_mapValueNames.clear();
  m_mapTool.clear();
  m_mapFloat.clear();
  
  m_arrActive.clear();
  m_arrAll.clear();
  
  m_hDevStudio = NULL;
  m_hStatusBar = NULL;
  m_hToolBar = NULL;
  m_hToolBarsParent = NULL;
}
 
//////////////////////////////////////////////////////////////////////////
// Function return handle to DevStudio window
 
HWNDCToDoWindowHack::FindDevStudioWindow( void )
{
  // If window Found before
  if( m_hDevStudio != NULL )
    returnm_hDevStudio;
  
  HWNDhWnd = ::GetActiveWindow();
  HWNDhDesktopWnd = ::GetDesktopWindow();
  
  while( hWnd && hWnd != hDesktopWnd )
  {
    m_hDevStudio = hWnd;
    hWnd = ::GetParent(hWnd);
  }
 
  return m_hDevStudio;
}
 
//////////////////////////////////////////////////////////////////////////
// Function return handle to panel which is a parent to all docked toolbars
 
HWNDCToDoWindowHack::FindToolBarsParent( void )
{
  if( m_hDevStudio == NULL )
    FindDevStudioWindow();
 
  m_hToolBarsParent = ::GetDlgItem( m_hDevStudio, DEF_TOOLBARS_PARENT_CID );
 
  return m_hToolBarsParent;
}
 
//////////////////////////////////////////////////////////////////////////
// Function return handle of statusbar window/control
 
HWNDCToDoWindowHack::FindStatusBar( void )
{
  if( m_hDevStudio == NULL )
    FindDevStudioWindow();
 
  m_hStatusBar = ::GetDlgItem( m_hDevStudio, DEF_STATUSBAR_CID );
 
  return m_hStatusBar;
}
 
//////////////////////////////////////////////////////////////////////////
// Find in registry all AddIns info and fill two array arrActie and arrAll
 
TStrArray &CToDoWindowHack::GetAllAddons( void )
{
  if( !m_arrAll.empty() ) 
    return m_arrAll;
  
  CRegKeytmpReg;
  CRegKeytmpReg2;
  LONG    lResult = S_OK;
  
  lResult = tmpReg.Open( HKEY_CURRENT_USER, DEF_ADDON_REGISTY );
  
  if( lResult == ERROR_SUCCESS )
  {
    long    lCount = 0;
    DWORD   dwSize = MAX_STRING_LENGTH;
    string  strName;
    string  tmpValue;
    
    tmpValue.reserve( MAX_STRING_LENGTH );
    strName.reserve( MAX_STRING_LENGTH );
    
    // Get All subkeys of Visual Studio Add-on Key and store name in arrAll.
    // Check if addon in active state and then store it in arrActive.
    do
    {
      lResult  = ::RegEnumKey( tmpReg.m_hKey, lCount, 
                     ( LPSTR )strName.c_str( ), MAX_STRING_LENGTH );
      if( lResult != ERROR_SUCCESS )  break;
 
      m_arrAll.push_back( string( strName.c_str( ) ) );
      stringtmpName = DEF_ADDON_REGISTY_SUB + string( strName.c_str( ) );
      
      lResult = tmpReg2.Open( HKEY_CURRENT_USER, tmpName.c_str( ) );
      if( lResult != ERROR_SUCCESS )  break;
 
      dwSize = MAX_STRING_LENGTH;
      lResult = tmpReg2.QueryValue( ( LPTSTR )tmpValue.c_str( ), NULL, 
                                      &dwSize );
      if( lResult != ERROR_SUCCESS )  break;
 
      tmpReg2.Close();
 
      // If Add-on active then add it into array
      if( tmpValue.c_str( )[0] == '1' )
        m_arrActive.push_back( string( strName.c_str( ) ) );
 
      lCount++;
    } while( 1 );
  }
 
  tmpReg.Close();
 
  return m_arrAll;
};
 
//////////////////////////////////////////////////////////////////////////
// Redirect call to GetAllAddons function   
 
TStrArray &CToDoWindowHack::GetActiveAddons( void )
{
  if( m_arrActive.empty() )
  {
    m_arrAll.empty();
    GetAllAddons();
  }    
 
  return m_arrActive;
};
 
//////////////////////////////////////////////////////////////////////////
// Find toolbar window by unique Addon Method Name. As unique string must
// be used name of method wich add-on represent to Visual Studio.
 
LONGCToDoWindowHack::FindToolbarCID( stringstrUniqName )
{
  CRegKeytmpReg;
  LONG    lResult = S_OK;
 
  lResult = tmpReg.Open( HKEY_CURRENT_USER, DEF_ADDON_REGISTRY_CID );
  
  LONG    lCount = 0;
  string  strValName, strValue;
  DWORD   dwSize1, dwSize2, dwType;
  char    arrLong[ MAX_STRING_LENGTH ];
 
  strValName.reserve( MAX_STRING_LENGTH );
  strValue.reserve( MAX_STRING_LENGTH );
  
  // Clear All old values
  m_mapValueNames.clear();
  m_mapFloat.clear();
  m_mapTool.clear();
 
  // Find in registry all windows CID's
  do
  {
    lResult = ::RegEnumValue( tmpReg.m_hKey, lCount, 
      ( LPSTR )strValName.c_str( ), &( dwSize1 = MAX_STRING_LENGTH ), 0, 
      &dwType, ( LPBYTE )arrLong, &( dwSize2 = MAX_STRING_LENGTH ) );
 
    lCount++;
 
    if( lResult != ERROR_SUCCESS )  break;
    if( dwType != REG_BINARY ) continue;
    
    // Check if it param with needed to us name
    char *czWinPos = strstr( strValName.c_str( ), "Window178" );
    if( czWinPos == NULL ) continue;
    
    stringtmpOut;
    tmpOut.reserve( dwSize2 );
 
    // Remove all data and live only text
    for( DWORDi = 0; i < dwSize2; i++ )
      if( isalpha( arrLong[i] ) ) 
        tmpOut += arrLong[i];
 
    if( strstr( tmpOut.c_str( ), strUniqName.c_str( ) ) == NULL ) continue;
 
    czWinPos += 7;
    longlCID = 0;
 
    // Convert HEX string to LONG
    lCID = ( ( czWinPos[0]-'0' ) << 12 ) + 
           ( ( czWinPos[1]-'0' ) << 8 ) +
           ( ( czWinPos[2]-'0' ) << 4 ) +
           ( ( czWinPos[3]-'0' )  );
 
    m_mapValueNames.insert( TValMap::value_type( lCID, tmpOut ) );
  } while( 1 );
 
  // If Nothing found -- Addon must be active and be one time saved into 
  // registry --
  if( m_mapValueNames.empty() )
    return 0;
 
  // Get max value of CID in visual Studio for addon's
  TValIterj = m_mapValueNames.end(); j--;
  m_lMaxId = j->first - DEF_TOOLBAR_ADDON_CID;
 
  // NOTE: -- after that moment we have all CID's which our toolbar can 
  // have, that is why we will try to find real quantity of addon toolbars 
  // in devstudio. --
 
  // Find in system all windows
  FindFloat( );
  FindTool( );
 
  // If Nothing Found
  if( m_mapTool.empty() && m_mapFloat.empty() )
    return 0;
 
  // find our window
  return FindInMaps();
};  
 
//////////////////////////////////////////////////////////////////////////
// Try to find in tool and float maps our addon window. Can be found only 
// one window that is we call return on first found result.
 
longCToDoWindowHack::FindInMaps( void )
{
  TValIteri;
  TIdIter  k;
 
  // Find In Float
  if( !m_mapFloat.empty() )
  {
    for( i = m_mapValueNames.begin(); i != m_mapValueNames.end(); i++ )
    {
      k = m_mapFloat.find( i->first );
      
      if( k == m_mapFloat.end() )
        continue;
      else
      {
        m_lCIDToolbar = k->first;
        m_hToolBar = k->second;
        
        return m_lCIDToolbar;
      }
      
    }
  }
  
  // Find in docked toolbars
  if( !m_mapTool.empty() )
  {
    for( i = m_mapValueNames.begin(); i != m_mapValueNames.end(); i++ )
    {
      k = m_mapTool.find( i->first );
      
      if( k == m_mapTool.end() )
        continue;
      else
      {
        m_lCIDToolbar = k->first;
        m_hToolBar = k->second;
        
        return m_lCIDToolbar;
      }
      
    }
  }
 
  return 0;
}
 
//////////////////////////////////////////////////////////////////////////
// Find all toolbars docked to ToolbarsParent 
 
BOOLCToDoWindowHack::FindTool()
{
  if( m_hToolBarsParent == NULL )
    FindToolBarsParent();
 
  HWNDhTmp = NULL;
  longlId  = DEF_TOOLBAR_ADDON_CID;
  
  do
  {
    hTmp = ::GetDlgItem( m_hToolBarsParent, lId );
    
    if( hTmp != NULL )
      m_mapTool.insert( TIdMap::value_type( ::GetDlgCtrlID( hTmp ), hTmp ) );
    
    lId++;
    
    // If no addon toolbars found
    if( ( lId - DEF_TOOLBAR_ADDON_CID > m_lMaxId ) && ( hTmp == NULL ) )
      break;
    
  } while( 1 );
  
  return TRUE;
}
 
//////////////////////////////////////////////////////////////////////////
// Find all float toolbars which was not docked on toolbarsParent window
 
BOOLCToDoWindowHack::FindFloat()
{
  if( m_hDevStudio == NULL )
    FindDevStudioWindow();
  
  HWNDhTmp = ::GetDesktopWindow();
  
  hTmp = ::GetWindow( hTmp, GW_CHILD );
  
  do
  {
    hTmp = ::GetWindow( hTmp, GW_HWNDNEXT );
    
    if( ::GetParent( hTmp ) ==  m_hDevStudio )
    {
      HWNDhSec = ::GetWindow( hTmp, GW_CHILD );
      
      if( hSec != NULL )
      {
        HWNDhTest = ::GetWindow( hSec, GW_HWNDNEXT );
        
        if( hTest == NULL )
        {
          longlSecID = ::GetDlgCtrlID( hSec );
          
          if( ( lSecID - DEF_TOOLBAR_ADDON_CID > 0 ) && 
              ( lSecID - DEF_TOOLBAR_ADDON_CID <= m_lMaxId ) )
            m_mapFloat.insert( TIdMap::value_type( ::GetDlgCtrlID( hSec ), 
                               hSec ) );
        }
      }
    }
    
  } while( hTmp != NULL );
  
  return TRUE;
}
 
#endif // _TODO_ADDON_WINDOWS_HACK_
 
//:> end of file

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


Written By
CEO ArtfulBits Inc.
Ukraine Ukraine
Name:Kucherenko Oleksandr

Born:September 20, 1979

Platforms: Win32, Linux; - well known and MS-DOS; Win16; OS/2 - old time not touched;

Hardware: IBM PC

Programming Languages: Assembler (for Intel 80386); Borland C/C++; Borland Pascal; Object Pascal; Borland C++Builder; Delphi; Perl; Java; Visual C++; Visual J++; UML; XML/XSL; C#; VB.NET; T-SQL; PL/SQL; and etc.

Development Environments: MS Visual Studio 2001-2008; MS Visual C++; Borland Delphi; Borland C++Builder; C/C++ any; Rational Rose; GDPro; Together and etc.

Libraries: STL, ATL, WTL, MFC, NuMega Driver Works, VCL; .NET 1.0, 1.1, 2.0, 3.5; and etc.

Technologies: Client/Server; COM; DirectX; DirectX Media; BDE; HTML/DHTML; ActiveX; Java Servlets; DCOM; COM+; ADO; CORBA; .NET; Windows Forms; GDI/GDI+; and etc.

Application Skills: Databases - design and maintain, support, programming; GUI Design; System Programming, Security; Business Software Development. Win/Web Services development and etc.

Comments and Discussions

 
Generaladding menu items to (right click) context menues Pin
robinraul12-Mar-03 11:58
robinraul12-Mar-03 11:58 
Generalmaking an undermenue in the toolbar,with my addins. Pin
schaereran@gmx.net13-Feb-03 21:11
schaereran@gmx.net13-Feb-03 21:11 
GeneralWindow178 -> Window1ba Pin
Mike Eriksson10-Jan-03 2:22
Mike Eriksson10-Jan-03 2:22 
GeneralRe: Window178 -> Window1ba Pin
Oleksandr Kucherenko14-Apr-03 22:38
Oleksandr Kucherenko14-Apr-03 22:38 
GeneralWonderful Pin
13-Nov-01 12:33
suss13-Nov-01 12:33 
Thanks for this tutorial.
Like beguinner now I'm thinking on migrate to VB. Cry | :((
But first one last question: exists anyway to put file numbers in the grey column like happens on UltraEdit?

thanks
GeneralHiding / Showing Toolbar buttons Pin
13-Sep-01 6:13
suss13-Sep-01 6:13 
GeneralRe: Hiding / Showing Toolbar buttons Pin
HJo14-Apr-03 22:24
HJo14-Apr-03 22:24 
GeneralSetting Toolbar Names... Pin
Anna-Jayne Metcalfe20-Aug-01 3:35
Anna-Jayne Metcalfe20-Aug-01 3:35 
GeneralRe: Setting Toolbar Names... Pin
George20-Aug-01 3:50
George20-Aug-01 3:50 
GeneralRe: Setting Toolbar Names... [modified] Pin
Anna-Jayne Metcalfe20-Aug-01 5:40
Anna-Jayne Metcalfe20-Aug-01 5:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.