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( ( 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>
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
classCToDoWindowHack
{
public:
CToDoWindowHack( );
~CToDoWindowHack( );
voidReset( void );
HWNDFindDevStudioWindow( void );
HWNDFindToolBarsParent( void );
HWNDFindStatusBar( void );
TStrArray &GetAllAddons( void );
TStrArray &GetActiveAddons( void );
LONGFindToolbarCID( stringstrUniqName );
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;
};
CToDoWindowHack::CToDoWindowHack()
: m_hDevStudio( NULL )
, m_hToolBarsParent( NULL )
, m_hStatusBar( NULL )
, m_lCIDToolbar( 0 )
, m_hToolBar( NULL )
, m_lMaxId( 0 )
{
}
CToDoWindowHack::~CToDoWindowHack()
{
Reset();
}
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;
}
HWNDCToDoWindowHack::FindDevStudioWindow( void )
{
if( m_hDevStudio != NULL )
returnm_hDevStudio;
HWNDhWnd = ::GetActiveWindow();
HWNDhDesktopWnd = ::GetDesktopWindow();
while( hWnd && hWnd != hDesktopWnd )
{
m_hDevStudio = hWnd;
hWnd = ::GetParent(hWnd);
}
return m_hDevStudio;
}
HWNDCToDoWindowHack::FindToolBarsParent( void )
{
if( m_hDevStudio == NULL )
FindDevStudioWindow();
m_hToolBarsParent = ::GetDlgItem( m_hDevStudio, DEF_TOOLBARS_PARENT_CID );
return m_hToolBarsParent;
}
HWNDCToDoWindowHack::FindStatusBar( void )
{
if( m_hDevStudio == NULL )
FindDevStudioWindow();
m_hStatusBar = ::GetDlgItem( m_hDevStudio, DEF_STATUSBAR_CID );
return m_hStatusBar;
}
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 );
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( tmpValue.c_str( )[0] == '1' )
m_arrActive.push_back( string( strName.c_str( ) ) );
lCount++;
} while( 1 );
}
tmpReg.Close();
return m_arrAll;
};
TStrArray &CToDoWindowHack::GetActiveAddons( void )
{
if( m_arrActive.empty() )
{
m_arrAll.empty();
GetAllAddons();
}
return m_arrActive;
};
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 );
m_mapValueNames.clear();
m_mapFloat.clear();
m_mapTool.clear();
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;
char *czWinPos = strstr( strValName.c_str( ), "Window178" );
if( czWinPos == NULL ) continue;
stringtmpOut;
tmpOut.reserve( dwSize2 );
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;
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( m_mapValueNames.empty() )
return 0;
TValIterj = m_mapValueNames.end(); j--;
m_lMaxId = j->first - DEF_TOOLBAR_ADDON_CID;
FindFloat( );
FindTool( );
if( m_mapTool.empty() && m_mapFloat.empty() )
return 0;
return FindInMaps();
};
longCToDoWindowHack::FindInMaps( void )
{
TValIteri;
TIdIter k;
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;
}
}
}
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;
}
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( ( lId - DEF_TOOLBAR_ADDON_CID > m_lMaxId ) && ( hTmp == NULL ) )
break;
} while( 1 );
return TRUE;
}
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_