Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can we have hidden WebView2 and still invoke Printing? #3266

Closed
ajtruckle opened this issue Mar 6, 2023 · 19 comments
Closed

Can we have hidden WebView2 and still invoke Printing? #3266

ajtruckle opened this issue Mar 6, 2023 · 19 comments
Assignees

Comments

@ajtruckle
Copy link

ajtruckle commented Mar 6, 2023

Environment: MFC / Win32 / CDialog

My current program uses CHtmlView. I have a CHtmlView attached to my dialog. On this control I display an "editor" version of a schedule. I can replicate this behaviour using a WebView2 control.

When the user wants to print, it currently does this:

  1. Create hidden CHtmlView
  2. Load a "printable" version of the schedule.
  3. Display print preview full screen.
  4. Destroy the hidden CHtmlView if they cancel printing.
  5. Destroy the hidden CHtmlView when they shut my program down (if it exists).

So:

  • How can I create this second hidden WebView2 control?
  • How can I invoke a full screen print preview despite it being hidden?
  • How can I destroy it if printing is cancelled?

Thank you for your guidance for this as I do not know how to do any of this. I only know how to create the initial WebView2 attached to my CDialog which has the editors' version of the schedule.

Thank you in advance for taking me through the essential steps of what is involved.

@ajtruckle
Copy link
Author

ajtruckle commented Mar 6, 2023

I originally asked this question here: #1485. And, in that question I was redirected to: #1331.

But, whilst a Print API has now been developed, my questions remain:

  • How to invoke a hidden WebView2 control not attached to my dialog?
  • How to invoke a full screen print preview from this hidden WebView2 control?

I really would like to move on with implementing WebView2 in my application and these queries were originally raised in July 2021. I appreciate there is much to do!

Any sample code of the main concepts appreciated. Thanks.

@ajtruckle
Copy link
Author

ajtruckle commented Mar 6, 2023

I am open to suggestions to make this easier. For example:

  • Show a visible maximized webview2 on the screen (model style)
  • Automatically load the printable schedule and invoke printing with preview
  • Terminate browser control once printing has been issued or cancelled.

What ever is easiest to implement that still looks nice from a users perspective.

Thanks for your help!

@novac42 novac42 assigned vickiez and monica-ch and unassigned vickiez Mar 6, 2023
@novac42
Copy link
Contributor

novac42 commented Mar 6, 2023

Thanks for reaching out. I've assigned this to a dev that can help follow up on this.

@ajtruckle
Copy link
Author

@novac42 @monica-ch

Thank you! At first I tried to create a second invisible browser control:

void CChristianLifeMinistryEditorDlg::InitialiseWebPreview2()
{
	m_pWebBrowser = std::make_unique<CWebBrowser>();
	if (m_pWebBrowser != nullptr)
	{
		m_lblHtmlPreview.GetWindowRect(m_rctHtmlPreview);
		ScreenToClient(m_rctHtmlPreview);

		m_pWebBrowser->CreateAsync(
			WS_VISIBLE | WS_CHILD,
			m_rctHtmlPreview,
			this,
			1,
			[this]() {
				m_pWebBrowser->SetParent(this);
				m_pWebBrowser->DisablePopups();
				m_pWebBrowser->AddContextMenuRequestedHandler();

				UpdatePreview();

			});
	}

	/*
	m_pWebBrowserPrint = std::make_unique<CWebBrowser>();
	if (m_pWebBrowserPrint != nullptr)
	{
		m_lblHtmlPreview.GetWindowRect(m_rctHtmlPreview);
		ScreenToClient(m_rctHtmlPreview);

		m_pWebBrowserPrint->CreateAsync(
			WS_VISIBLE | WS_CHILD,
			m_rctHtmlPreview,
			this,
			1,
			[this]() {
				m_pWebBrowserPrint->SetParent(this);
				m_pWebBrowserPrint->DisablePopups();
			});
	}
	*/
	
}

As you can see, I have commented the code out. It did not work. Because when I tried to do my own printing, the first thing I did was to navigate to my printer version of the schedule and try to print. No joy.

