So. I\'m working on a BHO in IE and I want to add a browser action like this:
<
Continuation from my other answer.
Code for the AddBrowserActionForIE9
function.
void AddBrowserActionForIE9( HWND hWndIEFrame, HWND hWndToolBar ) {
// do nothing if already done
LRESULT lr = SendMessage( hWndToolBar, TB_BUTTONCOUNT, 0, 0 );
UINT ButtonCount = (UINT)lr;
for ( WPARAM index = 0; index < ButtonCount; ++index ) {
TBBUTTON tbb;
LRESULT lr = SendMessage( hWndToolBar, TB_GETBUTTON, index, reinterpret_cast( &tbb ) );
if ( lr == TRUE ) {
if ( tbb.idCommand == 4242 ) return;
}
}
HIMAGELIST hImgList = (HIMAGELIST)SendMessage( hWndToolBar, TB_GETIMAGELIST, 0, 0 );
HIMAGELIST hImgListHot = (HIMAGELIST)SendMessage( hWndToolBar, TB_GETHOTIMAGELIST, 0, 0 );
HIMAGELIST hImgListPressed = (HIMAGELIST)SendMessage( hWndToolBar, TB_GETPRESSEDIMAGELIST, 0, 0 );
// load little or big bitmap
int cx, cy;
BOOL bRetVal = ImageList_GetIconSize( hImgList, &cx, &cy );
HBITMAP hBitMap = LoadBitmap( CCSoBABHO::sm_hModule,
MAKEINTRESOURCE( cx <= 17 ? IDB_BITMAP_SO_LITTLE : IDB_BITMAP_SO_BIG ) );
int iImage = -1;
if ( hImgList ) {
iImage = ImageList_Add( hImgList, hBitMap, NULL );
}
if ( hImgListHot ) {
ImageList_Add( hImgListHot, hBitMap, NULL );
}
if ( hImgListPressed ) {
ImageList_Add( hImgListPressed, hBitMap, NULL );
}
TBBUTTON tbb;
memset( &tbb, 0, sizeof( TBBUTTON ) );
tbb.idCommand = 4242;
tbb.iBitmap = iImage;
tbb.fsState = TBSTATE_ENABLED;
tbb.fsStyle = BTNS_BUTTON;
lr = SendMessage( hWndToolBar, TB_INSERTBUTTON, 0, reinterpret_cast( &tbb ) );
if ( lr == TRUE ) {
// force TB container to expand
HWND hWndBand = GetParent( hWndToolBar );
RECT rectBand;
GetWindowRect( hWndBand, &rectBand );
HWND hWndReBar = GetParent( hWndBand );
POINT ptNew = { rectBand.left - cx, rectBand.top };
ScreenToClient( hWndReBar, &ptNew );
MoveWindow( hWndBand, ptNew.x, ptNew.y, rectBand.right - rectBand.left + cx,
rectBand.bottom - rectBand.top, FALSE );
// force IE to resize address bar
RECT rect;
GetWindowRect( hWndIEFrame, &rect );
SetWindowPos( hWndIEFrame, NULL, rect.left, rect.top, rect.right - rect.left + 1,
rect.bottom - rect.top, SWP_NOZORDER );
SetWindowPos( hWndIEFrame, NULL, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, SWP_NOZORDER );
}
if ( hBitMap ) DeleteObject( hBitMap );
return;
}
5. Routing the Click
The simplest way to listen to the click is just to catch WM_COMMAND
messages in the hook and check the command Id in wParam
. Real production code may be more complete (verify that the WM_COMMAND is indeed coming from the Toolbar).
if ( pcwprets && ( pcwprets->message == WM_COMMAND ) ) {
if ( LOWORD( pcwprets->wParam ) == 4242 ) {
NotifyActiveBhoIE9( pcwprets->hwnd );
}
}
The NotifyActiveBhoIE9
function will:
a) Find the IEFrame in the current thread
b) Find the current activated tab for the found IEFrame
c) Find the thread hosting the tab
Each BHO instance will have an invisible window created with the Thread Identifier in it's Window Text. A simple FindWindow
call will give us that window and the BHO will be notified with a message.
Creating the private window:
// New Members in CCSoBABHO
static wchar_t * sm_pszPrivateClassName
static void RegisterPrivateClass( void );
static void UnregisterPrivateClass( void );
HWND m_hWndPrivate;
static LRESULT CALLBACK wpPrivate( HWND hWnd, UINT uiMsg,
WPARAM wParam, LPARAM lParam );
static wchar_t * MakeWindowText( wchar_t * pszBuffer, size_t cbBuffer,
DWORD dwTID );
bool CreatePrivateWindow( void );
bool DestroyPrivateWindow( void ) {
if ( m_hWndPrivate ) DestroyWindow( m_hWndPrivate );
};
// implementation
wchar_t * CCSoBABHO::sm_pszPrivateClassName = L"SoBrowserActionClassName";
void CCSoBABHO::RegisterPrivateClass( void ) {
WNDCLASS wndclass;
memset( &wndclass, 0, sizeof( wndclass ) );
wndclass.hInstance = sm_hInstance;
wndclass.lpszClassName = sm_pszPrivateClassName;
wndclass.lpfnWndProc = wpPrivate;
RegisterClass( &wndclass );
return;
}
void CCSoBABHO::UnregisterPrivateClass( void ) {
UnregisterClass( sm_pszPrivateClassName, sm_hInstance );
return;
}
wchar_t * CCSoBABHO::MakeWindowText( wchar_t * pszBuffer, size_t cbBuffer,
DWORD dwTID ) {
swprintf( pszBuffer, cbBuffer / sizeof( wchar_t ),
L"TID_%.04I32x", dwTID );
return pszBuffer;
}
bool CCSoBABHO::CreatePrivateWindow( void ) {
wchar_t szWindowText[ 64 ];
m_hWndPrivate = CreateWindow( sm_pszPrivateClassName,
MakeWindowText( szWindowText,
sizeof( szWindowText ),
GetCurrentThreadId() ),
0, 0, 0,0 ,0 ,NULL, 0, sm_hInstance, this );
return m_hWndPrivate ? true : false;
}
Call sites:
RegisterPrivateClass
called in DllMain
, when PROCESS_ATTACH
UnregisterPrivateClass
called in DllMain
, when PROCESS_DETACH
CreatePrivateWindow
called in SetSite
, when pUnkSite != NULL
DestroyPrivateWindow
called in SetSite
, when pUnkSite == NULL
The NotifyActiveBhoIE9 implementation:
void CCSoBABHO::NotifyActiveBhoIE9( HWND hWndFromIEMainProcess ) {
// Up to Main Frame
HWND hWndChild = hWndFromIEMainProcess;
while ( HWND hWndParent = GetParent( hWndChild ) ) {
hWndChild = hWndParent;
}
HWND hwndIEFrame = hWndChild;
// down to first "visible" FrameTab"
struct ew {
static BOOL CALLBACK ewp( HWND hWnd, LPARAM lParam ) {
if ( ( GetWindowLongPtr( hWnd, GWL_STYLE ) & WS_VISIBLE ) == 0 ) return TRUE;
wchar_t szClassName[ 32 ];
if ( GetClassName( hWnd, szClassName, _countof( szClassName ) ) ) {
if ( wcscmp( szClassName, L"Frame Tab" ) == 0 ) {
*reinterpret_cast( lParam ) = hWnd;
return FALSE;
}
}
return TRUE;
}
};
HWND hWndFirstVisibleTab = 0;
EnumChildWindows( hwndIEFrame, ew::ewp,
reinterpret_cast( &hWndFirstVisibleTab ) );
if ( hWndFirstVisibleTab == 0 ) return;
// down to first child, (in another process)
HWND hWndThreaded = GetWindow( hWndFirstVisibleTab, GW_CHILD );
if ( hWndThreaded == 0 ) return;
DWORD dwTID = GetWindowThreadProcessId( hWndThreaded, NULL );
wchar_t szWindowText[ 64 ];
HWND hWndPrivate = FindWindow( sm_pszPrivateClassName,
MakeWindowText( szWindowText,
sizeof( szWindowText ), dwTID ) );
if ( hWndPrivate ) SendMessage( hWndPrivate, WM_USER, 0, 0 );
}
The invisible window is connected to the BHO with a classic one: storing a this
pointer in Windows Words.
LRESULT CALLBACK CCSoBABHO::wpPrivate( HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam ) {
switch( uMsg ) {
case WM_CREATE: {
CREATESTRUCT * pCS = reinterpret_cast( lParam );
SetWindowLongPtr( hWnd, GWLP_USERDATA,
reinterpret_cast( pCS->lpCreateParams ) );
return 0;
}
case WM_USER: {
CCSoBABHO * pThis =
reinterpret_cast( GetWindowLongPtr( hWnd, GWLP_USERDATA ) );
if ( pThis ) pThis->OnActionClick( wParam, lParam );
break;
}
default: return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
return 0;
}
6. Processing the "TAB DRAG & DROP" case
When you "Drag and Drop" a tab to the Desktop, IE9 creates a new IEFrame Main window, in a new thread in the source iexplore.exe process, hosting the tab.
To detect that, a simple solution is to listen to the DISPID_WINDOWSTATECHANGED event: use the IWebBrowser2::get_HWND
method to retrieve the current IE main window. If that window is not the same as the previously save one, then the tab has been reparented. Then, just launch the broker process: if the new parent frame has not yet the button, it will be added.
case DISPID_WINDOWSTATECHANGED: {
LONG lFlags = pDispParams->rgvarg[ 1 ].lVal;
LONG lValidFlagsMask = pDispParams->rgvarg[ 0 ].lVal;
LONG lEnabledUserVisible = OLECMDIDF_WINDOWSTATE_USERVISIBLE |
OLECMDIDF_WINDOWSTATE_ENABLED;
if ( ( lValidFlagsMask & lEnabledUserVisible ) == lEnabledUserVisible ) {
SHANDLE_PTR hWndIEFrame = 0;
HRESULT hr = m_spIWebBrowser2->get_HWND( &hWndIEFrame );
if ( SUCCEEDED( hr ) && hWndIEFrame ) {
if ( reinterpret_cast( hWndIEFrame ) != m_hWndIEFrame ) {
m_hWndIEFrame = reinterpret_cast( hWndIEFrame );
LaunchMediumProcess();
}
}
}
break;
}
The github project has been updated.