38 votes

Ajouter un bouton d'action du navigateur dans Internet Explorer BHO

Donc, je travaille sur un BHO dans IE et je veux ajouter un Action du navigateur comme ça :

enter image description here

Dans Internet Explorer, cela ressemblerait à quelque chose comme

enter image description here

Les seuls tutoriels et documents que j'ai trouvés concernaient la création d'éléments de barre d'outils. Aucun ne mentionne cette option. Je sais que c'est possible car Crossrider vous permet de faire exactement la même chose. Je ne sais simplement pas comment.

Je n'arrive pas à trouver de documentation sur la façon dont je pourrais implémenter ceci dans un BHO. Toute indication est la bienvenue.

Je l'ai étiqueté avec C# car une solution C# serait probablement plus simple mais une solution C++, ou toute autre solution qui fonctionne est également la bienvenue.

20voto

manuell Points 5049

EDIT: https://github.com/somanuell/SoBrowserAction


Voici une capture d'écran de mon travail en cours.

New button in IE9

Les choses que j'ai fait:

1. S'échapper de la mode protégé

Le BHO Inscription doit mettre à jour l' HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy - clés. Voir la Compréhension et le Travail en Mode Protégé d'Internet Explorer.

- Je choisir le processus, car il est noté comme "meilleure pratique" et est plus facile à déboguer, mais l' RunDll32Policy peut faire l'affaire aussi.

Localiser l' rgs le fichier contenant votre BHO les paramètres du registre. C'est l'une contenant le upadte à la Clé de Registre 'Browser Helper Object'. Ajoutez à cela le fichier suivant:

HKLM {
  NoRemove SOFTWARE {
    NoRemove Microsoft {
      NoRemove 'Internet Explorer' {
        NoRemove 'Low Rights' {
          NoRemove ElevationPolicy {
            ForceRemove '{AE6E5BFE-B965-41B5-AC70-D7069E555C76}' {
              val AppName = s 'SoBrowserActionInjector.exe'
              val AppPath = s '%MODULEPATH%'
              val Policy = d '3'
            }
          }
        }
      }
    }
  }
}

Le GUID doit être nouvelle, n'utilisez pas de mine, l'utilisation d'un Générateur de GUID. L' 3 de la valeur de la politique garantit que le processus de courtier sera lancé en tant que moyen d'un processus d'intégrité. L' %MODULEPATH%macro n'est PAS prédéfini.

Pourquoi utiliser une macro? Vous pouvez éviter que le nouveau code dans votre fichier RGS, à condition que votre fichier MSI contient cette mise à jour du registre. En traitant avec MSI peuvent être douloureuses, il est souvent plus facile de fournir une "auto-inscription" paquet. Mais si vous n'avez pas utiliser une macro, vous ne pouvez pas permettre à l'utilisateur de choisir le répertoire d'installation. À l'aide d'une macro permet de mettre à jour dynamiquement le registre avec le bon répertoire d'installation.

Comment faire de la macro fonctionne? Localiser l' DECLARE_REGISTRY_RESOURCEID macro dans l'en-tête de votre BHO classe et de la commenter. Ajoutez la fonction suivante de la définition qui en-tête:

static HRESULT WINAPI UpdateRegistry( BOOL bRegister ) throw() {
   ATL::_ATL_REGMAP_ENTRY regMapEntries[2];
   memset( &regMapEntries[1], 0, sizeof(ATL::_ATL_REGMAP_ENTRY));
   regMapEntries[0].szKey = L"MODULEPATH";
   regMapEntries[0].szData = sm_szModulePath;
   return ATL::_pAtlModule->UpdateRegistryFromResource(IDR_CSOBABHO, bRegister,
                                                       regMapEntries);
}

Ce code est emprunté à partir de l'ATL de mise en œuvre pour l' DECLARE_REGISTRY_RESOURCEID (dans mon cas, c'est celui fourni avec VS2010, vérifiez votre version de l'ATL et le code de mise à jour si nécessaire). L' IDR_CSOBABHO macro est l'ID de la ressource de l' REGISTRY des ressources de l'ajout de la RGS dans votre fichier RC.

