Customize an IE Context Menu to Add CodeGuru Favorites

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

Contents

Introduction

Being a frequent visitor of CodeGuru forums, I have come across questions that are oft-repeated and have already been answered in detail previously. At times like these, I have struggled to search for that particular thread and found it difficult to locate, or consumes too much time to weed it out of the several threads shown by the search engine. I have always wanted to have a location where I could store these useful threads, and from this desire, came upon the idea of "Why not have all these useful links available as a quick text ready to be pasted onto the forum's editor window? And, why not have this available at my fingertips, literally; in other words, available on the click of a mouse?" Something like what you see in the figure below:

I did some research and decided to go the route
as explained here. Having done this research, I came up with a basic idea. I knew I needed to make some Registry entries to add the menu item, and I needed a simple script to perform the actions I needed to when these menu items are invoked.

Having done this, I came up with requirements for two menu items. I needed one capable of adding a given URL to my own set of favorites. I needed another menu capable of pasting my saved URLs to CodeGuru edit boxes while replying to forum posts.

About the Article and Attached Demo

This article is an attempt to provide a step-by-step guide to implementing a custom IE context menu. A knowledge of COM (Component Object Model) and ActiveX is a prerequisite and will help you understand certain portions. The sample you will develop here demonstrates just the approach and core code snippets, just enough to demonstrate concepts. The demo attached is, however, a more complete solution. It has implementation of saving the favorites to an XML file and loading them in from there. Having said this, you will notice that there is a disconnect between the attached sample and the article code. This is expected because the article is only a guide to customize an IE context menu and the attached sample is a polished solution whose foundation is still based off of the things discussed in the article. If you are interested in just installing the component and using it, please proceed to the "Installation and Usage" section.

Implementing a Custom Context Menu

Okay; it's time to get started. All you need to see a custom menu item on an IE context menu is a bunch of Registry entries and a script.

You have pretty much covered the basics. That is all there is to adding custom menu item entries. You will now add the necessary script actions to do what you want. For this part, your goals are the following:

  • When clicking on Add to CodeGuru Favorites, you want to save the URL and the document title onto a file. I will leave out the details of saving to file for the demo, but, for this article, it suffices to simply be able to get this information and show it to the user.
  • Goal number two would be to do the reverse; in other words, when right-clicking on text boxes and selecting Show CodeGuru Favorites, you want to pop up a menu that has leaf items, and on selecting one of the items, you want to paste that text to the edit box.

Let me start by saying this. The goals can be achieved in many ways and probably be achieved entirely by using JavaScripting. However, I, being a C++ guy and having limited to no knowledge on JavaScript, decided to implement it by using C++ and COM. I decided to implement the two functionalities inside an ActiveX class. So, get started.

  • Using your IDE (VS2005 or VS 6.0), create a new workspace and select the ATL COM Appwizard. Select an appropriate location and set the project name as, say, "CodeGuruFavorites". Simply finish the wizard selecting all defaults. Build.
    • If using VS6.0, navigate to the Insert menu. Select "New ATL Object". Select controls category and select "Lite Control". Click Next. For the shortname, specify "CGFavorites". Click OK. Build.
    • If using VS2005, navigate to the Project menu. Select "Add class". Select ATL. Select "ATL Control". Click Add. For the shortname, specify "CGFavorites". Click Finish. Build.
  • Go to classview, right-click on ICGFavorites, and select "Add Method" from the menu options. Type in ShowDefaultContextMenu for the method name. For parameters, type in the following: IDispatch* pDispatch, BSTR bstrTitle, BSTR bstrURL (in case of VS2005, you will have to add these parameters one by one). Hit OK/Finish. Build.
  • Similarly, add another method called ShowTextAreaContextMenu with just one parameter: IDispatch* pDispatch. Hit OK/Finish. Build.
  • Open CGFavorites.cpp and add the following code to ShowDefaultContextMenu implementation.
  • STDMETHODIMP CCGFavorites::ShowDefaultContextMenu(
       IDispatch *pDispatch, BSTR bstrTitle, BSTR bstrURL)
    {
       // TODO: Add your implementation code here
       ::MessageBoxW(NULL,bstrTitle, bstrURL,MB_OK);
       return S_OK;
    }
    
  • Open AddToCGFavorites.html and replace the script with the following:
  • <SCRIPT LANGUAGE="JavaScript">
    var parentwin = external.menuArguments; 
    var doc = parentwin.document;
    var str = new String(parentwin.event.srcElement.name);
    var oFav = new ActiveXObject("CodeguruFavorites.CGFavorites");
    oFav.ShowDefaultContextMenu(parentwin,doc.title, doc.location);
    </SCRIPT>
    
  • Launch IE. Navigate to codeguru.com. Right-click and select "Add to Codeguru Favorites". You should see a message box with the title and URL.
  • Awesome. You now have the first goal done. The MessageBox can be replaced by all sorts of fancy code to save the URL and document title to the file etc. etc.

