How to wait for IWebBrowser2::ExecWB() to finish when printing from a WebBrowser control? - c++

I'm using the WebBrowser control in my CDialog-based MFC window. The window allows printing, using code similar to this:
CComPtr<IWebBrowser2> pWebBrowser = this->GetIWebBrowser2();
if(pWebBrowser)
{
HRESULT hr;
COleVariant varNull;
if(SUCCEEDED(hr = pWebBrowser->ExecWB(
bDoPreview ? OLECMDID_PRINTPREVIEW : OLECMDID_PRINT,
OLECMDEXECOPT_PROMPTUSER, varNull, varNull)))
{
//All good
bRes = TRUE;
}
}
IWebBrowser2* GetIWebBrowser2()
{
IWebBrowser2* pBrowser = NULL;
LPUNKNOWN unknown = m_browser.GetControlUnknown();
if(unknown)
{
unknown->QueryInterface(IID_IWebBrowser2,(void **)&pBrowser);
if(unknown)
{
unknown->Release();
}
}
return pBrowser;
}
It works, except that if the document is large enough, pWebBrowser->ExecWB() seems to return right away and all the printing is done asynchronously. So in that case if the user closes my window (that houses this WebBrowser control) printing is aborted midway.
Thus my question, how do I wait for the printing to finish before I can allow closing of the host window?

Related

WIN32 - Create a WebView2 synchronously does not work - I don't get a white rectangle for my integrated web browser

