Using Microsoft Edge in a native Windows desktop app – part 3

This article has been updated for the version of WebView2 that requires Microsoft Edge WebView2 Runtime 88.0.705.50 or newer.

In the previous article, we learned how to create a web view and display web content in a Windows desktop application. In this third article of the series, we will look in detail at navigation and handling events, in general.

Navigation overview

To navigate to a web URL you must use the method Navigate() from the ICoreWebView2 interface. The only argument this method takes is the URL of the web page. However, you must specify the scheme, such as http:// or https:// or file://. URLs of the form www.bing.com or simply bing.com do not work. For this reason, in the code shown in the previous article, you can see this helper method for navigating:

CString CWebBrowser::NormalizeUrl(CString url)
{
   if (url.Find(_T("://")) < 0)
   {
      if (url.GetLength() > 1 && url[1] == ':')
         url = _T("file://") + url;
      else
         url = _T("http://") + url;
   }

   return url;
}

void CWebBrowser::NavigateTo(CString url)
{
   m_pImpl->m_webView->Navigate(NormalizeUrl(url));
}

What is happening here, we look if the URL contains ://. If it does not, but it starts with something like C: then we prefix it with file://, otherwise with http://.

During navigation, the web view control generates several events, as follows:

(Source: docs.microsoft.com)

NavigationStarting is the first event, generated when navigation begins. If HTTP redirection occurs, then multiple NavigationStarting events will be fired. When navigation completes, the event NavigationCompleted is fired. In between these, SourceChanged, ContentLoading, and HistoryChanged events may be generated.

You can learn more about navigation events here.

If you want to display some HTML content that you have locally, or generated on the fly, and you do not actually need to go to the web, you can use the method NavigateToString() that will display the HTML content provided as a string.

Handling navigation events

To handle web content events, you must register handlers. You can do this using the ICoreWebView2 interface. For instance, to handle NavigationStarting and NavigationCompleted, call add_NavigationStarting and add_NavigationCompleted. If you no longer wish to handle these events, you can remove the handlers by calling remove_NavigationStarting and remove_NavigationCompleted.

The same approach for registering and unregistering event handlers applies to all events. For an event X there is an add_X() and remove_X() pair of methods to add and remove handlers.

You can only register events after the web view control has been created and you have a valid pointer to the ICoreWebView2 interface. In the sample application, and the code shown in the previous article, the method OnCreateWebViewCompleted() contained a call to RegisterEventHandlers(). In this method, we add the handlers for the two navigation events.