Basically, this is all that is happening. When you right-click, IE looks at what custom menus need to be added for the current context. It then appends those menu items. When one of the custom menu items is clicked, it executes the script specified. Some of the important information is passed in as menuArguments, which is what you use to obtain the necessary information like URL, title, and window object.

Moving on to the second goal, as in showing a popup menu and pasting contents into the edit box. Start with showing a popup menu at the cursor location.

  • Start by adding the following code to create a popup menu with two sub menuitems and to show it using TrackPopupMenu API.
  • #include <exdisp.h>
    #include <shlguid.h>
    
    STDMETHODIMP CCGFavorites::ShowTextAreaContextMenu(IDispatch
                                                       *pDispatch)
    {
       // TODO: Add your implementation code here
       //create a popup menu
       HMENU hPopupMenu = CreatePopupMenu();
    
       //insert items
       InsertMenuW(hPopupMenu,0,MF_BYPOSITION,1000,L"First");
       InsertMenuW(hPopupMenu,1,MF_BYPOSITION,1001,L"Second");
    
       //get the hWnd of the browser window
       CComQIPtr<IServiceProvider> isp = pDispatch;
       CComQIPtr<IWebBrowser2> pBrowser2;
       isp->QueryService(IID_IWebBrowserApp,IID_IWebBrowser2,
                         (void**)&pBrowser2);
    
       HWND  hWnd;
       IOleWindow* pWindow = NULL;
       if (SUCCEEDED(isp->QueryService(
                                       SID_SShellBrowser,
                                       IID_IOleWindow,
                                       (void**)&pWindow)))
       {
          if (SUCCEEDED(pWindow->GetWindow(&hwnd)))
          {
             // hwnd is the handle of TabWindowClass on IE7 and
             // above and is the browser window on earlier versions
          }
          pWindow->Release();
       }
    
       //show menu
       POINT pt;
       GetCursorPos(&pt);
       int iSelection = ::TrackPopupMenu(hPopupMenu,
          TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD,
          pt.x,pt.y, 0,hWnd,NULL);
    
       DestroyMenu(hPopupMenu);
    
      return S_OK;
    }
    
    Nothing much here. The only tricky parts are how you get the browser window handle. Note that this is what you use the pDispatch for. It's a long-winded way of getting to the IWebBrowser2 interface and from there, obtain the HWND of the window. Build.
  • Open ShowCGFavorites.html and add the following code.
  • <SCRIPT LANGUAGE="JavaScript">
    var parentwin = external.menuArguments;
    var oFav = new ActiveXObject("CodeguruFavorites.CGFavorites");
    oFav.ShowTextAreaContextMenu(parentwin);
    </SCRIPT>
    
  • Launch IE. Navigate to, say, www.gmail.com and right-click on the username edit box. You should see a menu item, Show CodeGuru Favorites. Click on it. Oops... Nothing happens??
  • Not to worry. It appears that TrackPopupMenu is failing for some reason. I haven't figured out why, but, my guess is that our TrackPopupMenu is called as a result of IE also calling track popupmenu.
  • To circumvent this, you do this. Post a user-defined message to the window and popup the menu in that. Well, how do you process it because you aren't the one that created the window? Simple, by subclassing. You just subclass the hwnd, post your message, handle the message and show menu, and then unsubclass. The code below shows these changes:
  • WNDPROC fnOldWndProc;
    LRESULT CALLBACK SubclassWndProc(HWND hwnd,
       UINT uMsg,
       WPARAM wParam,
       LPARAM lParam
    )
    {
       //if it is our custom message for showing favorites list
       if (uMsg == (WM_APP + 1))
       {
          //show favorites menu
          //create a popup menu
          HMENU hPopupMenu = CreatePopupMenu();
    
          //insert items
          InsertMenuW(hPopupMenu,0,MF_BYPOSITION,1000,L"First");
          InsertMenuW(hPopupMenu,1,MF_BYPOSITION,1001,L"Second");
    
          //show menu
          POINT pt;
          GetCursorPos(&pt);
          int iSelection = ::TrackPopupMenu(hPopupMenu,
             TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD,
             pt.x,pt.y, 0,hwnd,NULL);
    
          DestroyMenu(hPopupMenu);
    
          return 0;
       }
    
       return CallWindowProc(fnOldWndProc, hwnd, uMsg,
                             wParam, lParam);
    }
    
    STDMETHODIMP CCGFavorites::ShowTextAreaContextMenu(IDispatch
       *pDispatch)
    {
       // TODO: Add your implementation code here
    
       //get the hWnd of the browser window
       CComQIPtr<IServiceProvider> isp = pDispatch;
       CComQIPtr<IWebBrowser2> pBrowser2;
       isp->QueryService(IID_IWebBrowserApp,IID_IWebBrowser2,
          (void**)&pBrowser2);
    
       HWND  hWnd;
       IOleWindow* pWindow = NULL;
       if (SUCCEEDED(isp->QueryService(
                                       SID_SShellBrowser,
                                       IID_IOleWindow,
                                       (void**)&pWindow)))
       {
          if (SUCCEEDED(pWindow->GetWindow(&hwnd)))
          {
             // hwnd is the handle of TabWindowClass on IE7 and
             // above and is the browser window on earlier versions
          }
          pWindow->Release();
       }
    
       //subclass the window here so we can process custom message
       /to show menu
       fnOldWndProc = (WNDPROC)::SetWindowLong(hWnd,GWL_WNDPROC,
          (DWORD)SubclassWndProc);
    
       //post our own message to show menu
       ::PostMessage(hWnd, (WM_APP + 1), 0,0);
    
       //restore the old WndProc back
       ::SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)fnOldWndProc);
    
       return S_OK;
    }
    
  • Build. Launch IE. Navigate to, say, www.gmail.com and right-click on the username edit box. You should see a menu item, Show CodeGuru Favorites. Click on it. Oops... This time, the menu did appear, but just flashed and disappeared!!
  • Something is still not right. Possibly, it's because you are posting a message and not waiting for it to complete. You can circumvent this hurdle by adding an event and wait for it until it finishes right after PostMessage. The idea is to create a manual reset event initially set to not-triggered and wait on it after PostMessage. The subclass procedure would set the event when it returns from TrackPopupMenu.
    Changes are as below:
  • WNDPROC fnOldWndProc;
    LRESULT CALLBACK SubclassWndProc(HWND hwnd,
       UINT uMsg,
       WPARAM wParam,
       LPARAM lParam
    )
    {
       //if it is our custom message for showing favorites list
       if (uMsg == (WM_APP + 1))
       {
          //show favorites menu
          //create a popup menu
          HMENU hPopupMenu = CreatePopupMenu();
    
          //insert items
          InsertMenuW(hPopupMenu,0,MF_BYPOSITION,1000,L"First");
          InsertMenuW(hPopupMenu,1,MF_BYPOSITION,1001,L"Second");
    
          //show menu
          POINT pt;
          GetCursorPos(&pt);
          int iSelection = ::TrackPopupMenu(hPopupMenu,
             TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD,
             pt.x,pt.y, 0,hwnd,NULL);
    
          //after showing menu, signal the event
          SetEvent((HANDLE)lParam);
    
          DestroyMenu(hPopupMenu);
    
          return 0;
       }
    
       return CallWindowProc(fnOldWndProc, hwnd, uMsg,
                             wParam, lParam);
    }
    
    STDMETHODIMP CCGFavorites::
       ShowTextAreaContextMenu(IDispatch *pDispatch)
    {
       // TODO: Add your implementation code here
    
       //get the hWnd of the browser window
       CComQIPtr<IServiceProvider> isp = pDispatch;
       CComQIPtr<IWebBrowser2> pBrowser2;
       isp->QueryService(IID_IWebBrowserApp,IID_IWebBrowser2,
                         (void**)&pBrowser2);
    
       HWND  hWnd;
       IOleWindow* pWindow = NULL;
       if (SUCCEEDED(isp->QueryService(
                                       SID_SShellBrowser,
                                       IID_IOleWindow,
                                       (void**)&pWindow)))
       {
          if (SUCCEEDED(pWindow->GetWindow(&hwnd)))
          {
             // hwnd is the handle of TabWindowClass on IE7 and
             // above and is the browser window on earlier versions
          }
          pWindow->Release();
       }
       HANDLE hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    
       //subclass the window here so we can process custom message
       //to show menu
       fnOldWndProc = (WNDPROC)::SetWindowLong(hWnd,GWL_WNDPROC,
          (DWORD)SubclassWndProc);
    
       //post our own message to show menu
       ::PostMessage(hWnd, (WM_APP + 1), 0,(LPARAM)hEvent);
    
       //Wait for the event to be signalled, indicating menu
       //is gone
       WaitForSingleObject(hEvent,INFINITE);
    
       //cleanup
       CloseHandle(hEvent);
    
       //restore the old WndProc back
       ::SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)fnOldWndProc);
    
       return S_OK;
    }
    
  • Build. Perform the same steps and invoke the custom menu. There. That is much better. The menu is shown and it stays there for you to select. Click on it. Nothing happens. That is expected because you haven't written that part yet.
  • To perform the paste operation, you can use the IWebBrowser2::ExecWB method passing in OLECMDID_PASTE as the command ID. How do you get this interface pointer in the Subclass procedure? Easy. You have this pointer already available in the showTextAreaContextMenu method. You simply have to pass it as WPARAM for the message. Do that as shown below.
    Modiy the PostMessage code as demonstrated below:
  •    //post our own message to show menu
       ::PostMessage(hWnd, (WM_APP + 1), (WPARAM)pBrowser2.p,
          (LPARAM)hEvent);
    
    Add this, after TrackPopupMenu call
    switch(iSelection)
    {
    case 1000:
       {
          CComBSTR oText(L"First one");
          CComVariant oVarIn(oText);
          CComVariant oVarOut;
          //get the IWebBrowser2 interface
          CComPtr<IWebBrowser2> pSp = (IWebBrowser2*)wParam;
          HRESULT hre = pSp->ExecWB(OLECMDID_PASTE,
             OLECMDEXECOPT_DODEFAULT,&oVarIn,&oVarOut);
       }
       break;
    case 1001:
       {
          CComBSTR oText(L"Second one");
          CComVariant oVarIn(oText);
    
          CComVariant oVarOut;
          //get the IWebBrowser2 interface
          CComPtr<IWebBrowser2> pSp = (IWebBrowser2*)wParam;
          HRESULT hre = pSp->ExecWB(OLECMDID_PASTE,
             OLECMDEXECOPT_DODEFAULT,&oVarIn,&oVarOut);
       }
       break;
    }
    
    Nothing special here. You simply extract the IWebBrowser2 interface from the wParam and call ExecWB on it.
  • Build. And, repeat the test steps on an edit box. All is fine as long as you don't select any menu item from your popup. As soon as you select one, you see an exception. Reason being, IWebBrowser2 interface has been sent across a thread boundary and per COM rules, this is a no-no. When such a situation arises, there are some procedures to follow. One way is to stream the interface; another simple procedure is to use a Global Interface Table. A Global Interface Table, or GIT for short, is a clever thing. It is a per-process entity. So, if multiple threads try to create one, the same instance is returned. It is like a bucket holding interfaces. If you want to share interface pointers across threads, you throw them into this GIT bucket. In return, you get a cookie back. You pass this cookie around to other threads and these threads in return will pass the cookie to the GIT to get a marshalled interface back. Simple. All you have to do now is to create such a GIT, throw your IWebBrowser2 interface in and pass the cookie to the subclass procedure instead of the interface pointer.
    With these changes, this is how the code looks:
  • WNDPROC fnOldWndProc;
    LRESULT CALLBACK SubclassWndProc(HWND hwnd,
       UINT uMsg,
       WPARAM wParam,
       LPARAM lParam
    )
    {
       //if it is our custom message for showing favorites list
       if (uMsg == (WM_APP + 1))
       {
          //show favorites menu
          //create a popup menu
          HMENU hPopupMenu = CreatePopupMenu();
    
          //insert items
          InsertMenuW(hPopupMenu,0,MF_BYPOSITION,1000,L"First");
          InsertMenuW(hPopupMenu,1,MF_BYPOSITION,1001,L"Second");
    
          //show menu
          POINT pt;
          GetCursorPos(&pt);
          int iSelection = ::TrackPopupMenu(hPopupMenu,
             TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD,
             pt.x,pt.y, 0,hwnd,NULL);
    
          switch(iSelection)
          {
          case 1000:
             {
                CComBSTR oText(L"First one");
                CComVariant oVarIn(oText);
    
                CComVariant oVarOut;
                //get the IWebBrowser2 interface
                CComPtr<IWebBrowser2> pSp;
                DWORD dwCookie = wParam;
                CComQIPtr<IGlobalInterfaceTable,
                   &IID_IGlobalInterfaceTable> spGIT;
                CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                   NULL,CLSCTX_INPROC_SERVER,
                   IID_IGlobalInterfaceTable,(void **)&spGIT);
                spGIT->GetInterfaceFromGlobal(dwCookie,
                   IID_IWebBrowser2,(void**)&pSp);
                HRESULT hre = pSp->ExecWB(OLECMDID_PASTE,
                   OLECMDEXECOPT_DODEFAULT,&oVarIn,&oVarOut);
             }
             break;
          case 1001:
             {
                CComBSTR oText(L"Second one");
                CComVariant oVarIn(oText);
    
                CComVariant oVarOut;
                //get the IWebBrowser2 interface
                CComPtr<IWebBrowser2^gt; pSp;
                DWORD dwCookie = wParam;
                CComQIPtr<IGlobalInterfaceTable,
                   &IID_IGlobalInterfaceTable> spGIT;
                CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                   NULL,CLSCTX_INPROC_SERVER,
                   IID_IGlobalInterfaceTable,(void **)&spGIT);
                spGIT->GetInterfaceFromGlobal(dwCookie,
                   IID_IWebBrowser2,(void**)&pSp);
                HRESULT hre = pSp->ExecWB(OLECMDID_PASTE,
                   OLECMDEXECOPT_DODEFAULT,&oVarIn,&oVarOut);
             }
             break;
          }
          //after showing menu, signal the event
          SetEvent((HANDLE)lParam);
    
          DestroyMenu(hPopupMenu);
    
          return 0;
       }
    
       return CallWindowProc(fnOldWndProc, hwnd, uMsg,
                             wParam, lParam);
    }
    
    STDMETHODIMP CCGFavorites::ShowTextAreaContextMenu(IDispatch
       *pDispatch)
    {
       // TODO: Add your implementation code here
       //create a GIT object
       //GIT is used to marshal the IWebBrowser2 interface pointer
       CComQIPtr<IGlobalInterfaceTable,
          &IID_IGlobalInterfaceTable> spGIT;
       CoCreateInstance(CLSID_StdGlobalInterfaceTable,NULL,
          CLSCTX_INPROC_SERVER,IID_IGlobalInterfaceTable,
          (void **)&spGIT);
    
       //get the hWnd of the browser window
       CComQIPtr<IServiceProvider> isp = pDispatch;
       CComQIPtr<IWebBrowser2> pBrowser2;
       isp->QueryService(IID_IWebBrowserApp,IID_IWebBrowser2,
          (void**)&pBrowser2);
    
       //register interface in global
       DWORD dwCookie = 0;
       spGIT->RegisterInterfaceInGlobal(pBrowser2,
          IID_IWebBrowser2, &dwCookie);
    
       HWND  hWnd;
       IOleWindow* pWindow = NULL;
       if (SUCCEEDED(isp->QueryService(
                                       SID_SShellBrowser,
                                       IID_IOleWindow,
                                       (void**)&pWindow)))
       {
          if (SUCCEEDED(pWindow->GetWindow(&hwnd)))
          {
             // hwnd is the handle of TabWindowClass on IE7 and
             // above and is the browser window on earlier versions
          }
          pWindow->Release();
       }
    
       HANDLE hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    
       //subclass the window here so we can process custom message
       /to show menu
       fnOldWndProc = (WNDPROC)::SetWindowLong(hWnd,GWL_WNDPROC,
          (DWORD)SubclassWndProc);
    
       //post our own message to show menu
       ::PostMessage(hWnd, (WM_APP + 1), (WPARAM)dwCookie,
          (LPARAM)hEvent);
    
       //Wait for the event to be signalled, indicating menu
       //is gone
       WaitForSingleObject(hEvent,INFINITE);
    
       //cleanup
       CloseHandle(hEvent);
    
       //restore the old WndProc back
       ::SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)fnOldWndProc);
    
       return S_OK;
    }
    
  • Build. And, repeat the test steps on an edit box. This time, you select the menu item and you no longer see an exception. However, IE is now just hung!! Why would that happen?
    This is what is happening. You will notice that, if you comment out the call to ExecWBm, all is fine. When you do call ExecWB, what happens is that it results in more window messages to the underlying window object used by COM. However, these aren't getting processed because your ShowTextAreaContextMenu still hasn't completed and is blocked on WaitForSingleObject. This results in a deadlock; hence, the IE lockup. What you need to solve this is to implement a mechanism of processing window messages while still remaining blocked on the hEvent. You have MsgWaitForMultipleObjects, which does exactly that. You introduce this code in your method now, as shown below. Simply replace the WaitForSingleObject line with this block of code:
  • //start loop
       while (TRUE)
       {
          // block-local variable
          DWORD result ;
          MSG msg ;
    
          // Read all of the messages in this next loop,
          // removing each message as we read it.
          while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
          {
             // Otherwise, dispatch the message.
             DispatchMessage(&msg);
          } // End of PeekMessage while loop.
    
          // Wait for any message sent or posted to this queue 
          // or for one of the passed handles be set to signaled.
          result = MsgWaitForMultipleObjects(1, &hEvent,
                   FALSE, INFINITE, QS_ALLINPUT);
    
          // The result tells us the type of event we have.
          if (result == (WAIT_OBJECT_0 + 1))
          {
             // New messages have arrived.
             // Continue to the top of the always while loop to
             // dispatch them and resume waiting.
             continue;
          }
          else
          {
             // Our event was signalled .. time to quit loop
             break;
          } // End of else clause.
       }    // End of the always while loop.
    
  • Build and perform the test again. Voilà!! The test is now pasted on selecte menu items!!
  • Your main goals have been achieved. However, there is a small improvement that you can do. As of now, if you need to use this component, you need to ship the DLL, and also the script HTML files and the Registry entries. This is cumbersome. It would be cool if you could somehow encapsulate all this within the DLL and be done with it.
    Interestingly, this is possible and is fairly simple to achieve:
    • Go to resource view and to CodeGuru Favorites resources, right-click, and Add/Insert new resource. In the resulting dialog, select HTML and click on "Import". Navigate to the location of the script HTML files and select it. Similarly, add the second HTML file too.
    • Open resource.h and note the values of IDR_HTML1 and IDR_HTML2.
    • Open the CGFavorites.rgs file and add the following to the end, replacing IDR_HTML1 and IDR_HTML2 by the values noted from resource.h:
    • HKCU
      {
         Software
         {
            Microsoft
            {
               'Internet Explorer'
               {
                  MenuExt
                  {
                     ForceRemove 'Add to Codeguru Favorites' =
                        s 'res://%MODULE%/IDR_HTML1'
                     {
                        val Contexts = d '1'
                     }
                     ForceRemove 'Show Codeguru Favorites' =
                        s 'res://%MODULE%/IDR_HTML2'
                     {
                        val Contexts = d '4'
                     }
                  }
               }
            }
         }
      }
      
    • Remove all the Registry entries you created manually at the beginning of the article. Launch IE again just to make sure the context menu items don't appear anymore.
    • Now, just build the project and launch IE again. You now should see the context menu entries. Thus, with this approach, you have made all the capabilities self-contained within the DLL. Just registering the DLL on the target machine for the particular user is enough to populate the right entries.