L' sm_szModulePath variable doit contenir le chemin d'installation du processus de courtier EXE. J'ai choisi de faire un public statique de la variable de membre de mon BHO classe. Un moyen simple, il est dans l' DllMain fonction. Lors de l' regsvr32 chargement de votre fichier Dll, DllMain est appelé, et le registre est mis à jour avec le bon chemin.

extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {

   if ( dwReason == DLL_PROCESS_ATTACH ) {
      DWORD dwCopied = GetModuleFileName( hInstance,
                                          CCSoBABHO::sm_szModulePath,
                                          sizeof( CCSoBABHO::sm_szModulePath ) /
                                                        sizeof( wchar_t ) );
      if ( dwCopied ) {
         wchar_t * pLastAntiSlash = wcsrchr( CCSoBABHO::sm_szModulePath, L'\\' );
         if ( pLastAntiSlash ) *( pLastAntiSlash ) = 0;
      }
   }

   return _AtlModule.DllMain(dwReason, lpReserved);

}

Un grand merci à Mladen Janković.

Comment faire pour lancer le processus de Courtier?

Une place est dans l' SetSite mise en œuvre. Il va être lancé plusieurs fois, mais nous traiterons que du processus lui-même. Nous verrons plus loin que le processus de courtier peut recevoir comme argument le HWND de l'hébergement IEFrame. Cela peut être fait avec l' IWebBrowser2::get_HWND méthode. Je suppose ici que votre déjà un IWebBrowser2* membre.