void CWebBrowser::RegisterEventHandlers()
{
   // NavigationCompleted handler
   CHECK_FAILURE(m_pImpl->m_webView->add_NavigationCompleted(
      Callback<ICoreWebView2NavigationCompletedEventHandler>(
         [this](
            ICoreWebView2*, 
            ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
         {
            m_isNavigating = false;

            BOOL success;
            CHECK_FAILURE(args->get_IsSuccess(&success));

            if (!success)
            {
               COREWEBVIEW2_WEB_ERROR_STATUS webErrorStatus{};
               CHECK_FAILURE(args->get_WebErrorStatus(&webErrorStatus));
               if (webErrorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_DISCONNECTED)
               {
                  // Do something here if you want to handle a specific error case.
                  // In most cases this isn't necessary, because the WebView will
                  // display its own error page automatically.
               }
            }

            wil::unique_cotaskmem_string uri;
            m_pImpl->m_webView->get_Source(&uri);

            if (wcscmp(uri.get(), L"about:blank") == 0)
            {
               uri = wil::make_cotaskmem_string(L"");
            }

            auto callback = m_callbacks[CallbackType::NavigationCompleted];
            if (callback != nullptr)
               RunAsync(callback);

            return S_OK;
         })
      .Get(),
            &m_navigationCompletedToken));

   // NavigationStarting handler
   CHECK_FAILURE(m_pImpl->m_webView->add_NavigationStarting(
      Callback<ICoreWebView2NavigationStartingEventHandler>(
         [this](
            ICoreWebView2*,
            ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
         {
            wil::unique_cotaskmem_string uri;
            CHECK_FAILURE(args->get_Uri(&uri));

            m_isNavigating = true;
            
            return S_OK;
         }).Get(), &m_navigationStartingToken));
}

The functions add_NavigationStarting() and add_NavigationCompleted, as well as the other event handler registration methods, take two arguments: the first is a pointer to a callback that is invoked when the event occurs, and the second is a pointer to a EventRegistrationToken object, which represent a reference to a delegate (the callback) that receives change notifications. This token is set by the function and must be passed to the method that removes the event handler. In other words, the token received from add_NavigationStarting() must be passed to remove_NavigationStarting() in order to be able to remove the event handler.

What we do in the event handlers above is the following. At the start of the navigation, we only set a Boolean flag that indicates that the navigation is in progress. We need this for instance if we want to have a button that we can press to stop loading a page if that takes too long. At the end of the navigation, the flag is reset but we also invoke a callback, if any was set by the caller when starting the navigation. In the demo app, we use a callback for the navigation completion from the main frame in order to update the URL in the toolbar with the URL resulted after the navigation, which may not be the original one (because HTTP redirects can occur).

In the previous article, we saw a method called CloseWebView() what closed the web view control. Here is the method updated with removing handlers for the navigation events.

void CWebBrowser::CloseWebView()
{
   if (m_pImpl->m_webView)
   {
      m_pImpl->m_webView->remove_NavigationCompleted(m_navigationCompletedToken);
      m_pImpl->m_webView->remove_NavigationStarting(m_navigationStartingToken);

      m_pImpl->m_webController->Close();

      m_pImpl->m_webController = nullptr;
      m_pImpl->m_webView = nullptr;
      m_pImpl->m_webSettings = nullptr;
   }

   m_pImpl->m_webViewEnvironment = nullptr;
}

Handling other events

Let us look at another example for handling events. For this purpose we will consider the DocumentTitleChanged event that is occurring when the DocumentTitle property of the web view changes. This may occur before or after the NavigationCompleted event. To add/remove a handler for this event, you need a pointer to the ICoreWebView2 interface.

We can handle this event as follows, by adding a handler in the RegisterEventHandlers method we saw above.

CHECK_FAILURE(m_pImpl->m_webView->add_DocumentTitleChanged(
   Callback<ICoreWebView2DocumentTitleChangedEventHandler>(
      [this](ICoreWebView2* sender, IUnknown* args) -> HRESULT {
         wil::unique_cotaskmem_string title;
         CHECK_FAILURE(sender->get_DocumentTitle(&title));

         m_strTitle = title.get();
         
         auto callback = m_callbacks[CallbackType::TitleChanged];
         if (callback != nullptr)
            RunAsync(callback);

         return S_OK;
      })
   .Get(), &m_documentTitleChangedToken));

What we do here, is retrieving the title of the document and storing it in the class. Then, if a callback was set for this event we invoke it. We can modify the creation of the web view and install a callback for this event so that every time a page is loaded and the title changes we update the title of the main window of the application.

void CMfcEdgeDemoView::OnInitialUpdate()
{
   CView::OnInitialUpdate();

   this->ModifyStyleEx(WS_EX_CLIENTEDGE | WS_EX_WINDOWEDGE, 0, 0);
   this->ModifyStyle(WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME | WS_BORDER, 0, 0);

   m_pWebBrowser = std::make_unique<CWebBrowser>();

   if (m_pWebBrowser != nullptr)
   {
      CRect rectClient;
      GetClientRect(rectClient);

      m_pWebBrowser->CreateAsync(
         WS_VISIBLE | WS_CHILD,
         rectClient,
         this,
         1,
         [this]() {
            m_pWebBrowser->SetParentView(this);
            m_pWebBrowser->DisablePopups();
            m_pWebBrowser->Navigate(L"https://bing.com", nullptr);

            m_pWebBrowser->RegisterCallback(CWebBrowser::CallbackType::TitleChanged, [this]() {
               CString title = m_pWebBrowser->GetTitle();

               if (GetDocument() != nullptr)
               {
                  GetDocument()->SetTitle(title);
               }

               AfxGetMainWnd()->SetWindowText(title);
            });
         });
   }
}

List of events

Currently, the following events can be handled.

Event Description Add/Remove handlers
AcceleratorKeyPressed Fires when an accelerator key or key combo is pressed or released while the WebView is focused add_AcceleratorKeyPressed
remove_AcceleratorKeyPressed
ContainsFullScreenElementChanged An HTML element inside the WebView is entering fullscreen to the size of the WebView or leaving fullscreen add_ContainsFullScreenElementChanged
remove_ContainsFullScreenElementChanged
ContentLoading Fires before any content is loaded, including scripts added with AddScriptToExecuteOnDocumentCreated add_ContentLoading
remove_ContentLoading
DocumentTitleChanged Fires when the DocumentTitle property of the WebView changes. add_DocumentTitleChanged
remove_DocumentTitleChanged
FrameNavigationCompleted Fires when a child frame has completely loaded (body.onload has fired) or loading stopped with error. add_FrameNavigationCompleted
remove_FrameNavigationCompleted
FrameNavigationStarting Fires when a child frame in the WebView requesting permission to navigate to a different URI. add_FrameNavigationStarting
remove_FrameNavigationStarting
GotFocus Fires when WebView got focus. add_GotFocus
remove_GotFocus
LostFocus Fires when WebView lost focus. add_LostFocus
remove_LostFocus
MoveFocusRequested Fires when the user tries to tab out of the WebView. add_MoveFocusRequested
remove_MoveFocusRequested
NavigationCompleted Fires when the WebView has completely loaded (body.onload has fired) or loading stopped with error. add_NavigationCompleted
remove_NavigationCompleted
NavigationStarting Fires when the WebView main frame is requesting permission to navigate to a different URI add_NavigationStarting
remove_NavigationStarting
NewWindowRequested Fires when content inside the WebView requested to open a new window, such as through window.open. add_NewWindowRequested
remove_NewWindowRequested
PermissionRequested Fires when content in a WebView requests permission to access some privileged resources. add_PermissionRequested
remove_PermissionRequested
ProcessFailed Fires when a WebView process terminated unexpectedly or become unresponsive. add_ProcessFailed
remove_ProcessFailed
HistoryChange Listen to the change of navigation history for the top level document. add_HistoryChanged
remove_HistoryChanged
ScriptDialogOpening Fires when a JavaScript dialog (alert, confirm, or prompt) will show for the webview. add_ScriptDialogOpening
remove_ScriptDialogOpening
SourceChanged Fires when the Source property changes. add_SourceChanged
remove_SourceChanged
WebMessageReceived Fires when the IsWebMessageEnabled setting is set and the top level document of the webview calls window.chrome.webview.postMessage. add_WebMessageReceived
remove_WebMessageReceived
WebResourceRequested Fires when the WebView is performing an HTTP request to a matching URL and resource context filter that was added with AddWebResourceRequestedFilter. add_WebResourceRequested
remove_WebResourceRequested
WindowCloseRequested Fires when content inside the WebView requested to close the window, such as after window.close is called. add_WindowCloseRequested
remove_WindowCloseRequested
ZoomFactorChanged Fires when the ZoomFactor property of the WebView changes, either because the caller modified the ZoomFactor property, or due to the user manually modifying the zoom. add_ZoomFactorChanged
remove_ZoomFactorChanged

Try the app

You can download, build, and try the sample app for this series from here: MfcEdgeDemo.zip (24793 downloads ) .

6 Replies to “Using Microsoft Edge in a native Windows desktop app – part 3”

  1. hi mariusbancila, This is good I’ve tested and working fine after made simple change.
    replaced code as => is_aggregate : public integral_constant {}; with is_aggregate : public integral_constant {};
    Can you please help me to create simple edge webview2 win32 in C++ with windows desktop Dynamic link library.
    Need to use this dll in VB6 to show the url.

  2. thank you for your explanation,
    i’am inspired from some ideas e.g. discover installed versions.

    what i’am stronly missing is:
    How To load resources from memory using: ICoreWebView2WebResourceRequestedEventHandler
    or in general, How to provide a scheme handler like IInternetProtocolRoot from legacy IE

    Are you motivated to show us that in part 4

  3. Hello, I am using the below function to get the document in Internet Explorer, now I wish to convert the same function to find the document in Edge Browser. So kindly help me, thanks in advance.

    bool TestID(CString csID, MSHTML::IHTMLElementPtr &htmlElement)
    {
    IWebBrowser2Ptr pWebBrowser2Ptr;
    IDispatchPtr pDisp;

    pWebBrowser2Ptr->get_Document(&pDisp);

    if(pDisp)
    {
    MSHTML::IHTMLDocument2Ptr htmlDoc(pDisp);

    if(htmlDoc)
    {
    MSHTML::IHTMLElementCollectionPtr htmlElementsPtr=htmlDoc->Getall();

    if(htmlElementsPtr)
    {
    _variant_t var(csID);
    htmlElement=htmlElementsPtr->item(var);

    if(htmlElement)
    return true;
    }
    }
    }
    return false
    }

  4. Hi Mariusbancila

    Many thanks for the articles on WebView2 they are certainly a lot more helpful than the other documentation I have seen, is it okay to use the EdgeWebBrowser class in our own applications or as a basis for implementations.

    I didn’t see any license information in the article or in the download.

    Many thanks

  5. Can you please explain how we can correctly support the NewWindowRequested event handler using the CWebBrowser class? I can’t quite understand how to support it compared to the official samples. I am very grateful of an explanation of handling the NewWindowRequested event.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.