@ajtruckle
Copy link
Author

From my research it seems that it is not possible to use a WebView2 control in a hidden state because it is not fully initialised until it is displayed.

Can we do this then?

  • Display a temporary pop up window using the existing visible browser control on my dialog.
  • Maximize this pop up.
  • Invoke print preview for pop up
  • If cancelled or printing completes destroy the pop up.

@monica-ch
Copy link
Contributor

@ajtruckle Yes you can open the printable version of the schedule in a webview2 popup, invoke the print dialog and use JS afterprint that fires when printing is started (~when user click Print button) or cancelled and close the popup.

@ajtruckle
Copy link
Author

@monica-ch Thanks for confirming. Can you please guide me on the steps involved for Win32 API?

@ajtruckle
Copy link
Author

@monica-ch I came up with a partially successful solution:

if (m_pWebBrowser != nullptr)
{
	m_pWebBrowser->Navigate(strPreviewXML,
		[this, bTriggeerPrintEvent]() {
			if (bTriggeerPrintEvent)
				m_pWebBrowser->ShowPrintUI(COREWEBVIEW2_PRINT_DIALOG_KIND_BROWSER);
		});
}

This code re-uses the same browser control. But I don't know how to use the JS afterprint you referred to because I will need to re-call UpdatePreview() after it has printed / saved as PDf or been cancelled so that the editor returns to the "Editor" version of the schedule. If we can get this working it would be a temporary way forward. But ideally I would like a full screen popup window instead so we can take advantage of the preview area.

@monica-ch
Copy link
Contributor

monica-ch commented Mar 8, 2023

We have ExecuteScript API that runs JS code. In the afterprint event callback that invokes when is print is completed/cancelled, post a web message to the native code. Listen to add_WebMessageReceived event that triggers when a page posts a message and do UpdatePreview() or something.

window.addEventListener('afterprint', (event) => {
 window.chrome.webview.postMessage('PrintDone');
});
m_webView->add_WebMessageReceived(
    Microsoft::WRL::Callback<ICoreWebView2WebMessageReceivedEventHandler>(
     [this](ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args)
     {
         .....
         return S_OK;
     })
    .Get(), nullptr);

@ajtruckle
Copy link
Author

Hi @monica-ch Thanks.

I have changed the logic to avoid the need to do an update afterwards. So that simplifies it.

So, step 1, how do I used ExecuteScriot to create the pop up window, maximised to the display, and invoke the print up? What’s the code for that? Thanks.

When the print finishes / cancels what I need it to do is close this pop up window we created

I appreciate the example provided but I need a little more help to understand the correct code. Thank you.

@monica-ch
Copy link
Contributor

You might have to do something like below.

  1. Open a new window pop up using ExecuteScript API and JS window.open(url).
  2. Handle the new window using add_NewWindowRequested API, refer add_NewWindowRequested here.
  3. Once new window webview is set, register add_WebMessageReceived, add_NavigationCompleted events.
  4. In the navigation completed callback, register afterprint and call ShowPrintUI.