Installation and Usage

To install and use, perform the following steps:

  • Unzip the compiled_binary.zip to a local folder. Place CGFavorites.dll in a location of your preference.
  • Launch cmd.exe from Start->Run.
  • Herein, type RegSvr32 [full path to the CGFavorites.dll]. For example, if you placed the DLL in C:\TempCG, your command will be
    RegSvr32 C:\TempCG\CGFavorites.dll
    You should see a message that you successfully registered.
  • Now, launch IE. Open an URL, say, http://www.codeguru.com/forum/showthread.php?t=365351. Right-click anywhere on the browser window to pop up the default context menu. Herein, you should see a new item, "Add to CodeGuru Favorites". Select it. A dialog pops up, asking you to specify the description to use. You can modify it if needed, and then click OK. The page is now added to your CodeGuru favorites.
  • Hit Post reply on this page. You will be taken to an editor window. Herein, right-click. You should see a new menu item, Show CodeGuru Favorites, with submenus for articles and threads. Select the appropriate one and select any item. The URL and description should now be pasted at the cursor location in the editor window.
  • The favorites are stored into "My Documents" folder currently. The file stored in is CGFavorites.xml.

Update History

  • September 09, 2006: Context menus other than the CodeGuru favorites-related ones weren't functional. Fixed this bug.
  • May 16, 2007: Rearchitected. No longer need Browser Helper Object. All implementation is per Microsoft recommendation and uses a combination of script code and an ActiveX control.
  • June 27, 2008: Fixed bug appearing on IE7 tabbed windows. Using IOleWindow instead of get_HWND.