STDMETHODIMP CCSoBABHO::SetSite( IUnknown* pUnkSite ) {

   if ( pUnkSite ) {
      HRESULT hr = pUnkSite->QueryInterface( IID_IWebBrowser2, (void**)&m_spIWebBrowser2 );
      if ( SUCCEEDED( hr ) && m_spIWebBrowser2 ) {
         SHANDLE_PTR hWndIEFrame;
         hr = m_spIWebBrowser2->get_HWND( &hWndIEFrame );
         if ( SUCCEEDED( hr ) ) {
            wchar_t szExeName[] = L"SoBrowserActionInjector.exe";
            wchar_t szFullPath[ MAX_PATH ];
            wcscpy_s( szFullPath, sm_szModulePath );
            wcscat_s( szFullPath, L"\\" );
            wcscat_s( szFullPath, szExeName );
            STARTUPINFO si;
            memset( &si, 0, sizeof( si ) );
            si.cb = sizeof( si );
            PROCESS_INFORMATION pi;
            wchar_t szCommandLine[ 64 ];
            swprintf_s( szCommandLine, L"%.48s %d", szExeName, (int)hWndIEFrame );
            BOOL bWin32Success = CreateProcess( szFullPath, szCommandLine, NULL,
                                                NULL, FALSE, 0, NULL, NULL, &si, &pi );
            if ( bWin32Success ) {
               CloseHandle( pi.hThread );
               CloseHandle( pi.hProcess );
            }
         }
      }

      [...] 

2. L'injection de la IEFrame fils

Il semble que ce peut être la partie la plus complexe, car il existe de nombreuses façons de le faire, chacun présentant des avantages et des inconvénients.

Le processus de courtier, le "injecteur", peut-être une courte durée, avec un argument simple (un HWND ou un TID), qui devra composer avec un unique IEFrame, si ce n'est déjà traitées par l'instance précédente.

Plutôt, la "injecteur" peut-être une longue durée de vie, finalement, ne s'arrêtant jamais, processus qui devront continuer à regarder le Bureau, le traitement de nouvelles IEFrames tels qu'ils apparaissent. L'unicité du processus peut être garanti par un Mutex Nommé.

Pour le moment, je vais essayer d'aller avec un principe KISS (Keep It Simple, Stupid). Que est: un court vécu de l'injecteur. Je sais pour sûr que cela conduira à un traitement spécial, dans le BHO, pour le cas d'un Onglet de Glisser Et Déposer des ed sur le Bureau, mais je vais voir ça plus tard.

Va que la route fait une injection de Dll qui survit à la fin de l'injecteur, mais je vais déléguer cela à la Dll elle-même.

Voici le code de l'injecteur processus. Il installe un WH_CALLWNDPROCRET crochet pour le fil d'hébergement de la IEFrame, utilisez SendMessage (avec un message enregistré) pour immédiatement déclencher l'injection de Dll, puis supprime le crochet et se termine. Le BHO Dll devez exporter CallWndRetProc rappel nommé HookCallWndProcRet. Erreur chemins sont omis.

#include <Windows.h>
#include <stdlib.h>

typedef LRESULT (CALLBACK *PHOOKCALLWNDPROCRET)( int nCode, WPARAM wParam, LPARAM lParam );
PHOOKCALLWNDPROCRET g_pHookCallWndProcRet;
HMODULE g_hDll;
UINT g_uiRegisteredMsg;

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE, char * pszCommandLine, int ) {

   HWND hWndIEFrame = (HWND)atoi( pszCommandLine );
   wchar_t szFullPath[ MAX_PATH ];
   DWORD dwCopied = GetModuleFileName( NULL, szFullPath,
                                       sizeof( szFullPath ) / sizeof( wchar_t ) );
   if ( dwCopied ) {
      wchar_t * pLastAntiSlash = wcsrchr( szFullPath, L'\\' );
      if ( pLastAntiSlash ) *( pLastAntiSlash + 1 ) = 0;
      wcscat_s( szFullPath, L"SoBrowserActionBHO.dll" );
      g_hDll = LoadLibrary( szFullPath );
      if ( g_hDll ) {
         g_pHookCallWndProcRet = (PHOOKCALLWNDPROCRET)GetProcAddress( g_hDll,
                                                                      "HookCallWndProcRet" );
         if ( g_pHookCallWndProcRet ) {
            g_uiRegisteredMsg = RegisterWindowMessage( L"SOBA_MSG" );
            if ( g_uiRegisteredMsg ) {
               DWORD dwTID = GetWindowThreadProcessId( hWndIEFrame, NULL );
               if ( dwTID ) {
                  HHOOK hHook = SetWindowsHookEx( WH_CALLWNDPROCRET,
                                                  g_pHookCallWndProcRet,
                                                  g_hDll, dwTID );
                  if ( hHook ) {
                     SendMessage( hWndIEFrame, g_uiRegisteredMsg, 0, 0 );
                     UnhookWindowsHookEx( hHook );
                  }
               }
            }
         }
      }
   }
   if ( g_hDll ) FreeLibrary( g_hDll );
   return 0;
}

3. Survivant de l'Injection: "hook me harder"

La temporaire de chargement de la Dll dans le principal IE processus est suffisant pour ajouter un nouveau bouton à la Barre d'outils. Mais le fait d'être en mesure de surveiller l' WM_COMMAND pour que le bouton nouveau nécessite plus: un chargé en permanence de Dll et d'un crochet toujours en place malgré la fin de l'accrochage processus. Une solution simple est de connecter le fil à nouveau, passant de la Dll handle d'instance.

Comme chaque onglet de l'ouverture d'un nouveau BHO instanciation, un nouveau injecteur processus, la fonction de raccordement doit avoir un moyen de savoir si le thread actuel est déjà accro (je ne veux pas simplement ajouter un crochet pour chaque onglet d'ouverture, qui n'est pas propre)

Thread Local Storage est le chemin à parcourir:

  1. Attribuer un index TLS en DllMain, pour DLL_PROCESS_ATTACH.
  2. Magasin de la nouvelle - HHOOK TLS de données, et l'utiliser pour savoir si l' thread est déjà accro
  3. Décrocher, si nécessaire, lors de l' DLL_THREAD_DETACH
  4. Gratuit le TLS indice en DLL_PROCESS_DETACH

Qui mène le code suivant:

// DllMain
// -------
   if ( dwReason == DLL_PROCESS_ATTACH ) {
      CCSoBABHO::sm_dwTlsIndex = TlsAlloc();

      [...]

   } else if ( dwReason == DLL_THREAD_DETACH ) {
      CCSoBABHO::UnhookIfHooked();
   } else if ( dwReason == DLL_PROCESS_DETACH ) {
      CCSoBABHO::UnhookIfHooked();
      if ( CCSoBABHO::sm_dwTlsIndex != TLS_OUT_OF_INDEXES )
         TlsFree( CCSoBABHO::sm_dwTlsIndex );
   }

// BHO Class Static functions
// --------------------------
void CCSoBABHO::HookIfNotHooked( void ) {
   if ( sm_dwTlsIndex == TLS_OUT_OF_INDEXES ) return;
   HHOOK hHook = reinterpret_cast<HHOOK>( TlsGetValue( sm_dwTlsIndex ) );
   if ( hHook ) return;
   hHook = SetWindowsHookEx( WH_CALLWNDPROCRET, HookCallWndProcRet,
                             sm_hModule, GetCurrentThreadId() );
   TlsSetValue( sm_dwTlsIndex, hHook );
   return;
}

void CCSoBABHO::UnhookIfHooked( void ) {
   if ( sm_dwTlsIndex == TLS_OUT_OF_INDEXES ) return;
   HHOOK hHook = reinterpret_cast<HHOOK>( TlsGetValue( sm_dwTlsIndex ) );
   if ( UnhookWindowsHookEx( hHook ) ) TlsSetValue( sm_dwTlsIndex, 0 );
}

Nous avons maintenant presque au complet crochet de la fonction:

LRESULT CALLBACK CCSoBABHO::HookCallWndProcRet( int nCode, WPARAM wParam,
                                                LPARAM lParam ) {
   if ( nCode == HC_ACTION ) {
      if ( sm_uiRegisteredMsg == 0 )
         sm_uiRegisteredMsg = RegisterWindowMessage( L"SOBA_MSG" );
      if ( sm_uiRegisteredMsg ) {
         PCWPRETSTRUCT pcwprets = reinterpret_cast<PCWPRETSTRUCT>( lParam );
         if ( pcwprets && ( pcwprets->message == sm_uiRegisteredMsg ) ) {
            HookIfNotHooked();
            HWND hWndTB = FindThreadToolBarForIE9( pcwprets->hwnd );
            if ( hWndTB ) {
               AddBrowserActionForIE9( pcwprets->hwnd, hWndTB );
            }
         }
      }
   }
   return CallNextHookEx( 0, nCode, wParam, lParam);
}

Le code pour AddBrowserActionForIE9 sera édité plus tard.

Pour IE9, l'obtention de la TUBERCULOSE est assez simple:

HWND FindThreadToolBarForIE9( HWND hWndIEFrame ) {
   HWND hWndWorker = FindWindowEx( hWndIEFrame, NULL,
                                   L"WorkerW", NULL );
   if ( hWndWorker ) {
      HWND hWndRebar= FindWindowEx( hWndWorker, NULL,
                                    L"ReBarWindow32", NULL );
      if ( hWndRebar ) {
         HWND hWndBand = FindWindowEx( hWndRebar, NULL,
                                       L"ControlBandClass", NULL );
         if ( hWndBand ) {
            return FindWindowEx( hWndBand, NULL,
                                 L"ToolbarWindow32", NULL );
         }
      }
   }
   return 0;
}

4. Le traitement de la Barre d'outils

Cette partie peut être largement améliorée:

  1. Je viens de créer un bitmap en noir et blanc, et tout était bien, c'est: les pixels noirs où transparent. Chaque fois que j'ai essayé d'ajouter un peu de couleurs et/ou de niveaux de gris, les résultats ont été horribles. Je ne suis pas à l'aise, à tous, avec ces "bitmap dans la barre d'outils de la magie"
  2. La taille de l'image bitmap doit dépend de la taille de l'autre bitmaps déjà dans la barre d'outils. J'ai juste utilisé deux images (une "normale", et un "gros")
  3. Il peut être possible d'optimiser la part de la force, c'est à dire "redessiner" l'état nouveau de la barre d'outils, avec une moindre largeur de la barre d'adresse. Il fonctionne, il est un moyen rapide de "redessiner" phase impliquant l'ensemble de l'IE de la Fenêtre Principale.

Voir mon autre réponse à la question, comme je suis actuellement dans l'impossibilité de modifier la réponse avec le format de code de travail.

11voto

Oliver Salzburg Points 2455

Après un examen plus approfondi, je me suis rendu compte que la "barre d'outils de favoris et d'actions" n'est qu'une simple barre d'outils de contrôles communs (j'avais précédemment supposé qu'il s'agissait d'un contrôle personnalisé).

Je n'ai pas encore pu ajuster mon code et voir où cela me mène, mais l'approche devrait être légèrement différente de ce que j'ai décrit ci-dessous.

D'après ce que je sais, si vous voulez que votre bouton de barre d'outils comporte une image, vous devez d'abord insérer cette image dans la liste des images de la barre d'outils ( TB_GETIMAGELIST pour récupérer la liste, TB_ADDBITMAP pour ajouter votre image).

Maintenant, nous pouvons créer notre TBBUTTON et l'envoyer à notre barre d'outils avec l'option TB_ADDBUTTONS o TB_INSERBUTTONS message.

Ça devrait mettre le bouton sur la barre. Mais comment le relier à votre code ?

La barre d'outils génère un WM_COMMAND lorsque le bouton est cliqué (probablement avec l'option iCommand membre de la TBBUTTON dans le mot bas de la wParam ). Il suffit donc de SetWindowsHookEx con WH_CALLWNDPROC et attendre ce message...

Mise en œuvre à venir quand je l'aurai fait fonctionner ;)


Réponse originale

Comme nous en avons discuté précédemment sur le chat, je doute qu'il existe un moyen officiellement pris en charge pour ajouter des boutons supplémentaires (ou tout autre élément d'interface utilisateur) à cet endroit de l'interface utilisateur d'Internet Explorer.

Cependant, il existe toujours la méthode de la "force brute" qui consiste à créer simplement une nouvelle fenêtre enfant à l'intérieur de la fenêtre d'Internet Explorer.

Jusqu'à présent, je n'ai pas été en mesure de créer un exemple complet, principalement parce que mes tentatives pour redimensionner la barre d'outils, sur laquelle se trouvent les 3 boutons d'action, ont échoué.

Bref, voici ce que j'ai pu trouver jusqu'à présent :

internal class MyButtonFactory
{
  public void Install()
  {

    IntPtr ieFrame = WinApi.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "IEFrame", null);
    IntPtr navigationBar = WinApi.FindWindowEx(ieFrame, IntPtr.Zero, "WorkerW", "Navigation Bar");
    IntPtr reBar = WinApi.FindWindowEx(navigationBar, IntPtr.Zero, "ReBarWindow32", null);
    IntPtr controlBar = WinApi.FindWindowEx(reBar, IntPtr.Zero, "ControlBandClass", null);
    IntPtr toolsBar = WinApi.FindWindowEx(controlBar, IntPtr.Zero, "ToolbarWindow32", "Favorites and Tools Bar");

    IntPtr myButton = WinApi.CreateWindowEx(0, "Button", "MySpecialButtonName",
                                            WinApi.WindowStyles.WS_CHILD | WinApi.WindowStyles.WS_VISIBLE, 0, 0, 16,
                                            16,
                                            toolsBar, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

    if (IntPtr.Zero == myButton)
    {
      Debug.WriteLine(new Win32Exception(Marshal.GetLastWin32Error()).Message);
    }

    IntPtr buttonWndProc = Marshal.GetFunctionPointerForDelegate(new WinApi.WndProc(WndProc));
    WinApi.SetWindowLongPtr(new HandleRef(this, myButton), -4, buttonWndProc); // -4 = GWLP_WNDPROC
  }

  [AllowReversePInvokeCalls]
  public IntPtr WndProc(IntPtr hWnd, WinApi.WM msg, IntPtr wParam, IntPtr lParam)
  {
    switch (msg)
    {
      case WinApi.WM.LBUTTONUP:
        MessageBox.Show("Hello World");
        break;
      default:
        return WinApi.DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return IntPtr.Zero;
  }
}

Cela nécessite quelques appels à l'API Windows, ce qui a donné lieu à un copiage de 1600 lignes de bête à partir de pinvoke.net Je vais donc l'omettre dans ce billet.

Outre le fait que je n'ai pas réussi à faire en sorte que le bouton s'intègre parfaitement à la barre d'outils, dès que je configure mon propre gestionnaire de messages de fenêtre, le bouton n'est plus affiché.

Il est évident qu'il y a encore beaucoup de travail à faire pour que cette approche fonctionne, mais j'ai pensé que je devais quand même partager ce qui a été fait jusqu'à présent.

Une autre idée qui m'est venue à l'esprit était d'ignorer toute la barre d'outils et de placer simplement le bouton à côté. C'est peut-être plus facile à gérer.

En cherchant sauvagement sur le web des termes liés à l'API Windows, je suis également tombé sur l'article de CodeProject Ajouter votre contrôle sur une autre application qui semble pouvoir être très pertinent dans ce contexte.

2voto

manuell Points 5049

Suite de mon autre réponse.

Code pour le AddBrowserActionForIE9 fonction.

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<LPARAM>( &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<LPARAM>( &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. Acheminement du clic

Le moyen le plus simple d'écouter le clic est d'attraper WM_COMMAND dans le hook et vérifier l'Id de la commande dans wParam . Le code de production réel peut être plus complet (vérifier que la WM_COMMAND provient bien de la Toolbar).

if ( pcwprets && ( pcwprets->message == WM_COMMAND ) ) {
   if ( LOWORD( pcwprets->wParam ) == 4242 ) {
      NotifyActiveBhoIE9( pcwprets->hwnd );
   }
}

Le site NotifyActiveBhoIE9 la fonction sera :

a) Trouver l'IEFrame dans le fil de discussion en cours
b) Trouver l'onglet actuellement activé pour l'IEFrame trouvé
c) Trouver le fil hébergeant l'onglet

