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

The WebView2 control supports different printing scenarios. In this new article, I’ll show you how to display a print dialog and, respectively, how to print to PDF.

Articles in this series:

Printing APIs

The WebView2 control is constantly evolving, with new capabilities added with each new release. However, this actually poses a problem with the API. Since existing COM interfaces should be stable, every new additions are made to a new interface. At the point of writing this article, there is ICoreWebView2, ICoreWebView2_2, ICoreWebView2_3, up to ICoreWebView2_17. There are also ICoreWebView2Environment, ICoreWebView2Environment2 and up to ICoreWebView2Environment12, ICoreWebView2EnvironmentOptions and up to ICoreWebView2EnvironmentOptions5, ICoreWebView2Settings and up to ICoreWebView2Settings7, and so on and so forth. If you ask me, the API is becoming cumbersome, but that’s the way it is and we have to live with it.

The printing APIs are the following:

  • ICoreWebView2_7 interface contains PrintToPdf (prints the current web page as a PDF document)
  • ICoreWebView2_16 interface contains Print (to print the current page to the specified printer, with the specified settings), PrintToPdfStream (prints the current web page as PDF to a stream), and ShowPrintUI (opens a print dialog).

In this article, we will discuss PrintToPdf() and ShowPrintUI().

We’ll modify the existing demo application with new options on the toolbar, as shown in the following image:

Displaying a printing dialog

The first option that we’ll address is displaying the print dialog. For this purpose, we’ll use the ICoreWebView2_16::ShowPrintUI() method. It has a single parameter, an enumeration that specifies the kind of dialog to display: system’s default or browser specific. Using this method is straight forward:

void CWebBrowser::ShowPrintUI(bool const systemDialog)
{
   if (m_pImpl->m_webView != nullptr)
   {
      CHECK_FAILURE(m_pImpl->m_webView2_16->ShowPrintUI(systemDialog ? COREWEBVIEW2_PRINT_DIALOG_KIND_SYSTEM : 
                                                                       COREWEBVIEW2_PRINT_DIALOG_KIND_BROWSER));
   }   
}

Here is the result of running with either option:

Printing to PDF

Printing to a PDF document is a very popular option nowadays. It’s no surprise it was the first printing feature supported by the WebView2 control. For this, we will use the ICoreWebView2_7::PrintToPdf() method. This method has two parameters: a pointer to a printer settings object and a completion handler (that is called when the operation has completed). If the first argument is set to null, the default printer settings are being used.

Here is how this method can be used:

void CWebBrowser::PrintToPDF(bool const landscape, std::function<void(bool, CString)> callback)
{
   WCHAR defaultName[MAX_PATH] = L"default.pdf";

   OPENFILENAME openFileName = {};
   openFileName.lStructSize   = sizeof(openFileName);
   openFileName.hwndOwner     = nullptr;
   openFileName.hInstance     = nullptr;
   openFileName.lpstrFile     = defaultName;
   openFileName.lpstrFilter   = L"PDF File\0*.pdf\0";
   openFileName.nMaxFile      = MAX_PATH;
   openFileName.Flags         = OFN_OVERWRITEPROMPT;

   if (::GetSaveFileName(&openFileName))
   {
      wil::com_ptr<ICoreWebView2PrintSettings> printSettings = nullptr;
      wil::com_ptr<ICoreWebView2Environment6> webviewEnvironment6;
      CHECK_FAILURE(m_pImpl->m_webViewEnvironment->QueryInterface(IID_PPV_ARGS(&webviewEnvironment6)));

      if (webviewEnvironment6)
      {
         CHECK_FAILURE(webviewEnvironment6->CreatePrintSettings(&printSettings));
         CHECK_FAILURE(printSettings->put_Orientation(landscape ? COREWEBVIEW2_PRINT_ORIENTATION_LANDSCAPE : 
                                                                  COREWEBVIEW2_PRINT_ORIENTATION_PORTRAIT));
      }

      wil::com_ptr<ICoreWebView2_7> webview2_7;
      CHECK_FAILURE(m_pImpl->m_webView->QueryInterface(IID_PPV_ARGS(&webview2_7)));
      if (webview2_7)
      {
         m_printToPdfInProgress = true;

         CHECK_FAILURE(webview2_7->PrintToPdf(
            openFileName.lpstrFile, 
            printSettings.get(),
            Callback<ICoreWebView2PrintToPdfCompletedHandler>(
               [this, callback, &openFileName](HRESULT errorCode, BOOL isSuccessful) -> HRESULT {
                  CHECK_FAILURE(errorCode);

                  m_printToPdfInProgress = false;

                  callback(isSuccessful, openFileName.lpstrFile);

                  return S_OK;
               })
            .Get()));
      }
   }
}