Downloads

Comments

  • Context menu closes when submenu displayed

    Posted by gcage on 05/29/2008 03:04pm

    Following the step by step instructions (they are excellent) and downloading the sample code I find that when the Show Codeguru Favorites context menu item is selected the context menu closes leaving the popup menu hanging in space. The popup menu functions correctly. How do I get the context menu to remain visible until the submenu item has been selected. By watching messages go by in SubclassWndProc it appears that the context menu is closed as a result of the WM_INITMENUPOPUP message.

    • Thanks for the clarification

      Posted by kirants on 09/21/2008 12:45pm

      I now see what you both are saying. Unfortunately, with the approach taken here, the menu will go away since, the script method is called as response to the menu and it returns immediately.

      Reply
    • I made a screencast

      Posted by waltersobchak on 09/21/2008 06:53am

      Well, its like gcage writes. The "Show Codeguru Favorites" menu entry does not contain submenus. Only when you click it, the original IE context menu disappears and the Codeguru Favorites menu with its subentries appears. Please see this screencast: http://screencast.com/t/dHHFW4wUWT

      Reply
    • More clarifications please

      Posted by kirants on 06/27/2008 08:27pm

      Hello, sorry for the delay. I am not clear on your findings. Could you please elaborate? Also, if you could turn on allowing me to contact you via email private messaging, that would help speed up communication. What IE version are you using , by the way ?

      Reply
    Reply
  • Launch a program with javascript?

    Posted by kras on 07/08/2007 08:40am

    How can i use javascript to open a program? I want to create a new entry in the context menu that opens a program and passes the right clicked url as an argument. Is that possible? regards Jimmie

    • Solution..

      Posted by kirants on 07/09/2007 08:30pm

      I am assuming you have read through the article on the basics of adding a context menu entry ( simply using the registry and adding a html file in a fixed folder location ). If you have done that and are able to get at least the message box, you can follow these steps. 
      
      For the Contexts entry, use hex 20 value. This means, to show the context menu when right clicked on an anchor tag. When this is done, and you select your menu item, the URL will can be extracted and passed on to an application ( using firefox for e.g.) like below for the javascript code:
      
      var parentwin = external.menuArguments;
      var doc = parentwin.document;
      var str = new String(parentwin.event.srcElement.href);
      v = new ActiveXObject("Shell.Application");
      
      v.ShellExecute("firefox.exe", str, "", "open", 10);
      

      Reply
    Reply
  • IWebBrowser2::Navigate does not work

    Posted by pkronk on 07/01/2007 12:53pm

    Hi, is there any possibility e.g. to navigate to a website instead of pasting the text? I tried using pSp->Navigate(L"www.myurl.com", ...); instead of pSp->ExecWB(OLECMDID_PASTE, ...); but it does not work. All IWebBrowser2 members which use navigation don't work (which are GoBack, GoForward, GoHome, Navigate, and Navigate2). There are no errors, just nothing happens. Any ideas? Thanks in advance, Peter

    Reply
  • very nice article

    Posted by dingo_kasper on 06/01/2007 07:27am

    i read it i read it... oho it's done .. i am very delightful to say that it is wonderful  and remarkable work
    
    dingo

    • Thank you

      Posted by kirants on 06/01/2007 12:12pm

      .. for your nice words. I am glad you found it interesting

      Reply
    Reply
  • Doesn't work in edit mode

    Posted by KFC123 on 04/13/2007 03:10am

    Hi there, I apply the code to the IE component at edit mode, when I click the item on encode menu, it clear the page!?

    Reply
  • How about vista?

    Posted by yecheng_110 on 03/09/2007 03:15am

    This is no file named "shdoclc.dll" in vista.

    • Should work now

      Posted by kirants on 05/23/2007 05:03pm

      Updated, actually re-architected , so should work on Vista now. Please let me know if you see a problem still

      Reply
    • Interesting..

      Posted by kirants on 03/09/2007 02:49pm

      Haven't tried it on Vista yet. Will do so and add comment. Thanks for pointing it out.

      Reply
    Reply
  • Overriding IDocHostUIHandler

    Posted by mcrosby on 12/07/2006 02:46pm

    Microsoft says "The IDocHostUIHandler interface is designed to be overridden by applications that host the WebBrowser control. It is not intended for use by Browser Helper Objects (BHOs) because, in addition to the problem of appending items to the standard menu discussed below, only one add-on at a time can override IDocHostUIHandler and multiple add-ons can easily conflict with each other." http://msdn2.microsoft.com/en-us/library/aa770042.aspx So how can this be done without overriding IDocHostUIHandler. Thanks, Murphy

    • Updated per recommendation

      Posted by kirants on 05/23/2007 05:12pm

      Updated article per recommendation. The new architecture doesn't even need to be a BHO implementation. Simple javascript and an activex component do the job. Thanks mcrosby. Appreciate your comments.

      Reply
    • IE7 Menu's

      Posted by mcrosby on 12/07/2006 02:51pm

      "In Windows Internet Explorer 7, the technique for overriding the context menu from a DocObject host is the same as Internet Explorer 6; however, the host must implement its own menu resources. The internal resources of Internet Explorer should not be used as they may change or move (as has been done in Internet Explorer 7)." I ran into this when I was displaying the Anchor menu item and "Open in New Tab" was not there. I am still trying to solve both of these problems. Thanks, Murphy

      Reply
    Reply
  • One more thing too :-)

    Posted by samdan on 11/02/2006 01:10pm

    Instead of adding codeguru page favorites, can this context menu invoking do something else? Like say, if I click on the menu item, can I open microsoft word or any other program on my machine? I think this can be easily done, but do not know how [not a ATL/COM guru and never used them :-( ]

    • Sure.

      Posted by kirants on 11/20/2006 01:05am

      The way to do this is to launch the process using a. ShellExecute b. CreateProcess

      Reply
    Reply
  • Can we use this for other context menu?

    Posted by samdan on 11/02/2006 01:07pm

    I recently happend to read your article on codeguru about customizing the context menu. It is great. I want to know one thing. Can we use the same or similar code for creating extension for context menu on IE when a word/sentence or a part of text is blocked/selected? It means when I want to copy one word or sentence, I block it using mouse and then want to have the context menu show the new item you described? Google has some such thing currently and uses it for searching depending on word selected. Thanks a lot in advance. Sam

    • Solution

      Posted by kirants on 11/04/2006 01:27am

      thanks for your nice words about article. Am glad it would be of help to you. 
       
      I think I know the answer to your question , if I understand you right. When you say text is blocked, do you mean the text has been selected ? If so, you should try this ( I haven't done this myself, but I guess that is the way ). 
       
      In CCGFavorites::ShowContextMenu, the first thing I do is check for the dwID. The dwID will be different for different items and is listed in mshtmhst.h like below:
       
      #define CONTEXT_MENU_DEFAULT        0
      #define CONTEXT_MENU_IMAGE          1
      #define CONTEXT_MENU_CONTROL        2
      #define CONTEXT_MENU_TABLE          3
      // in browse mode
      #define CONTEXT_MENU_TEXTSELECT     4 
      #define CONTEXT_MENU_ANCHOR         5
      #define CONTEXT_MENU_UNKNOWN        6
       
      What I suggest is for you to handle the case where dwID is CONTEXT_MENU_TEXTSELECT , instead of CONTEXT_MENU_CONTROL like I am doing now.

      Reply
    Reply
  • Applet in WebBrowser

    Posted by colin-123 on 09/13/2006 06:50am

    When I open a html ,which has a Applet, by the WebBrowser Control (the Control is hosted in a Dialog-based Application ) ,I got some error ("Free block 0x**** modified after it was freed") What's the reason ?

    • Some questions..

      Posted by kirants on 09/13/2006 12:37pm

      1. Does this happen only if the CGFavorites extension is installed ? 2. What version of IE is used ?

      Reply
    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • In this highly valued report, you'll learn the results of "The Forrester Wave™: Hybrid Integration For Enterprises, Q4 2016" extensive research of the top Hybrid Integration Solutions, including each product's overall ranking, specific capabilities and strengths and weaknesses.

  • Testing full recoveries of IT environments requires a proven methodology. Establishing and meeting recovery time objectives (RTOs), configuring a cloud recovery system, and tracking your changing environment are all critical components of a successful cloud recovery operation. It's also important to establish and follow a set of cloud disaster recovery (CDR) best practices. Read this technical guide to learn about these best practices, along with how disaster recovery as a service (DRaaS) can help you complete …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date