Chaque instance de BHO aura une fenêtre invisible créée avec l'identifiant du fil dans son texte de fenêtre. Un simple FindWindow call nous donnera cette fenêtre et le BHO sera notifié avec un message.

Création de la fenêtre privée :

// 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;
}

Sites d'appel :
RegisterPrivateClass appelé DllMain quand PROCESS_ATTACH
UnregisterPrivateClass appelé DllMain quand PROCESS_DETACH
CreatePrivateWindow appelé SetSite quand pUnkSite != NULL
DestroyPrivateWindow appelé SetSite quand pUnkSite == NULL

L'implémentation de NotifyActiveBhoIE9 :

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<HWND*>( lParam ) = hWnd;
               return FALSE;
            }
         }
         return TRUE;
      }
   };

   HWND hWndFirstVisibleTab = 0;
   EnumChildWindows( hwndIEFrame, ew::ewp,
                     reinterpret_cast<LPARAM>( &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 );
}

La fenêtre invisible est connectée au BHO avec un classique : le stockage d'un this pointeur dans Windows Words.

LRESULT CALLBACK CCSoBABHO::wpPrivate( HWND hWnd, UINT uMsg,
                                       WPARAM wParam, LPARAM lParam ) {
   switch( uMsg ) {
      case WM_CREATE: {
         CREATESTRUCT * pCS = reinterpret_cast<CREATESTRUCT*>( lParam );
         SetWindowLongPtr( hWnd, GWLP_USERDATA,
                           reinterpret_cast<LONG_PTR>( pCS->lpCreateParams ) );
         return 0;
      }
      case WM_USER: {
         CCSoBABHO * pThis =
            reinterpret_cast<CCSoBABHO*>( GetWindowLongPtr( hWnd, GWLP_USERDATA ) );
         if ( pThis ) pThis->OnActionClick( wParam, lParam );
         break;
      }
      default: return DefWindowProc( hWnd, uMsg, wParam, lParam );
   }
   return 0;
}