There are several things happening here:

  • First, a path for the destination PDF file is being requested from the user.
  • A printer settings object is created and the selected orientation is being set. The portrait orientation is the default, so if no other options are set, creating a print settings object is not really necessary. You can just pass null.
  • A pointer to the ICoreWebView2_7 interface is retrieved so that we can invoke the PrintToPdf() method.
  • Upon completion of the print job, the provided callback is invoked.
  • A Boolean flag, m_printToPdfInProgress is used to indicate that a printing operation is in progress. This is then used elsewhere in the application to ensure a second operation is not started until the current one completes. This is because a single print operation can execute at any given point. If any of the available print methods (listed in the beginning) are invoked while a print job is in process, the completion handler is invoked immediately with the isSuccessful argument set to FALSE.

The following snippet shows the code in the main frame that invokes the PDF printing. Here you can notice the check of the print-in-progress flag:

void CMainFrame::OnButtonPrintToPdf()
{
   auto view = dynamic_cast<CMfcEdgeDemoView*>(GetActiveView());
   if (view != nullptr)
   {
      if (view->IsPrintToPdfInProgress())
      {
         ::MessageBox(
            nullptr,
            L"A PDF file is currently printing. Please wait until the operation completes.",
            L"Warning",
            MB_OK|MB_ICONEXCLAMATION);

         return;
      }

      CButton* check = (CButton*)m_wndDlgBar.GetDlgItem(IDC_CHECK_LANDSCAPE);

      view->PrintToPDF(check->GetCheck() == BST_CHECKED, [](bool const isSuccessful, CString path) {
         if (isSuccessful)
         {
            auto result = ::MessageBox(
               nullptr,
               L"Print to PDF succeeded. Would you like to open the document?",
               L"Print to PDF completed",
               MB_YESNO);
            
            if (result == IDYES)
            {
               ShellExecute(0, 0, path, 0, 0, SW_SHOW);
            }
         }
         else
         {
            ::MessageBox(
               nullptr,
               L"Print to PDF failed",
               L"Print to PDF completed",
               MB_OK);
         }
      });
   }
}

In the next images, you can see the PDFs created from the web page, with portrait and landscape orientation:

Downloads

You can download the demo application and try it here: MfcEdgeDemo.zip (20637 downloads ) .

See also

To learn more about the printing capabilities of the WebView2 control, you can read the following articles:

1 Reply to “Using Microsoft Edge in a native Windows desktop app – part 6”

  1. You don’t need to hang on to multiple versions of an interface, they’re all backwards compatible.
    The ICoreWebView2_17 contains (inherits) all the older interfaces, so it has all the functionality of all the other ICoreWebView2_
    So your code only needs to have a member of the last one you need… And when you create the ICoreWebView2 (or receive it as a function parameter), you just need to QueryInteface the latest one you need and use that.

    At least… assuming you can require consumers of your code to have at least a certain version of the runtime installed which is likely “always”…
    If your code can’t make this assumption, you will have to deal with the various versions and the fact that some objects may not have a certain version (and up) of an interface.

Leave a Reply

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