In brief, I explain you my problem. In my application, I've a button to load the browser. When I click on the button, it opens and creates well the webBrowser (white rectangle).
BUT when I try to create the web Browser when I open a window (who contains different components : buttons, edittext,...) of my application through the Event WM_SHOWWINDOW, I don't see my web browser EXCEPTED IF I put in comment all the loop "while" in the constructor EdgeBrowser.
Why ? Can you give me a solution please ? It would be friendly.
I created a class EdgeBrowser with a constructor who receives the handle of my WIN32 component (HWND component). In my constructor, I do something like that :
EdgeBrowser::EdgeBrowser(HWND hwnd)
{
_hwnd = hwnd;
EdgeBrowser::_beginAsyncOperation = true;
this->CreateWebView();
while (EdgeBrowser::_beginAsyncOperation)
{
Sleep(10);
MSG msg;
for (int nmsg = 0; nmsg < 50; ++nmsg)
{
int rc = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
if (rc == 0)
{
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
And in a part of my "CreateWebView" function with the different listeners :
void EdgeBrowser::CreateWebView
{
// other code
HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(nullptr, userDataDir.c_str(),nullptr,Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(this,&EdgeBrowser::OnCreateCoreWebView2EnvironmentCompletedHandler).Get());
}
HRESULT EdgeBrowser::OnEnvironmentReadyCompletedHandler(HRESULT result, ICoreWebView2Environment* env)
{
HRESULT createBrowserControlsResult = m_uiEnv->CreateCoreWebView2Controller(this->_thisHandler, Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(this,&EdgeBrowser::OnCreateCoreWebView2ControllerCompletedHandler).Get());
HRESULT createBrowserOptionsResult = m_uiEnv->CreateCoreWebView2Controller(this->_thisHandler,Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( this,&EdgeBrowser::OnCreateCoreWebView2ControllerOptionsCompletedHandler).Get());
}
HRESULT EdgeBrowser::OnCreateCoreWebView2ControllerOptionsCompletedHandler(HRESULT result, ICoreWebView2Controller* host)
{
m_optionsController = host;
HRESULT getOptWebViewResult = m_optionsController->get_CoreWebView2(&m_optionsWebView);
//other code
EdgeBrowser::_beginAsyncOperation = false;
return S_OK;
}
The WebView2 control requires a message loop to run on the UI thread on which it is created (see this page for more info on WebView2 threading). You can see the WebView2APISample sample app as a sample C++ Win32 HWND based application.

Detecting CTRL+Wheel with CHtmlView

I know how to set the zoom factor for a CHtmlView:
HRESULT CChristianLifeMinistryHtmlView::SetZoomFactor(long iZoom, bool bRefreshBrowser /*true*/)
{
HRESULT hr = S_OK;
VARIANT vZoom;
m_lZoomFactor = iZoom;
if (bRefreshBrowser)
{
vZoom.vt = VT_I4;
vZoom.lVal = iZoom;
hr = ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, &vZoom, nullptr);
}
return hr;
}
HRESULT CChristianLifeMinistryHtmlView::ExecWB(OLECMDID cmdID, OLECMDEXECOPT cmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
{
HRESULT hr;
ASSERT(m_pBrowserApp != NULL);
hr = m_pBrowserApp->ExecWB(cmdID, cmdexecopt, pvaIn, pvaOut);
return hr;
}
I have just introduced a CStatusBar into the main editor that encompasses this view and one of my users has stated that they use CTRL + Wheel to change the zoom factor.
I have my menu structure with associated hotkeys that the user can use to change the zoom, thus my status bar pane is updated to the right value they selected.
But when they use the CTRL + Wheel to change the zoom my application is not detecting this. So they zoom in or out to a scale and my status bar pane is staying at the original zoom factor.
With MFC and the CHtmlView web browser control how to I detect when they have changed the zoom using CTRL + Wheel so I can update my status pane?
If this is CHtmlView with Doc/View structure, use PreTranslateMessage to catch messages.
Documentation for WM_MOUSEWHEEL suggests several macros for finding the state of virtual keys and wheel movement:
BOOL CMyHtmlView::PreTranslateMessage(MSG* pmsg)
{
if(pmsg->message == WM_MOUSEWHEEL)
{
int fwKeys = GET_KEYSTATE_WPARAM(pmsg->wParam);
int zDelta = GET_WHEEL_DELTA_WPARAM(pmsg->wParam);
if (fwKeys & MK_CONTROL)
{
//mousewheel + control key is down
TRACE("%d %d\n", zDelta, zDelta / WHEEL_DELTA);
//update statusbar, or return TRUE to handle this manually
}
}
return CHtmlView::PreTranslateMessage(pmsg);
}
CHtmlView also has its own CHtmlView::ExecWB method to set and get the zoom value etc.
CHtmlView::OnUpdateUI should also send notification for the change.
But the browser may not send a signal at the right time. Just make a timer to wait 1 second after detecting CTRL+WHEEL. Example:
BEGIN_MESSAGE_MAP(CMyHtmlView, CHtmlView)
ON_WM_TIMER()
END_MESSAGE_MAP()
const int ID_TIMER_ZOOM = 1;
BOOL CMyHtmlView::PreTranslateMessage(MSG* pmsg)
{
if(pmsg->message == WM_MOUSEWHEEL)
if (GET_KEYSTATE_WPARAM(pmsg->wParam) & MK_CONTROL)
SetTimer(ID_TIMER_ZOOM, 1000, NULL); //start timer for detecting zoom
return CHtmlView::PreTranslateMessage(pmsg);
}
void CMyHtmlView::OnTimer(UINT_PTR timer_id)
{
if(timer_id == ID_TIMER_ZOOM)
{
//get the zoom value
VARIANT vZoom;
vZoom.vt = VT_I4;
vZoom.lVal = 0;
ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, nullptr, &vZoom);
TRACE("zoom %d\n", vZoom.lVal);
//kill the timer
KillTimer(timer_id);
}
}

Receiving parent's window handle on IShellFolder/IShellFolder2 EnumObjects implementations while searching?

I'm scratching my head in receiving the parent window handle in a namespace extension project i'm working on.
The use case is as follows:
User browses to a virtual folder through windows explorer
User performs search (search box above)
I need to retrieve the search box's text before the search starts.
I've managed to do that on a test console app with ISearchBoxInfo interface (https://msdn.microsoft.com/en-us/library/windows/desktop/dd562062%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396)
There are 2 ways i can receive a pointer to this interface:
Using IObjectWithSite::SetSite call - which is not relevant as the search is conducted in a different thread, and i cannot share the COM object between those threads
Identifying the window handle and retrieve the ISearchBox through IWebBrowser2 interface.
Both methods don't work as when i perform the search, the EnumObjects is called via a different thread, and i cannot find a way to identify who is te parent explorer window.
When doing search, the hwnd that comes is always null as follows:
This is the EnumObjects code:
// Allows a client to determine the contents of a folder by
// creating an item identifier enumeration object and returning
// its IEnumIDList interface. The methods supported by that
// interface can then be used to enumerate the folder's contents.
HRESULT CFolderViewImplFolder::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList)
{
HRESULT hr;
_fd = hwnd;
if (hwnd != NULL) // NULL when performing a search
{
const int n = GetWindowTextLength(hwnd);
wstring text(n + 1, L'#');
if (n > 0)
{
GetWindowText(hwnd, &text[0], text.length());
}
}
if (m_nLevel >= g_nMaxLevel)
{
*ppenumIDList = NULL;
hr = S_FALSE; // S_FALSE is allowed with NULL out param to indicate no contents.
}
else
{
CFolderViewImplEnumIDList *penum = new (std::nothrow) CFolderViewImplEnumIDList(grfFlags, m_nLevel + 1, this);
hr = penum ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = penum->Initialize();
if (SUCCEEDED(hr))
{
hr = penum->QueryInterface(IID_PPV_ARGS(ppenumIDList));
}
penum->Release();
}
}
return hr;
}
In addition to my tests, as i have also implementation of IShellFolderViewCB.MessageSFVCB, which runs on the correct thread where i can retrieve the IShellBrowser and thus the handle - when i conduct the search i encounter the following messages:
103, 103, 67, UnmergeMenu, WindowClosing, 106, ViewRelease, ViewRelease
Afterward, no more messages are posted (no matter if i re-search) - the first breakpoint is always at EnumObjects method which i have no context about the parent window.
Any light shedding would be nice.
== EDIT ==
I've came up with some workaround - this is not perfect for all cases but works for most of them - other options will still be nice.
Whenever EnumObjects is called with hwnd = NULL, i'm doing the following: (it's in C# - but it can be easily in C++ also)
static public string PrepareSearch(string currentFolderName, IntPtr hwnd)
{
SHDocVw.ShellWindows shellWindows = new ShellWindows();
SHDocVw.IWebBrowser2 foundBrowser = null;
bool wasFound = false;
string foundTxt = null;
foreach (SHDocVw.IWebBrowser2 eie in shellWindows)
{
// as the search is conducted in another thread, while the main window is "free" and in a search mode, it should be first busy.
string locName = eie.LocationName;
string exeName = eie.FullName;
if (!string.IsNullOrEmpty(exeName) && exeName.IndexOf("explorer.exe", StringComparison.OrdinalIgnoreCase) >= 0 &&
!string.IsNullOrEmpty(locName) &&
eie.Busy && eie.ReadyState == tagREADYSTATE.READYSTATE_LOADING)
{
// in here we're ok, we would also want to get the window title to make sure we're searching correctly.
string title = NSEFolder.WindowText((IntPtr)eie.HWND);
if (!string.IsNullOrEmpty(title) &&
title.IndexOf(currentFolderName) >= 0)
{
// one or more windows were found, ignore the quick search.
if (wasFound)
{
return null;
}
wasFound = true;
foundTxt = locName;
}
}
}
if (wasFound && !string.IsNullOrEmpty(foundTxt))
{
return foundTxt;
}
return null;
}
Basically i'm going over all explorer windows, trying to find one that is indeed "explorer.exe" + not empty search string (LocationName) + busy... + title contains name of the current folder name.
it will fail when 2 windows are busy and have the same folder name in the title - but this might be good enough... Not sure here.
So, after a talk with microsoft - it's not possible to do so.
the workaround i've added is a valid one (thoguh not perfect).

How to get HWND of an embedded web browser control in MFC

I'm using the embedded web browser control in my dialog-based MFC window and I need to know the HWND of the web browser control in it. I was able to find the following code that claims to retrieve it:
HWND hWndWebBrowser = NULL;
LPUNKNOWN unknown = m_browser.GetControlUnknown();
IWebBrowser2* pWB = NULL;
if(SUCCEEDED(unknown->QueryInterface(IID_IWebBrowser2,(void **)&pWB)))
{
CComPtr<IServiceProvider> pServiceProvider;
if (SUCCEEDED(pWB->QueryInterface(IID_IServiceProvider, (void**)&pServiceProvider)))
{
CComPtr<IOleWindow> pWindow;
if (SUCCEEDED(pServiceProvider->QueryService(SID_SShellBrowser, IID_IOleWindow, (void**)&pWindow)))
{
SHANDLE_PTR hBrowser = 0;
if (SUCCEEDED(pWindow->GetWindow(&hBrowser)))
{
hWndWebBrowser = (HWND)hBrowser;
}
}
}
}
if(unknown)
{
unknown->Release();
}
but the problem is that when it runs, it returns a handle, but not the one I would expect. The best way to illustrate it is with this Spy++ screenshot:
I understand that I can use EnumChildWindows and look for a window with the Internet Explorer_Server class, but I'm somewhat concerned about using this undocumented class name.
Does anyone have a better way to retrieve that (web browser) window handle?
Per Obtaining the HWND for the WebBrowser control, you can use following function to retrieve HWND.
IOleWindow *pOWin;
HWND hBWnd;
HRESULT hRes = m_pBrowserApp->QueryInterface(IID_IOleWindow, (void **)&pOWin);
if (SUCCEEDED(hRes)) {
hRes = pOWin->GetWindow(&hBWnd);
if (SUCCEEDED(hRes)) {
// Place hBWnd-manipulating code here
}
pOWin->Release(); // Missing from the MS example
}
Because the class names (Shell DocObject View and Internet Explorer_Server) could change, the above code should be preferred, although it is unlikely given the fact that Internet Explorer is now discontinued.
The lexical of the question is a little tricky.
The HWND of the (Web Browser) is indeed the answer
that you posted and the answer posted by Santosh Dhanawade.
When a document is loaded, the web browser control creates a
new window or iframe, see the DWebBrowserEvents2::DocumentComplete event.
Event handler parameters:
" pDisp [in] "
A pointer to the IDispatch interface of the window or frame in which the document is loaded. This IDispatch interface can be queried for the IWebBrowser2 interface.
so, changing the question:
"Does anyone have a better way to retrieve that (web browser) window handle?"
to:
"Does anyone have a better way to retrieve that (window or iframe) window handle?"
we have that the window or iframe HWND that you are locking for,
will be abailable after that the document has been completed loaded.
Which means that we can do the follow:
Implement a DocumentComplete event handler throw a raw c or c++ implementation of
IDispatch or an ATL DispEventImpl or ATL DispEventSimpleImpl.
See Understanding COM Event Handling.
Sink our event handler into the the web browser control to get the events report.
And get the window or iframe HWND from the DocumentComplete event:
assuming a raw c++ IDispatch implementation:
IFACEMETHODIMP DWebBrowserEvents2Impl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr)
{
if (dispIdMember == DISPID_DOCUMENTCOMPLETE) {
VARIANT variantDispatch;
VariantInit(&variantDispatch);
HRESULT hr = DispGetParam(pDispParams, 0, VT_DISPATCH, &variantDispatch, NULL);
if (SUCCEEDED(hr)) {
IOleWindow* iOleWindow;
hr = variantDispatch.pdispVal->QueryInterface(IID_IOleWindow, (LPVOID*) &iOleWindow);
if (SUCCEEDED(hr)) {
HWND hwnd;
hr = iOleWindow->GetWindow(&hwnd);
iOleWindow->Release();
if (SUCCEEED(hr)){
//now the hwnd correponds to the Internet Explorer_Server window.
//Do what ever you want with the HWND handler.
}
}
}
return S_OK;
}
return E_NOTIMPL;
}
From my experience, the window we're looking for is a direct descendance of the CHtmlView derived CWnd, so I use this hack to get the window and set the focus to it:
static CWnd* findChildWebbrowser(CWnd* pWnd) {
if(pWnd == NULL) { return NULL; }
CWnd* pC = pWnd->GetWindow(GW_CHILD);
if(pC == NULL) { return NULL; };
CString buf;
::GetClassName(pC->GetSafeHwnd(), buf.GetBuffer(2048), 2047);
buf.ReleaseBuffer();
if(buf == _T("Internet Explorer_Server")) {
return pC;
}
return findChildWebbrowser(pC);
}
void CMyWebView::OnSetFocus(CWnd* pOldWnd) {
// CHtmlView::OnSetFocus(pOldWnd);
CWnd* pIE = findChildWebbrowser(this);
if(pIE!=NULL) {
// this makes cursor/page keys work
pIE->SetFocus();
// this makes the TAB key work
pIE->SendMessage(WM_LBUTTONDOWN);
pIE->SendMessage(WM_LBUTTONUP);
}
}

