Finding an add-in toolbar in Visual Studio






3.88/5 (8 votes)
Aug 19, 2001
3 min read

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