6. Traitement de l'affaire "TAB DRAG & DROP".

Lorsque vous faites un "glisser-déposer" d'un onglet sur le bureau, IE9 crée une nouvelle fenêtre principale IEFrame, dans un nouveau thread du processus source iexplore.exe, qui héberge l'onglet.

Pour détecter cela, une solution simple est d'écouter le fichier DISPID_WINDOWSTATECHANGED événement : utilisez le IWebBrowser2::get_HWND pour récupérer la fenêtre principale actuelle d'IE. Si cette fenêtre n'est pas la même que celle précédemment enregistrée, alors l'onglet a été reparenté. Ensuite, il suffit de lancer le processus de courtage : si le nouveau cadre parent n'a pas encore le bouton, il sera ajouté.

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<HWND>( hWndIEFrame ) != m_hWndIEFrame ) {
            m_hWndIEFrame = reinterpret_cast<HWND>( hWndIEFrame );
            LaunchMediumProcess();
         }
      }
   }
   break;
}

Le projet github a été mis à jour.

1voto

JonPall Points 601

L'injection de Dll est la réponse, mon pote.

Voilà .

Edit :

Bien sûr. Il semble que vous n'ayez pas à faire d'injection de DLL, les BHO ont accès depuis l'intérieur du processus IE. Donc c'est beaucoup plus facile.

En gros, vous devez d'abord trouver la fenêtre. Donc, en modifiant la fonction pour l'adapter à vos besoins, elle ressemblera à ceci :

BOOL FindFavoritesAndToolsBar(HWND mainWnd, HWND* addressBarWnd, HWND* cmdTargetWnd)
{
  mainWnd = ::FindWindowEx( mainWnd, NULL, TEXT( "WorkerW" ), NULL );
  mainWnd = ::FindWindowEx( mainWnd, NULL, TEXT( "ReBarWindow32" ), NULL );

  *cmdTargetWnd = ::FindWindowEx
  mainWnd, NULL, TEXT( "ControlBandClass" ), NULL );

  if( *cmdTargetWnd  )
      *addressBarWnd = ::FindWindowEx(
      *cmdTargetWnd, NULL, TEXT( "ToolbarWindow32" ), L"Favorites and Tools Bar" );

  return cmdTargetWnd != NULL;
}

I used Spy++ to find it.

Le reste de la logique est la même que dans l'article que j'ai cité. Vous sous-classez pour intercepter la boucle de messages, et ajoutez vos propres gestionnaires d'événements pour votre propre bouton.

Une autre approche consiste à créer un bouton en tant que fenêtre contextuelle, à définir la fenêtre IE comme parent, à trouver la position de la "barre de favoris et d'outils" et à positionner le bouton à côté d'elle. C'est encore plus facile, mais moins élégant bien sûr.

2e édition : Désolé, je viens de voir que j'ai fait écho à une partie de la réponse d'Oliver. Cependant, si vous faites ce que j'ai écrit ci-dessus à l'intérieur du BHO, le bouton se comportera comme n'importe quel bouton propre à IE et vous en aurez le contrôle total.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X