How to implement code for multiple buttons using c++ in Silverlight for Windows Embedded

I have referred the following link:
Silverlight for Windows Embedded
By referring this link i created a demo application which consist of two buttons created using Microsoft expression blend 2 tools. And then written a code referring the above site. Now my button names are "Browser Button" and "Media Button". On click of any one of the button i should able to launch the respective application. I was able to do for "Browser Button" but not for "Media Button" and if i do for "Media Button" then i am not able to do for "Browser Button".. I mean to say that how should i create event handler for both the buttons.
This is the code in c++ which i should modify
class BtnEventHandler
{
public:
HRESULT OnClick(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
{
RETAILMSG(1,(L"Browser event"));
Execute(L"\\Windows\\iesample.exe",L"");
return S_OK;
}
};
// entry point for the application.
INT WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,int nCmdShow)
{
PrintMessage();
int exitCode = -1;
HRESULT hr = S_OK;
if (!XamlRuntimeInitialize())
return -1;
HRESULT retcode;
IXRApplicationPtr app;
if (FAILED(retcode=GetXRApplicationInstance(&app)))
return -1;
if (FAILED(retcode=app->AddResourceModule(hInstance)))
return -1;
XRWindowCreateParams wp;
ZeroMemory(&wp, sizeof(XRWindowCreateParams));
wp.Style = WS_OVERLAPPED;
wp.pTitle = L"Bounce Test";
wp.Left = 0;
wp.Top = 0;
XRXamlSource xamlsrc;
xamlsrc.SetResource(hInstance,TEXT("XAML"),MAKEINTRESOURCE(IDR_XAML1));
IXRVisualHostPtr vhost;
if (FAILED(retcode=app->CreateHostFromXaml(&xamlsrc, &wp, &vhost)))
return -1;
IXRFrameworkElementPtr root;
if (FAILED(retcode=vhost->GetRootElement(&root)))
return -1;
IXRButtonBasePtr btn;
if (FAILED(retcode=root->FindName(TEXT("BrowserButton"), &btn)))
return -1;
IXRDelegate<XRMouseButtonEventArgs>* clickdelegate;
BtnEventHandler handler;
if(FAILED(retcode=CreateDelegate
(&handler,&BtnEventHandler::OnClick,&clickdelegate)))
return -1;
if (FAILED(retcode=btn->AddClickEventHandler(clickdelegate)))
return -1;
UINT exitcode;
if (FAILED(retcode=vhost->StartDialog(&exitcode)))
return -1;
return exitCode;
}
I have to add event handler for both the button so that on emulator whenever i click on any one of the button i should be able to launch the respective applications.
Thanks in advance
You can create two seperate functions to be the handlers for each button.
If you want the same handler to identify which button was pressed and act accordingly you can read the following MSDN article that demonstrates that.
I have not tried this, but you can also use IXRDependencyObject::GetName of the source object to know which button was pressed.
Your handler would look like:
HRESULT OnClick(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
{
BSTR pName[50];
source->GetName(pName);
if (_tcscmp(L"BrowserEvent", LPCWSTR(pName)) == 0)
{
RETAILMSG(1,(L"Browser event"));
Execute(L"\\Windows\\iesample.exe",L"");
}
else if (_tcscmp(L"BrowserEvent", LPCWSTR(pName)) == 0)
{
/* Handle another button or element */
}
return S_OK;
}