m_webView->ExecuteScript(L"window.open(url)",
                        Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
                            [](HRESULT error, PCWSTR result) -> HRESULT { return S_OK; })
                            .Get());
    //! [NewWindowRequested]
    CHECK_FAILURE(m_webView->add_NewWindowRequested(
        Callback<ICoreWebView2NewWindowRequestedEventHandler>(
            [this](ICoreWebView2* sender, ICoreWebView2NewWindowRequestedEventArgs* args)
            {
                if (!m_shouldHandleNewWindowRequest)
                {
                    args->put_Handled(FALSE);
                    return S_OK;
                }
                wil::com_ptr<ICoreWebView2Deferral> deferral;
                CHECK_FAILURE(args->GetDeferral(&deferral));
                AppWindow* newAppWindow;

                wil::com_ptr<ICoreWebView2WindowFeatures> windowFeatures;
                CHECK_FAILURE(args->get_WindowFeatures(&windowFeatures));

                RECT windowRect = {0};
                UINT32 left = 0;
                UINT32 top = 0;
                UINT32 height = 0;
                UINT32 width = 0;
                BOOL shouldHaveToolbar = true;

                BOOL hasPosition = FALSE;
                BOOL hasSize = FALSE;
                CHECK_FAILURE(windowFeatures->get_HasPosition(&hasPosition));
                CHECK_FAILURE(windowFeatures->get_HasSize(&hasSize));

                bool useDefaultWindow = true;

                if (!!hasPosition && !!hasSize)
                {
                    CHECK_FAILURE(windowFeatures->get_Left(&left));
                    CHECK_FAILURE(windowFeatures->get_Top(&top));
                    CHECK_FAILURE(windowFeatures->get_Height(&height));
                    CHECK_FAILURE(windowFeatures->get_Width(&width));
                    useDefaultWindow = false;
                }
                CHECK_FAILURE(windowFeatures->get_ShouldDisplayToolbar(&shouldHaveToolbar));

                windowRect.left = left;
                windowRect.right =
                    left + (width < s_minNewWindowSize ? s_minNewWindowSize : width);
                windowRect.top = top;
                windowRect.bottom =
                    top + (height < s_minNewWindowSize ? s_minNewWindowSize : height);

                // passing "none" as uri as its a noinitialnavigation
                if (!useDefaultWindow)
                {
                    newAppWindow = new AppWindow(
                        m_creationModeId, GetWebViewOption(), L"none", m_userDataFolder, false,
                        nullptr, true, windowRect, !!shouldHaveToolbar);
                }
                else
                {
                    newAppWindow = new AppWindow(m_creationModeId, GetWebViewOption(), L"none");
                }
                newAppWindow->m_isPopupWindow = true;
                newAppWindow->m_onWebViewFirstInitialized = [args, deferral, newAppWindow, this]()
                {
                    CHECK_FAILURE(args->put_NewWindow(newAppWindow->m_webView.get()));

                        CHECK_FAILURE(newAppWindow->m_webView->add_WebMessageReceived(
                        Microsoft::WRL::Callback<ICoreWebView2WebMessageReceivedEventHandler>(
                            [this](
                                ICoreWebView2* sender,
                                ICoreWebView2WebMessageReceivedEventArgs* args)
                            {
                                wil::unique_cotaskmem_string messageRaw;
                                CHECK_FAILURE(args->TryGetWebMessageAsString(&messageRaw));

                                if(messageRaw.get() == L"PrintDone")
                               {
                                /// UpdatePreview...
                               }
                                return S_OK;
                            })
                            .Get(),
                        nullptr));
                          
                            /// open the print dialog once navigation is completed
                            CHECK_FAILURE(newAppWindow->m_webView->add_NavigationCompleted(
                            Callback<ICoreWebView2NavigationCompletedEventHandler>(
                                [this, newAppWindow](
                                    ICoreWebView2* sender,
                                    ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
                                {
                                   /// Register `afterprint` event in the new window      
                              newAppWindow->m_webView->ExecuteScript(
                        LR"~(
                    window.addEventListener('afterprint', (event) => {
                      window.chrome.webview.postMessage('PrintDone');
                     });
                    )~",
                        Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
                            [](HRESULT error, PCWSTR result) -> HRESULT { return S_OK; })
                            .Get());

                                    /// Show print ui
                                    auto webView2_16 =
                                        newAppWindow->m_webView.try_query<ICoreWebView2_16>();
                                    CHECK_FEATURE_RETURN(webView2_16);
                                    CHECK_FAILURE(webView2_16->ShowPrintUI(
                                        COREWEBVIEW2_PRINT_DIALOG_KIND_BROWSER));

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

                    CHECK_FAILURE(args->put_Handled(TRUE));
                    CHECK_FAILURE(deferral->Complete());
                };
                return S_OK;
            })
            .Get(),
        nullptr));
    //! [NewWindowRequested]

@ajtruckle
Copy link
Author

ajtruckle commented Mar 9, 2023

Hi @monica-ch ! Thanks for the code / explanations.

Beginning with the first step, I tried:

void CWebBrowser::ShowPrintUIFullScreen()
{
	if (m_pImpl->m_webView)
	{
		m_pImpl->m_webView->ExecuteScript(L"window.open(url)",
			Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
				[](HRESULT error, PCWSTR result) -> HRESULT { return S_OK; })
			.Get());
	}
}

I call this function on demand for now, once I have something on the display. But, running this code alone appears to do nothing. 🤔 I get no errors and no popup on screen. Why? I thought that would happen at this stage.


In-fact, if I use the sample WebView2 app and use Inject Script on the Script menu:

  • window.print() works (also works in my code).
  • window.open(url) does not work. Just shows a popup message saying "Null".

@ajtruckle
Copy link
Author

Hi @monica-ch
Is it because I am literally using url?

@monica-ch
Copy link
Contributor

@ajtruckle Yes, please specify a valid URL.

@ajtruckle
Copy link
Author

@monica-ch Slowly getting there:

void CWebBrowser::ShowPrintUIFullScreen(const CString strUrl)
{
	if (m_pImpl->m_webView)
	{
		m_pImpl->m_webView->ExecuteScript(L"window.open('" + strUrl + L"')",
			Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
				[](HRESULT error, PCWSTR result) -> HRESULT { return S_OK; })
			.Get());
	}
}

If I use a literal url like 'https://www.microsoft.com' it displays in a popup. But when I try to use my my strUrl parameter (which is the literal file path on my PC to the same html file it will not find it. I tried NormalizeUrl(strUrl) and still no joy. We basically need it to display the same url that is displayed in the active view. How do we do this step? Thanks.

@ajtruckle
Copy link
Author

@monica-ch Got the first step working! Now on to next. 😊

@ajtruckle
Copy link
Author

@monica-ch So I have managed to do the first bit like this:

void CWebBrowser::ShowPrintUIFullScreen(const CString strUrl)
{
	if (m_pImpl->m_webView)
	{
		CRect rctSize;
		GetParentWindow()->GetClientRect(rctSize);
		rctSize.DeflateRect(10,10);
		CString strJS;

		strJS.Format(L"window.open('%s', 'customWindow', 'width=%d, height=%d, top=%d, left=%d')",
			GetLocationURL(), rctSize.Width(), rctSize.Height(), rctSize.top, rctSize.left);

		m_pImpl->m_webView->ExecuteScript(strJS,
			Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
				[](HRESULT error, PCWSTR result) -> HRESULT { return S_OK; })
			.Get());
	}
}
  • Displays popup window
  • Loads the same webpage.
  • Resizes to fit the application window

I could adjust it to take the work area instead to maximize to screen. But the window should be maximized anyway. It seemed just a little simpler.

My problem with the sample code is that it is using AppWindow. I don't use that class as it has a lot of baggage I don't need. So I have my CWebBrowser / CWebBrowserImpl() classes which are much simpler. I need to try and stitch the right code into CWebBrowser to:

  • Once new window webview is set, register add_WebMessageReceived, add_NavigationCompleted events.
  • In the navigation completed callback, register afterprint and call ShowPrintUI.

@ajtruckle
Copy link
Author

@monica-ch I have come up with a compromise. I added a new "Full Screen" button to my editor. So if they want to print and would like a bigger area to work with, they click the button to get the bigger view. Then, they right-click and invoke Print.

It sets us to where we want to be with a couple of mouse clicks and no extra code.

Personally, I would like to add for the team to consider extending the ShowPrintUI, when using the browser dialog, to have a second parameter - display on maximixed popup window.

This means anyone can use it if they need to and will be useful for CDialog users like me.

But I am happy with this compromise of showing a maximized popup to the dialog size. I may change to use screen size.

@ajtruckle
Copy link
Author

Hi @monica-ch Can I please ask the status of this ticket? Will any of my ideas be put on the feature list and tracked?

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants