c++internet-explorermfcwebbrowser-controlhosted-app

MFC WebBrowser Control: How many (normal) lines of code does it take to simulate Ctrl+N?


Update: Answer: Two normal lines of code required. Thanks Noseratio!

I banged my head on the keyboard for more hours than I would have cared to trying to simulate IEs Ctrl+N behavior in my hosted Browser control app. Unfortunately, due to complications which I've abstracted out of my code examples below, I can't just let IE do Ctlr+N itself. So I have to do it manually.

Keep in mind that I am running a hosted browser. So typically, opening links in new windows will actuall open it within a new "tab" within my application (it's not really a tab, but another window... but appearance-wise it's a tab). However, Ctrl+N is different -- here, it is expected a fully-fledged IE window will launch when pressed.

I think my problem is that of framing the questions -- admittedly I am new to WebBrowser control and I find it to be a lot of yucky. Regardless, I've scoured the Internet for the past day and couldn't come up with an elegant solution.

Basically, the ideal solution would be to call a "NewWindow" function within WebBrowser control or its affiliate libraries; however, all I was able to find where the *On*NewWindow methods, which were event handlers, not event signallers. Which I understand that most of the time, the user will be creating the events... but what about programmatic simulation?

I tried looking into an SENDMESSAGE approach where I could use the IDs that the OnNewWindow events use... that ended up in nothing than crashes. Perhaps I could go back to get it work, but I'd like confirmation is that approach is even worth my time.

The next approach, which should have been the most elegeant, but sadly didn't pan out, was like the following:

Navigate2(GetLocationURL().GetBuffer(), BrowserNavConstants::navOpenInNewWindow);

It would have worked marvelously if it weren't for the fact that the new window would open in the background, blinking in the taskbar. needing clicking to bring it to the front.

I tried to get around the limitation in a myriad of ways, including getting the dispatcher of the current context, then calling OnNewWindow2 with that IDispatch object. Then I would invoke QueryInterface on the dispatch object for an IWebBrowser control. The webBrowser control (presumably under the control of the new window) could then navigate to the page of the original context. However... this too was a pretty messy solution and in the end would cause crashes.

Finally, I resorted to manually invoking JavaScript to get the desired behavior. Really?? Was there really no more elegant a solution to my problem than the below mess of code?

        if ((pMsg->wParam == 'N') && (GetKeyState(VK_CONTROL) & 0x8000) && !(GetKeyState(VK_SHIFT) & 0x8000) && !(GetKeyState(VK_MENU) & 0x8000))
        {
            LPDISPATCH pDisp = CHtmlView::GetHtmlDocument();
            IHTMLDocument2 *pDoc;
            if (SUCCEEDED(pDisp->QueryInterface(IID_IHTMLDocument2, (void **)&pDoc))) 
            {
                IHTMLWindow2* pWnd;
                pDoc->get_parentWindow(&pWnd);
                BSTR bStrLang = ::SysAllocString(L"JavaScript");
                CString sCode(L"window.open(\"");
                sCode.Append(GetLocationURL().GetBuffer());
                sCode.Append(L"\");");
                BSTR bStrCode = sCode.AllocSysString();
                COleVariant retVal;
                pWnd->execScript(bStrCode, bStrLang, retVal);
                ::SysFreeString(bStrLang);
                ::SysFreeString(bStrCode);
                pDoc->Release();
            }
            pDisp->Release();

I find it hard to believe that I must resort to such hackery as this to get something as simple as opening a new window when the user presses Ctrl+N.

Please stackoverflow, please point out the clearly obvious thing I overlooked.


Solution

  • Ctrl-N in IE starts a new window on the same session. In your case, window.open or webBrowser.Navigate2 will create a window on a new session, because it will be run by iexplore.exe process which is separate from your app. The session is shared per-process, this is how the underlying UrlMon library works. So you'll loose all cookies and authentication cache for the new window. On the other hand, when you create a new window which hosts WebBrowser control within your own app process, you'll keep the session.

    If such behavior is OK for your needs, try first your initial Navigate2 approach, precededing it with AllowSetForegroundWindow(ASFW_ANY) call. If the new window still doesn't receive the focus correctly, you can try creating an instance of InternetExplorer.Application out-of-proc COM object, and use the same IWebBrowser2 interface to automate it. Below is a simple C# app which works OK for me, the new window is correctly brought to the foreground, no focus issues. It should not be a problem to do the same with MFC.

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace IeApp
    {
        public partial class MainForm : Form
        {
            // get the underlying WebBrowser ActiveX object;
            // this code depends on SHDocVw.dll COM interop assembly,
            // generate SHDocVw.dll: "tlbimp.exe ieframe.dll",
            // and add as a reference to the project
    
            public MainForm()
            {
                InitializeComponent();
            }
    
            private void NewWindow_Click(object sender, EventArgs e)
            {
                AllowSetForegroundWindow(ASFW_ANY);
                // could do: var ie =  new SHDocVw.InternetExplorer()
                var ie = (SHDocVw.InternetExplorer)Activator.CreateInstance(Type.GetTypeFromProgID("InternetExplorer.Application"));
                ie.Visible = true;
                ie.Navigate("http://www.example.com");
            }
    
            const int ASFW_ANY = -1;
            [DllImport("user32.dll")]
            static extern bool AllowSetForegroundWindow(int dwProcessId);
        }
    }