65.9K
CodeProject is changing. Read more.
Home

Finding an add-in toolbar in Visual Studio

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.88/5 (8 votes)

Aug 19, 2001

3 min read

viewsIcon

109950

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.

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

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 ...??? ).

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