At the current moment I am trying to test IAutoComplete COM interface, which requires me to make use of MSVC cpp compiler. I am faced with a problem I never faced in MingGW. MSVC sees my throw statement and completely ignores it. So I don't know if there is a setting I didn't set.
Alright I got a down vote which I probably deserve for giving a bad example. I thought that it failed to throw because it was in a struct but tries with a fresh struct proved me wrong. I might not be able to offer a reproducible example but I will try.
Prepare for alot of code.
Jav/AutoObject.h
#ifndef JAV_WIN32_AUTO_OBJECT_HPP
#define JAV_WIN32_AUTO_OBJECT_HPP
#include <Jav/config.h>
namespace Jav
{
struct AutoObject
{
virtual ~AutoObject(){}
void* operator new(size_t sz);
};
AutoObject* shallowCopy(AutoObject*);
AutoObject* deleteObject(AutoObject*);
}
#endif // JAV_WIN32_AUTO_OBJECT_HPP
Jav/win32/widgets/widgets_ex.h
#ifndef JAV_WIN32_GUI_WIDGETS_EX_HPP
#define JAV_WIN32_GUI_WIDGETS_EX_HPP
#include <Jav/win32/widgets/widgets.h>
namespace Jav { namespace win32 {
class Widget
{
public:
Widget(HWND widget=nullptr) : widget(widget){}
operator HWND() { return widget; }
public:
void setId(int id) { SetWindowLong(widget,GWLP_ID,id); }
void destroy() { DestroyWindow(widget); widget = NULL; }
void show() { ShowWindow(widget,SW_SHOW); }
void hide() { ShowWindow(widget,SW_HIDE); }
protected:
HWND widget;
};
}}
#endif // JAV_WIN32_GUI_WIDGETS_EX_HPP
Jav/win32/window/Window.h
#include <Jav/win32/widgets/widgets_ex.h>
using Window = Widget;
Jav/win32/window/SimpleWindow.h
#ifndef JAV_WIN32_WINDOW_SIMPLE_WINDOW_HPP
#define JAV_WIN32_WINDOW_SIMPLE_WINDOW_HPP
#include <Jav/AutoObject.h>
#include <Jav/win32/window/Window.h>
namespace Jav { namespace win32 {
struct Content__ : AutoObject
{
virtual void onCreate(HWND,CREATESTRUCTA*) {}
virtual void onDestroy() {}
virtual void onSize(int w,int h) {}
virtual void onDraw(Canvas&) {}
};
class SimpleWindow : public Window
{
public:
SimpleWindow(Content__*, const char *title, size_t w,size_t h,
int style=WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,
int ex_style=0);
public:
bool isOpen();
void close();
const MSG& getMessage();
const MSG& peekMessage();
void dispatchMessage(const MSG&);
private:
static ATOM registerWindow();
static LRESULT CALLBACK windowProc(HWND,UINT,WPARAM,LPARAM);
static HWND createWindow(Content__*, const char *,HMENU, size_t,size_t, int,int);
private:
MSG msg;
};
}}
#endif // JAV_WIN32_WINDOW_SIMPLE_WINDOW_HPP
AutoObject.cpp
#include <Jav/AutoObject.h>
#include <unordered_map>
#include <objbase.h>
namespace {
struct AutoObjectsMap : std::unordered_map<Jav::AutoObject*,size_t>
{
~AutoObjectsMap() { for(auto &obj : *this) CoTaskMemFree(obj.first); }
};
AutoObjectsMap *alloc_objects_ptr;
struct AutoObjectsMapMgr
{
~AutoObjectsMapMgr() { delete alloc_objects_ptr; };
};
}
#define alloc_objects (*alloc_objects_ptr)
namespace Jav
{
void* AutoObject::operator new(size_t sz)
{
auto mem = CoTaskMemAlloc(sz);
if(!alloc_objects_ptr) alloc_objects_ptr = new AutoObjectsMap();
alloc_objects[(AutoObject*)mem] = 1;
return mem;
}
AutoObject* shallowCopy(AutoObject *r)
{
auto elem = alloc_objects.find(r);
if( elem != alloc_objects.end() ) ++alloc_objects[r];
return r;
}
AutoObject* deleteObject(AutoObject *mem)
{
auto elem = alloc_objects.find(mem);
if( elem != alloc_objects.end() )
{
if(--elem->second == 0)
{
mem->~AutoObject();
CoTaskMemFree(mem);
alloc_objects.erase(elem);
return nullptr;
}
}
return mem;
}
}
SimpleWindow.cpp
#include <Jav/win32/window/SimpleWindow.h>
namespace Jav { namespace win32 {
namespace {
LRESULT CALLBACK SimpleWindow::windowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
Content__ *obj = (Content__*)GetWindowLongPtrA(hwnd,GWLP_USERDATA);
auto getWidth = [lParam](){ return LOWORD(lParam); };
auto getHeight = [lParam](){ return HIWORD(lParam); };
auto getX = [lParam](){ return LOWORD(lParam); };
auto getY = [lParam](){ return HIWORD(lParam); };
auto getWidget = [lParam](){ return (HWND)lParam; };
auto widgetId = [wParam](){ return LOWORD(wParam); };
auto widgetEvent = [wParam](){ return HIWORD(wParam); };
auto menuType = [wParam](){ return LOWORD(wParam); };
auto eventId = [wParam](){ return LOWORD(wParam); };
switch (uMsg)
{
case WM_CREATE:
{
CREATESTRUCT *cs = ((CREATESTRUCTA*)lParam);
obj = (Content__*)cs->lpCreateParams;
SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)obj);
obj->onCreate(hwnd,cs);
return 0;
}
case WM_DESTROY:
obj->onDestroy();
deleteObject(obj);
return 0;
case WM_SIZE:
obj->onSize(getWidth(),getHeight());
return 0;
default: return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
ATOM SimpleWindow::registerWindow()
{
WNDCLASSEX wincl = {};
wincl.cbSize = sizeof(WNDCLASSEX);
wincl.lpszClassName = "JavWin32WindowSimpleWindow";
wincl.lpfnWndProc = SimpleWindow::windowProc;
wincl.style = CS_HREDRAW|CS_VREDRAW;
wincl.hIcon = LoadIcon(NULL,IDI_APPLICATION);;
wincl.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
wincl.hCursor = LoadCursor(NULL,IDC_ARROW);
wincl.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wincl.cbWndExtra = WINDOW_WND_EXTRA_BYTES;
return RegisterClassEx(&wincl);
}
SimpleWindow::SimpleWindow(Content__ *obj,const char *title, size_t w,size_t h, int style,int ex_style)
: Window( createWindow(obj,title,NULL, w,h, style,ex_style) ),
app_ctx(*this)
{
msg.message = 0;
show();
}
bool SimpleWindow::isOpen()
{
return msg.message != WM_QUIT;
}
void SimpleWindow::close()
{
DestroyWindow(*this);
}
const MSG& SimpleWindow::getMessage()
{
GetMessageA(&msg,*this,0,0);
return msg;
}
const MSG& SimpleWindow::peekMessage()
{
PeekMessageA(&msg,*this,0,0,PM_REMOVE);
return msg;
}
void SimpleWindow::dispatchMessage(const MSG &msg)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}}
main.cpp
#include <Jav/AutoObject.h>
#include <Jav/win32/window/SimpleWindow.h>
using namespace Jav::win32;
class Content : public Content__
{
IAutoComplete *pac = NULL;
IAutoComplete2 *pac2 = NULL;
//IUnknown *punkSource;
//ColumnList columnList;
public:
~Content()
{
if(pac) pac->Release();
if(pac2) pac2->Release();
}
void onCreate(HWND hwnd,CREATESTRUCTA*)override
{
throw; //doesn't throw or terminate
HWND textBox; // pretend this is an edit control
attachAutoCompleteObject(textBox); //doesn't throw either
}
void attachAutoCompleteObject(HWND hwnd)
{
HRESULT hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pac));
if(FAILED(hr)) throw "Failed to create AutoComplete Object\n";
hr = pac->Init(hwnd, &columnList, NULL, NULL);
if(FAILED(hr)) throw "Failed to attach auto complete object\n";
hr = pac->QueryInterface(IID_PPV_ARGS(&pac2)); rep(pac2);
if (FAILED(hr)) throw "Failed to set autocomplete options\n";
hr = pac2->SetOptions(ACO_AUTOSUGGEST);
}
};
int main()
{
Content content;
SimpleWindow win(&content,"",640,480);
while(win.isOpen())
{
auto &msg = win.getMessage();
win.dispatchMessage(msg);
}
}
So you appear to have throw "literal" instead of just throw; you originally posted. Then it indeed should throw. The problem here is that you attempt to throw exception that goes through Windows DLLs, like GetMessage/DispatchMessage fuctions or COM method dispatch, which is not guarrantied to work.
Related
I have been able to download and build the sample projects from Microsoft. I can run the Win32 project and it displays a WebView2 object in the View and appears functional.
For my situation I want to use a CDialog as the parent for the WebView2 control and I can't work out how to do this. When I follow the instructions here it is based on a View style object. In the instructions it says:
Step 3 - Create a single WebView within the parent window
Add a WebView to the main window.
I get lost here and don't knwo how to add the control to my basic CDialog project.
Thank you for your direction on how to deal with this.
This alternative tutorial helped me. I downloaded the sample project, compiled it and it worked. Again, it was based on a application derived from a CView. However, I have managed to work out the principles required.
No doubt more will be involved as I continue to tweak the test application I am making.
Create a dialog based application using boiler place code.
Set the C++ Language Standard to ISO C++ 17.
Install two NuGet packages:
Microsoft.Web.WebView2 NuGet package
Microsoft.Windows.ImplementationLibrary NuGet package
Change InitInstance to use CoInitialize.
Add the ExitInstance handler and call UnCoinitialize.
For testing purposes I simply used the dimensions of my dialog. Begin by adding a variable to your dialog class:
std::unique_ptr<CWebBrowser> m_pWebBrowser;
Then, in OnInitDialog you can do something like this:
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->SetParent(this);
m_pWebBrowser->DisablePopups();
m_pWebBrowser->Navigate(L"https://jw.org", nullptr);
m_pWebBrowser->RegisterCallback(CWebBrowser::CallbackType::TitleChanged, [this]() {
CString title = m_pWebBrowser->GetTitle();
AfxGetMainWnd()->SetWindowText(title);
});
});
}
For the test, I also added the OnSize handler:
void CMFCTestWebView2Dlg::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
CRect rectClient;
if (m_pWebBrowser != nullptr)
{
m_staticBrowser.GetClientRect(rectClient);
m_staticBrowser.ClientToScreen(rectClient);
ScreenToClient(rectClient);
m_pWebBrowser->Resize(rectClient.Width(), rectClient.Height());
}
}
The heart of this is the CWebBrowser class. This is available in the linked tutorial and all I did was adjust the SetParentView method so that it accepted a CWnd pointer instead.
Oh, and you add DestroyWindow to the dialog class:
BOOL CMFCTestWebView2Dlg::DestroyWindow()
{
m_pWebBrowser.reset();
return CDialogEx::DestroyWindow();
}
CWebBrowser Header
#pragma once
#include <EventToken.h>
#include <functional>
#include <map>
struct ICoreWebView2Environment;
struct ICoreWebView2Controller;
struct CWebBrowserImpl;
class CView;
class CWebBrowser : public CWnd
{
public:
enum class CallbackType
{
CreationCompleted,
NavigationCompleted,
TitleChanged,
};
using CallbackFunc = std::function<void()>;
public:
CWebBrowser();
virtual ~CWebBrowser();
virtual BOOL Create(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID,
CCreateContext* = NULL) override;
BOOL CreateAsync(
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID,
CallbackFunc onCreated);
void RegisterCallback(CallbackType const type, CallbackFunc callback);
RECT GetBounds();
void SetBounds(LONG const width, LONG const height) { Resize(width, height); }
void Resize(LONG const width, LONG const height);
CString GetLocationURL();
void Navigate(CString const & url, CallbackFunc onComplete);
void NavigatePost(CString const& url, CString const& content, CString const& headers, CallbackFunc onComplete = nullptr);
void GoBack();
void GoForward();
void Reload();
void Stop();
bool IsNavigating() const { return m_isNavigating; }
void DisablePopups();
void PrintDocument();
CString GetTitle() const { return m_strTitle; }
void SetParent(CWnd* pParent) { m_pParent = pParent; }
bool IsWebViewCreated() const;
protected:
DECLARE_DYNCREATE(CWebBrowser)
DECLARE_MESSAGE_MAP()
private:
CWebBrowserImpl* m_pImpl;
std::map<CallbackType, CallbackFunc> m_callbacks;
EventRegistrationToken m_navigationCompletedToken = {};
EventRegistrationToken m_navigationStartingToken = {};
EventRegistrationToken m_documentTitleChangedToken = {};
bool m_isNavigating = false;
CWnd* m_pParent = nullptr;
CString m_strTitle;
private:
void RunAsync(CallbackFunc callback);
void CloseWebView();
void RegisterEventHandlers();
void ResizeToClientArea();
void NavigateTo(CString url);
CString NormalizeUrl(CString url);
static CString GetInstallPath();
static CString GetInstallPathFromRegistry(bool const searchWebView = true);
static CString GetInstallPathFromDisk(bool const searchWebView = true);
static CString GetUserDataFolder();
void InitializeWebView();
HRESULT OnCreateEnvironmentCompleted(HRESULT result, ICoreWebView2Environment* environment);
HRESULT OnCreateWebViewControllerCompleted(HRESULT result, ICoreWebView2Controller* controller);
static PCTSTR GetWindowClass();
static LRESULT CALLBACK WndProcStatic(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
bool HandleWindowMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* result);
BOOL CreateHostWindow(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID);
};
CWebBrowser Source
#include "pch.h"
#include <wrl.h>
#include "wil/com.h"
using namespace Microsoft::WRL;
#include "EdgeWebBrowser.h"
#include "Messages.h"
#include "WebView2.h"
#include <sstream>
#include <iomanip>
#include <shlwapi.h>
#pragma comment(lib,"shlwapi.lib")
#include "shlobj.h"
#pragma comment(lib,"Version.lib")
#include <string>
#define CHECK_FAILURE_STRINGIFY(arg) #arg
#define CHECK_FAILURE_FILE_LINE(file, line) ([](HRESULT hr){ CheckFailure(hr, L"Failure at " CHECK_FAILURE_STRINGIFY(file) L"(" CHECK_FAILURE_STRINGIFY(line) L")"); })
#define CHECK_FAILURE CHECK_FAILURE_FILE_LINE(__FILE__, __LINE__)
#define CHECK_FAILURE_BOOL(value) CHECK_FAILURE((value) ? S_OK : E_UNEXPECTED)
struct CWebBrowserImpl
{
wil::com_ptr<ICoreWebView2Environment> m_webViewEnvironment;
wil::com_ptr<ICoreWebView2Environment2> m_webViewEnvironment2;
wil::com_ptr<ICoreWebView2> m_webView;
wil::com_ptr<ICoreWebView2_2> m_webView2;
wil::com_ptr<ICoreWebView2Controller> m_webController;
wil::com_ptr<ICoreWebView2Settings> m_webSettings;
};
void ShowFailure(HRESULT hr, CString const & message)
{
CString text;
text.Format(L"%s (0x%08X)", (LPCTSTR)message, hr);
::MessageBox(nullptr, static_cast<LPCTSTR>(text), L"Failure", MB_OK);
}
void CheckFailure(HRESULT hr, CString const & message)
{
if (FAILED(hr))
{
CString text;
text.Format(L"%s : 0x%08X", (LPCTSTR)message, hr);
// TODO: log text
std::exit(hr);
}
}
/////////////////////////////////////////////////////////////////////////////
// CWebBrowser
IMPLEMENT_DYNCREATE(CWebBrowser, CWnd)
/////////////////////////////////////////////////////////////////////////////
// CWebBrowser properties
BEGIN_MESSAGE_MAP(CWebBrowser, CWnd)
END_MESSAGE_MAP()
CWebBrowser::CWebBrowser():m_pImpl(new CWebBrowserImpl())
{
m_callbacks[CallbackType::CreationCompleted] = nullptr;
m_callbacks[CallbackType::NavigationCompleted] = nullptr;
}
CWebBrowser::~CWebBrowser()
{
SetWindowLongPtr(m_hWnd, GWLP_USERDATA, 0);
CloseWebView();
delete m_pImpl;
}
BOOL CWebBrowser::CreateHostWindow(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID)
{
if (lpszClassName == nullptr)
lpszClassName = GetWindowClass();
if (!CWnd::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID))
return FALSE;
::SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
return TRUE;
}
BOOL CWebBrowser::Create(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID,
CCreateContext*)
{
if (!CreateHostWindow(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID))
return FALSE;
InitializeWebView();
return TRUE;
}
BOOL CWebBrowser::CreateAsync(
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID,
CallbackFunc onCreated)
{
if (!CreateHostWindow(nullptr, nullptr, dwStyle, rect, pParentWnd, nID))
return FALSE;
m_callbacks[CallbackType::CreationCompleted] = onCreated;
InitializeWebView();
return TRUE;
}
void CWebBrowser::RegisterCallback(CallbackType const type, CallbackFunc callback)
{
m_callbacks[type] = callback;
}
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_webView->remove_DocumentTitleChanged(m_documentTitleChangedToken);
m_pImpl->m_webController->Close();
m_pImpl->m_webController = nullptr;
m_pImpl->m_webView = nullptr;
m_pImpl->m_webView2 = nullptr;
m_pImpl->m_webSettings = nullptr;
}
m_pImpl->m_webViewEnvironment2 = nullptr;
m_pImpl->m_webViewEnvironment = nullptr;
}
void CWebBrowser::InitializeWebView()
{
CloseWebView();
CString subFolder = GetInstallPath();
CString appData = GetUserDataFolder();
ICoreWebView2EnvironmentOptions* options = nullptr;
HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(
subFolder,
appData,
options,
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
this,
&CWebBrowser::OnCreateEnvironmentCompleted).Get());
if (!SUCCEEDED(hr))
{
CString text;
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
text = L"Cannot found the WebView2 component.";
}
else
{
text = L"Cannot create the webview environment.";
}
ShowFailure(hr, text);
}
}
HRESULT CWebBrowser::OnCreateEnvironmentCompleted(
HRESULT result,
ICoreWebView2Environment* environment)
{
CHECK_FAILURE(result);
if (!environment)
return E_FAIL;
CHECK_FAILURE(environment->QueryInterface(IID_PPV_ARGS(&m_pImpl->m_webViewEnvironment)));
CHECK_FAILURE(environment->QueryInterface(IID_PPV_ARGS(&m_pImpl->m_webViewEnvironment2)));
CHECK_FAILURE(m_pImpl->m_webViewEnvironment->CreateCoreWebView2Controller(
m_hWnd,
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
this,
&CWebBrowser::OnCreateWebViewControllerCompleted).Get()));
return S_OK;
}
HRESULT CWebBrowser::OnCreateWebViewControllerCompleted(
HRESULT result,
ICoreWebView2Controller* controller)
{
if (result == S_OK)
{
if (controller != nullptr)
{
m_pImpl->m_webController = controller;
CHECK_FAILURE(controller->get_CoreWebView2(&m_pImpl->m_webView));
if (!m_pImpl->m_webView)
return E_FAIL;
CHECK_FAILURE(m_pImpl->m_webView->QueryInterface(IID_PPV_ARGS(&m_pImpl->m_webView2)));
CHECK_FAILURE(m_pImpl->m_webView->get_Settings(&m_pImpl->m_webSettings));
RegisterEventHandlers();
ResizeToClientArea();
}
auto callback = m_callbacks[CallbackType::CreationCompleted];
if (callback != nullptr)
RunAsync(callback);
}
else
{
ShowFailure(result, L"Cannot create webview environment.");
}
return S_OK;
}
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));
// DocumentTitleChanged handler
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));
}
void CWebBrowser::ResizeToClientArea()
{
if (m_pImpl->m_webController)
{
RECT bounds;
GetClientRect(&bounds);
m_pImpl->m_webController->put_Bounds(bounds);
}
}
RECT CWebBrowser::GetBounds()
{
RECT rc{0,0,0,0};
if (m_pImpl->m_webController)
{
m_pImpl->m_webController->get_Bounds(&rc);
}
return rc;
}
void CWebBrowser::Resize(LONG const width, LONG const height)
{
SetWindowPos(nullptr, 0, 0, width, height, SWP_NOMOVE| SWP_NOREPOSITION);
}
CString CWebBrowser::GetLocationURL()
{
CString url;
if (m_pImpl->m_webView)
{
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"");
}
url = uri.get();
}
return url;
}
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));
}
void CWebBrowser::Navigate(CString const & url, CallbackFunc onComplete)
{
if (m_pImpl->m_webView)
{
m_callbacks[CallbackType::NavigationCompleted] = onComplete;
NavigateTo(url);
}
}
// The raw request header string delimited by CRLF(optional in last header).
void CWebBrowser::NavigatePost(CString const& url, CString const& content, CString const& headers, std::function<void()> onComplete)
{
if (!m_pImpl->m_webView) return;
CString normalizedUrl{ NormalizeUrl(url) };
m_callbacks[CallbackType::NavigationCompleted] = onComplete;
wil::com_ptr<ICoreWebView2WebResourceRequest> webResourceRequest;
wil::com_ptr<IStream> postDataStream = SHCreateMemStream(
reinterpret_cast<const BYTE*>(static_cast<LPCTSTR>(content)),
content.GetLength() + 1);
CHECK_FAILURE(m_pImpl->m_webViewEnvironment2->CreateWebResourceRequest(
CT2W(normalizedUrl),
L"POST",
postDataStream.get(),
CT2W(headers),
&webResourceRequest));
CHECK_FAILURE(m_pImpl->m_webView2->NavigateWithWebResourceRequest(webResourceRequest.get()));
}
void CWebBrowser::PrintDocument()
{
if (m_pImpl->m_webView)
{
m_pImpl->m_webView->ExecuteScript(L"window.print();", nullptr);
}
}
void CWebBrowser::Stop()
{
if (m_pImpl->m_webView)
{
m_pImpl->m_webView->Stop();
}
}
void CWebBrowser::Reload()
{
if (m_pImpl->m_webView)
{
m_pImpl->m_webView->Reload();
}
}
void CWebBrowser::GoBack()
{
if (m_pImpl->m_webView)
{
BOOL possible = FALSE;
m_pImpl->m_webView->get_CanGoBack(&possible);
if(possible)
m_pImpl->m_webView->GoBack();
}
}
void CWebBrowser::GoForward()
{
if (m_pImpl->m_webView)
{
BOOL possible = FALSE;
m_pImpl->m_webView->get_CanGoForward(&possible);
if (possible)
m_pImpl->m_webView->GoForward();
}
}
void CWebBrowser::DisablePopups()
{
if (m_pImpl->m_webSettings)
{
m_pImpl->m_webSettings->put_AreDefaultScriptDialogsEnabled(FALSE);
}
}
PCTSTR CWebBrowser::GetWindowClass()
{
static PCTSTR windowClass = []
{
static TCHAR const * className = L"EdgeBrowserHost";
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProcStatic;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = AfxGetInstanceHandle();
wcex.hIcon = nullptr;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = className;
wcex.hIconSm = nullptr;
ATOM result = RegisterClassEx(&wcex);
if (result == 0)
{
[[maybe_unused]] DWORD lastError = ::GetLastError();
}
return className;
}();
return windowClass;
}
LRESULT CALLBACK CWebBrowser::WndProcStatic(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (auto app = (CWebBrowser*)::GetWindowLongPtr(hWnd, GWLP_USERDATA))
{
LRESULT result = 0;
if (app->HandleWindowMessage(hWnd, message, wParam, lParam, &result))
{
return result;
}
}
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
bool CWebBrowser::HandleWindowMessage(
HWND, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* result)
{
*result = 0;
switch (message)
{
case WM_SIZE:
{
if (lParam != 0)
{
ResizeToClientArea();
return true;
}
}
break;
case MSG_RUN_ASYNC_CALLBACK:
{
auto* task = reinterpret_cast<CallbackFunc*>(wParam);
(*task)();
delete task;
return true;
}
break;
}
return false;
}
void CWebBrowser::RunAsync(CallbackFunc callback)
{
auto* task = new CallbackFunc(callback);
PostMessage(MSG_RUN_ASYNC_CALLBACK, reinterpret_cast<WPARAM>(task), 0);
}
bool CWebBrowser::IsWebViewCreated() const
{
return m_pImpl->m_webView != nullptr;
}
CString CWebBrowser::GetInstallPath()
{
static CString path = []
{
auto installPath = GetInstallPathFromRegistry(); // check registry for WebView2
if (installPath.IsEmpty())
installPath = GetInstallPathFromDisk(); // check disk for WebView2
if (installPath.IsEmpty())
installPath = GetInstallPathFromRegistry(false);// check registry for Edge
if (installPath.IsEmpty())
installPath = GetInstallPathFromDisk(false); // check disk for Edge
return installPath;
}();
return path;
}
CString CWebBrowser::GetInstallPathFromRegistry(bool const searchWebView)
{
CString path;
HKEY handle = nullptr;
LSTATUS result = ERROR_FILE_NOT_FOUND;
if (searchWebView)
{
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView)",
0,
KEY_READ,
&handle);
if (result != ERROR_SUCCESS)
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
LR"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView)",
0,
KEY_READ,
&handle);
}
else
{
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge)",
0,
KEY_READ,
&handle);
if (result != ERROR_SUCCESS)
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
LR"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge)",
0,
KEY_READ,
&handle);
}
if (result == ERROR_SUCCESS)
{
TCHAR buffer[MAX_PATH + 1]{ 0 };
DWORD type = REG_SZ;
DWORD size = MAX_PATH;
result = RegQueryValueEx(handle, L"InstallLocation", 0, &type, reinterpret_cast<LPBYTE>(buffer), &size);
if (result == ERROR_SUCCESS)
path += CString{ buffer };
TCHAR version[100]{ 0 };
size = 100;
result = RegQueryValueEx(handle, L"Version", 0, &type, reinterpret_cast<LPBYTE>(version), &size);
if (result == ERROR_SUCCESS)
{
if (path.GetAt(path.GetLength() - 1) != L'\\')
path += L"\\";
path += CString{ version };
}
else
path.Empty();
RegCloseKey(handle);
}
return path;
}
CString CWebBrowser::GetInstallPathFromDisk(bool const searchWebView)
{
CString path =
searchWebView ?
LR"(c:\Program Files (x86)\Microsoft\EdgeWebView\Application\)" :
LR"(c:\Program Files (x86)\Microsoft\Edge\Application\)";
CString pattern = path + L"*";
WIN32_FIND_DATA ffd{ 0 };
HANDLE hFind = FindFirstFile(pattern, &ffd);
if (hFind == INVALID_HANDLE_VALUE)
{
[[maybe_unused]] DWORD error = ::GetLastError();
return {};
}
do
{
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
CString name{ ffd.cFileName };
int a, b, c, d;
if (4 == swscanf_s(ffd.cFileName, L"%d.%d.%d.%d", &a, &b, &c, &d))
{
FindClose(hFind);
return path + name;
}
}
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
return {};
}
CString CWebBrowser::GetUserDataFolder()
{
TCHAR szPath[MAX_PATH]{ 0 };
::SHGetFolderPath(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, szPath);
::PathAppend(szPath, LR"(\Demos\)");
::PathAppend(szPath, L"MfcEdgeDemo");
return CString{ szPath };
}
It works. One final comment, atleast for current ooperating systems... Make sure you have installed the WebView2 Runtime.
In my project i have created a simple Button class:
button.hpp:
class Button : public Control {
std::string text;
int id;
std::function<void(void)>&& callback{};
static inline const char* sClassName = "Button";
static LRESULT CALLBACK ButtonSubclassProc(HWND, UINT, WPARAM, LPARAM, UINT_PTR, DWORD_PTR);
public:
Button(HWND parent, POINT position, SIZE size, int id, const std::string& text) noexcept;
virtual ~Button() noexcept;
inline void SetCallback(std::function<void(void)>&& callback) noexcept { this->callback = callback; }
NODISCARD inline int GetId() const noexcept { return id; }
private:
NODISCARD bool CreateControl(HWND) noexcept override;
void DestroyControl() noexcept override;
void Invoke() const noexcept;
};
button.cpp:
#include "button.hpp"
Button::Button(HWND parent, POINT position, SIZE size, int id, const std::string& text) noexcept
: Control(position, size), id(id), text(text) {
isCreated = CreateControl(parent);
SetWindowSubclass(hwnd, &Button::ButtonSubclassProc, id, reinterpret_cast<DWORD_PTR>(this));
}
Button::~Button() noexcept {
RemoveWindowSubclass(hwnd, &Button::ButtonSubclassProc, id);
DestroyControl();
}
NODISCARD bool Button::CreateControl(HWND parent) noexcept {
hwnd = CreateWindow(sClassName, text.c_str(), WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, position.x, position.y, size.cx, size.cy, parent, reinterpret_cast<HMENU>(id), reinterpret_cast<HINSTANCE>(GetWindowLong(parent, GWL_HINSTANCE)), NULL);
return hwnd != NULL;
}
void Button::DestroyControl() noexcept {
Control::DestroyControl();
}
void Button::Invoke() const noexcept {
if (callback) {
callback();
}
}
LRESULT CALLBACK Button::ButtonSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR refData) {
auto button = reinterpret_cast<Button*>(refData);
if (uMsg == WM_COMMAND) {
MessageBox(0, std::to_string(id).c_str(), 0, 0);
button->Invoke();
return TRUE;
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
that's how i pass message from main window procedure:
case WM_COMMAND: {
return SendMessage(reinterpret_cast<HWND>(lParam), WM_COMMAND, wParam, lParam);
}
And it works fine when only one button is created, but when i try to create more, it does not matter which button i would push, i always get callback of the last created button.
I suppose this happens because of SetWindowSubclass call in counstructor and passing it this pointer, but i do not know what should i do. Read about GetWindowSubclass but i dont clearly understand how to use it.
Buttons are created in main window class:
mainwnd.hpp
class MainWindow : public NonCopyable, public NonMovable {
HINSTANCE handle;
HWND hwnd;
int width;
int height;
bool isRegistered{ false };
bool isCreated{ false };
IRenderer* renderer;
Button* buttonStart;
Button* buttonStop;
static inline const char* sClassName = "MAIN_WINDOW_CLASS";
static LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
public:
explicit MainWindow(HINSTANCE handle, int width, int height) noexcept;
~MainWindow() noexcept;
NODISCARD inline bool IsRegistered() const noexcept { return isRegistered; }
NODISCARD inline bool IsCreated() const noexcept { return isCreated; }
NODISCARD bool CheckControls() const noexcept;
NODISCARD inline int Width() const noexcept { return width; }
NODISCARD inline int Height() const noexcept { return height; }
void Update() noexcept;
private:
NODISCARD bool RegisterMainClass() const noexcept;
NODISCARD bool CreateMainWindow() noexcept;
void UnregisterMainClass() noexcept;
void DestroyMainWindow() noexcept;
void CreateControls() noexcept;
void DestroyControls() noexcept;
void OnButtonStartPushed();
void OnButtonStopPushed();
};
mainwnd.cpp
#include "main_window.hpp"
MainWindow::MainWindow(HINSTANCE handle, int width, int height) noexcept
: width(width), height(height), handle(handle) {
isRegistered = RegisterMainClass();
isCreated = CreateMainWindow();
CreateControls();
}
MainWindow::~MainWindow() noexcept {
DestroyControls();
DestroyMainWindow();
UnregisterMainClass();
}
bool MainWindow::RegisterMainClass() const noexcept {
WNDCLASS wc;
memset(&wc, 0, sizeof(WNDCLASSA));
if (!GetClassInfo(handle, sClassName, &wc)) {
wc.style = 0;
wc.hInstance = handle;
wc.lpszClassName = sClassName;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(LONG_PTR);
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.lpszMenuName = NULL;
}
return RegisterClass(&wc) != 0;
}
bool MainWindow::CreateMainWindow() noexcept {
if (IsRegistered()) {
hwnd = CreateWindow(sClassName, NULL, WS_VISIBLE | WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, handle, this);
}
return hwnd != NULL;
}
void MainWindow::UnregisterMainClass() noexcept {
UnregisterClass(sClassName, handle);
isRegistered = false;
}
void MainWindow::DestroyMainWindow() noexcept {
if (hwnd) {
DestroyWindow(hwnd);
hwnd = NULL;
}
isCreated = false;
}
void MainWindow::CreateControls() noexcept {
this->renderer = new Canvas(hwnd, { 10,10 }, { 200,200 });
buttonStart = new Button(hwnd, { 300,50 }, { 100,40 }, 145, "Start");
buttonStart->SetCallback(std::bind(&MainWindow::OnButtonStartPushed, this));
buttonStop = new Button(hwnd, { 300,150 }, { 100,40 }, 168, "Stop");
buttonStop->SetCallback(std::bind(&MainWindow::OnButtonStopPushed, this));
}
void MainWindow::DestroyControls() noexcept {
delete renderer;
delete buttonStart;
delete buttonStop;
}
bool MainWindow::CheckControls() const noexcept {
return renderer != 0;
}
void MainWindow::Update() noexcept {
renderer->Redraw();
}
void MainWindow::OnButtonStartPushed() {
MessageBox(0, "Start", 0, 0);
}
void MainWindow::OnButtonStopPushed() {
MessageBox(0, "Stop", 0, 0);
}
LRESULT CALLBACK MainWindow::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_NCCREATE) {
MainWindow* window = reinterpret_cast<MainWindow*>(lParam);
SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(window));
return TRUE;
}
auto window = reinterpret_cast<MainWindow*>(GetWindowLongPtr(hwnd, GWL_USERDATA));
if (window == nullptr) {
return FALSE;
}
switch (uMsg) {
case WM_DESTROY: {
PostQuitMessage(0);
return 0;
}
case WM_COMMAND: {
return SendMessage(reinterpret_cast<HWND>(lParam), WM_COMMAND, wParam, lParam);
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return TRUE;
}
Message Loop:
MSG message;
memset(&message, 0, sizeof(MSG));
while (true) {
if (PeekMessage(&message, 0, 0, 0, PM_REMOVE) != 0) {
if (message.message == WM_QUIT) {
break;
}
TranslateMessage(&message);
DispatchMessage(&message);
} else {
mainWindow.Update();
}
}
If you look at CONTROL.CPP, you'll see CONTROL::CONTROLNATIVEWINDOW::WndProc(). Here, for a test, I am outputting the CONTROL name. I have multiple derived classes of CONTROL that override the GetName() method. However, the only derived class it ever prints is WINDOW, even though it should output TEXTBOX as well.
What I can't tell is that if the reinterpret_cast in the LRESULT CALLBACK InternalWinProc() is casting in incorrectly:
LRESULT CALLBACK InternalWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
NativeWindow* window = reinterpret_cast<NativeWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (window) {
Message msg = Message::Create(hWnd, message, IntPtr((void*)wParam), IntPtr((void*)lParam));
window->WndProc(&msg);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Or, the line
window = new ControlNativeWindow(this);
in the base class CONTROL constructor is not saving the derived type this correctly.
When running through the debugger and looking at the line above, the this has a vptr pointing all to CONTROL::... methods instead of the respective derived class. Which makes me think that you can't save the this pointer of a derived class in the base class like I'm doing.
And then when I go to cast it back to NativeWindow* in the InternalWndProc function, it is giving a bad cast so when the line
control->GetName();
executes in the
Control::ControlNativeWindow::WndProc(Message * msg)
method it is incorrectly calling
Window::GetName();
Here is the rest of the code:
CONTROL.H
#ifndef CONTROL_H
#define CONTROL_H
#include "IntPtr.h"
#include "CreateParams.h"
#include "Point.h"
#include "Size.h"
#include "NativeWindow.h"
class Control
{
public:
Control();
~Control();
virtual void CreateControl();
Control* GetParent() { return parent; }
void SetParent(Control* parent) { this->parent = parent; }
IntPtr GetHandle() { return window->GetHandle(); }
Point GetLocation() { return Point(x, y); }
void SetLocation(Point point);
Size GetSize() { return Size(width, height); }
void SetSize(Size size);
std::string GetText();
void SetText(std::string text);
void Show();
bool IsHandledCreated();
class ControlNativeWindow : public NativeWindow
{
public:
ControlNativeWindow(Control* control);
virtual std::string GetName() { return "CONTROLNATIVEWindow\n"; }
protected:
virtual void WndProc(Message* msg);
private:
Control* control;
};
virtual std::string GetName() { return "Control\n"; }
protected:
virtual void OnPaint();
virtual void OnTextChanged();
virtual void DefWndProc(Message* msg);
virtual void WndProc(Message* msg);
virtual void CreateHandle();
virtual CreateParams GetCreateParams();
private:
Control* parent;
ControlNativeWindow* window;
int x;
int y;
int width;
int height;
std::string text;
bool visible;
};
#endif // CONTROL_H
CONTROL.CPP
#include "Control.h"
#include "Utility.h"
#include <string>
#include "Message.h"
using namespace std;
Control::Control()
: x(0), y(0),
width(200), height(20), // fix this
visible(false)
{
window = new ControlNativeWindow(this);
}
Control::~Control()
{
}
void Control::CreateControl()
{
CreateHandle();
}
void Control::SetLocation(Point point)
{
x = point.X;
y = point.Y;
}
void Control::SetSize(Size size)
{
width = size.Width;
height = size.Height;
}
std::string Control::GetText()
{
if (IsHandledCreated())
{
HWND hWnd = (HWND)GetHandle().ToPointer();
int len = GetWindowTextLength(hWnd);
wchar_t* str = new wchar_t[len + 1]; // fix
GetWindowText(hWnd, str, len);
return string(WStringToAnsi(str));
}
return text;
}
void Control::SetText(string text)
{
if (IsHandledCreated())
{
wstring str = AnsiToWString(text);
SetWindowText((HWND)GetHandle().ToPointer(), str.c_str());
}
this->text = text;
}
void Control::Show()
{
visible = true;
}
bool Control::IsHandledCreated()
{
return window->GetHandle() == IntPtr::Zero;
}
void Control::OnPaint()
{
}
void Control::OnTextChanged()
{
}
void Control::DefWndProc(Message * msg)
{
window->DefWndProc(msg);
}
void Control::WndProc(Message * msg)
{
switch (msg->Msg)
{
case WM_PAINT:
OnPaint();
break;
case WM_DESTROY:
PostQuitMessage(0);
default:
DefWndProc(msg);
};
}
void Control::CreateHandle()
{
CreateParams cp = GetCreateParams();
SetLocation(Point(cp.GetX(), cp.GetY()));
window->CreateHandle(&cp);
}
CreateParams Control::GetCreateParams()
{
CreateParams cp;
cp.SetParent(parent->GetHandle());
cp.SetCaption(text);
return cp;
}
Control::ControlNativeWindow::ControlNativeWindow(Control* control)
{
wstring ws = AnsiToWString(control->GetName());
OutputDebugString(ws.c_str());
this->control = static_cast<Control*>(control);
ws = AnsiToWString(this->control->GetName());
OutputDebugString(ws.c_str());
}
void Control::ControlNativeWindow::WndProc(Message * msg)
{
// HERE IS THE ISSUE
// IT IS OUTPUTTING WINDOW ALWAYS
// HOWEVER, IT SHOULD OUTPUT THE CORRECT DERIVED CLASS
wstring ws = AnsiToWString(control->GetName());
OutputDebugString(ws.c_str());
control->WndProc(msg);
}
NATIVEWINDOW.H
#ifndef NATIVE_WINDOW_H
#define NATIVE_WINDOW_H
#include <string>
#include "IntPtr.h"
class CreateParams;
class Message;
class NativeWindow
{
public:
NativeWindow();
~NativeWindow();
virtual void CreateHandle(CreateParams* cp);
IntPtr GetHandle() { return handle; }
virtual void DefWndProc(Message* msg);
virtual void WndProc(Message* msg);
virtual std::string GetName() { return "NATIVEWindow\n"; }
private:
IntPtr handle;
class WindowClass
{
public:
WindowClass(std::string className, int classStyle);
std::string GetClsName() { return className; }
int GetClassStyle() { return classStyle; }
private:
void registerClass();
friend NativeWindow;
NativeWindow* targetWindow;
std::string className;
int classStyle;
};
};
#endif // NATIVE_WINDOW_H
NATIVEWINDOW.CPP
#include "NativeWindow.h"
#include <Windows.h>
#include <string>
#include "CreateParams.h"
#include "Message.h"
#include "Utility.h"
using namespace std;
LRESULT CALLBACK InternalWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
NativeWindow::NativeWindow() : handle(IntPtr::Zero)
{
}
NativeWindow::~NativeWindow()
{
}
void NativeWindow::CreateHandle(CreateParams* cp)
{
WindowClass windowClass(cp->GetClsName(), cp->GetClassStyle());
wstring wideClassName = AnsiToWString(windowClass.GetClsName());
wstring wideCaption = AnsiToWString(cp->GetCaption());
HWND hWnd = CreateWindowEx(
cp->GetExStyle(),
wideClassName.c_str(),
wideCaption.c_str(),
cp->GetStyle(),
cp->GetX(), cp->GetY(),
cp->GetWidth(), cp->GetHeight(),
(HWND)cp->GetParent().ToPointer(),
nullptr,
nullptr,
nullptr
);
if (!hWnd)
{
MessageBox(nullptr, L"Call to CreateWindow Failed", L"FAIL", MB_OK);
return;
}
handle = hWnd;
windowClass.targetWindow = this;
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
}
void NativeWindow::WndProc(Message* msg)
{
DefWndProc(msg);
}
void NativeWindow::DefWndProc(Message * msg)
{
DefWindowProc((HWND)msg->HWnd.ToPointer(), (LRESULT)msg->Result.ToPointer(), (WPARAM)msg->WParam.ToPointer(), (LPARAM)msg->LParam.ToPointer());
}
NativeWindow::WindowClass::WindowClass(std::string className, int classStyle)
{
this->className = className;
this->classStyle = classStyle;
registerClass();
}
void NativeWindow::WindowClass::registerClass()
{
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = classStyle;
wndclass.lpfnWndProc = InternalWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = nullptr;
wndclass.hIcon = LoadIcon(nullptr, MAKEINTRESOURCE(IDI_APPLICATION));
wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndclass.lpszMenuName = nullptr;
wstring ws = AnsiToWString(className);
wndclass.lpszClassName = ws.c_str();
wndclass.hIconSm = LoadIcon(nullptr, MAKEINTRESOURCE(IDI_APPLICATION));
WNDCLASSEX wcex;
wcex.lpszClassName = ws.c_str();
bool found = GetClassInfoEx(nullptr, ws.c_str(), &wcex);
if (found)
return;
if (!RegisterClassEx(&wndclass))
{
DWORD dw = GetLastError();
if (dw == ERROR_CLASS_ALREADY_EXISTS)
{
MessageBox(nullptr, L"Class already exists", L"SUCCESS", MB_OK);
}
else
{
MessageBox(nullptr, L"Call to RegisterClassEx Failed", L"FAIL", MB_OK);
}
}
}
LRESULT CALLBACK InternalWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
NativeWindow* window = reinterpret_cast<NativeWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (window) {
Message msg = Message::Create(hWnd, message, IntPtr((void*)wParam), IntPtr((void*)lParam));
window->WndProc(&msg);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
WINDOW.H
#ifndef WINDOW_H
#define WINDOW_H
#include <Windows.h>
#include "Control.h"
#include "ControlCollection.h"
class Window : public Control
{
public:
Window();
~Window();
void Show();
virtual std::string GetName() { return "Window\n"; }
protected:
virtual CreateParams GetCreateParams();
private:
ControlCollection controls;
};
#endif // WINDOW_H
TEXTBOX.H
#ifndef TEXTBOX_H
#define TEXTBOX_H
#include "Control.h"
class TextBox : public Control
{
public:
TextBox();
~TextBox();
virtual std::string GetName() { return "TextBox\n"; }
protected:
virtual void WndProc(Message* msg);
virtual void OnTextChanged();
virtual CreateParams GetCreateParams();
private:
void reflectCommand(Message* msg);
};
#endif // TEXTBOX_H
A C++ object doesn't become a member of a derived class until after the base class' constructor finishes. In short, the object is not textbox type until after the control constructor leaves.
Consider an object with a vtable:
class A { public: A() x() { doit(); } virtual void doit(); private: int x; }
and a derived class:
class B { public: virtual void doit(); private: std::string myname; }
A B object upon entering A's constructor body is like this:
+--------------------+
| A vtable | // A vtable
+--------------------+
| int x | // A's member (0 from initializer list)
+--------------------+
| std::string myname | // B's member (uninit)
+--------------------+
Note that if B::doit() executes, it will access the uninitialized myname.
B's constructor will reassign the vtable pointer to B vtable and run myname's constructor, but that's after we've already executed the A constructor body. Java does this differently, among others, since reflection requires the object not to change type at runtime.
Calling one of the object's virtual methods therefore doesn't refer to the derived type's override yet.
Often, objects have an init method of some kind that the user is required to use after construction to allow derived classes to participate in initialization.
I have a C++ Win32 app that needs to be able to play an external .wav file every time a certain event is triggered. I currently have code that looks like this:
void CALLBACK timerCall(HWND hwnd, UINT msg, UINT timer, DWORD time)
{
if(/*some condition is met*/)
{
std::cout << "Detected event" << std::endl;
PlaySound("file.wav", NULL, SND_FILENAME | SND_ASYNC);
}
}
The problem is, the .wav file is several seconds long and I want each invocation of the event to play a new instance of that sound. Without SND_ASYNC there it wouldn't trigger the event until the sound had finished playing; this was resolved by adding SND_ASYNC. However, now if the event is triggered again while the sound is already playing, it interrupts the playing and simply starts over instead of overlapping the sounds.
How do I prevent the new call to PlaySound from interrupting the previous and force the sounds to overlap?
"waveaudio" device (which is used by PlaySound) does not support playing several files simultaneously. Try to use an .mp3 file. Example below.
#include <Windows.h>
#include <stdio.h>
#include <stdexcept>
#ifdef _UNICODE
#define stprintf_s swprintf_s
#else
#define stprintf_s sprintf_s
#endif
class Player {
public:
Player(LPCTSTR lpFileName) {
MCI_OPEN_PARMS openp;
MCI_SET_PARMS setp;
openp.dwCallback = NULL;
openp.lpstrDeviceType = reinterpret_cast<LPCTSTR>(MCI_ALL_DEVICE_ID);
openp.lpstrElementName = lpFileName;
TCHAR name[32];
static int alias = 0;
stprintf_s(name, TEXT("alias%08d"), alias++);
openp.lpstrAlias = name;
checkerror(mciSendCommand(0, MCI_OPEN, MCI_WAIT | MCI_OPEN_ELEMENT | MCI_OPEN_SHAREABLE | MCI_OPEN_ALIAS, reinterpret_cast<DWORD_PTR>(&openp)));
_device = openp.wDeviceID;
setp.dwCallback = NULL;
setp.dwTimeFormat = 0;
if (mciSendCommand(openp.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, reinterpret_cast<DWORD_PTR>(&setp)) == DWORD(-1)) {
close();
throw std::runtime_error("Can't open MCI device");
}
}
Player(Player const&) = delete;
Player(Player&& other) {
_device = other._device;
other._device = 0;
}
~Player() {
if (_device != 0) {
close();
}
}
void play(HWND hWndNotify) {
MCI_PLAY_PARMS params;
params.dwCallback = reinterpret_cast<DWORD_PTR>(hWndNotify);
params.dwFrom = NULL;
params.dwTo = NULL;
checkerror(mciSendCommand(_device, MCI_PLAY, (hWndNotify != 0) ? MCI_NOTIFY : 0, reinterpret_cast<DWORD_PTR>(¶ms)));
}
void rewind() {
MCI_SEEK_PARMS params;
checkerror(mciSendCommand(_device, MCI_SEEK, MCI_WAIT | MCI_SEEK_TO_START, reinterpret_cast<DWORD_PTR>(¶ms)));
}
void pause() {
MCI_GENERIC_PARMS params;
params.dwCallback = NULL;
checkerror(mciSendCommand(_device, MCI_PAUSE, MCI_WAIT, reinterpret_cast<DWORD_PTR>(¶ms)));
}
void stop() {
MCI_GENERIC_PARMS params;
params.dwCallback = NULL;
checkerror(mciSendCommand(_device, MCI_STOP, MCI_WAIT, reinterpret_cast<DWORD_PTR>(¶ms)));
}
MCIDEVICEID device() const { return _device; }
private:
MCIDEVICEID _device;
static void checkerror(MCIERROR code) {
if (code != 0) {
char buffer[260];
mciGetErrorStringA(code, buffer, sizeof(buffer) - 1);
throw std::runtime_error(buffer);
}
}
void close() {
MCI_GENERIC_PARMS params;
params.dwCallback = NULL;
checkerror(mciSendCommand(_device, MCI_CLOSE, MCI_WAIT, reinterpret_cast<DWORD_PTR>(¶ms)));
}
};
#include <map>
#include <string>
#include <mutex>
#ifdef _UNICODE
typedef std::wstring tstring;
#else
typedef std::string tstring;
#endif
class Repeater {
public:
Repeater(LPCTSTR fn) : fn(fn) {
hWnd = CreateWindowEx(0, TEXT("STATIC"), NULL, 0, 0, 0, 0, 0, HWND_DESKTOP, NULL, GetModuleHandle(NULL), 0);
if (hWnd == NULL)
throw std::runtime_error("Can't create window");
oldproc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG>(this));
SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG>(&myproc));
}
~Repeater() {
DestroyWindow(hWnd);
}
void play() {
Player player(fn.c_str());
std::lock_guard<std::recursive_mutex> lock(devmap_mutex);
player.play(hWnd);
devmap.insert(decltype(devmap)::value_type(player.device(), std::move(player)));
}
HWND wnd() const { return hWnd; }
void stop() {
std::lock_guard<std::recursive_mutex> lock(devmap_mutex);
devmap.clear();
}
private:
HWND hWnd;
tstring fn;
std::recursive_mutex devmap_mutex;
std::map<MCIDEVICEID, Player> devmap;
WNDPROC oldproc;
static LRESULT CALLBACK myproc(_In_ HWND hWnd, _In_ UINT Msg, _In_ WPARAM wParam, _In_ LPARAM lParam) {
auto self = reinterpret_cast<Repeater*>(GetWindowLong(hWnd, GWLP_USERDATA));
switch (Msg) {
case MM_MCINOTIFY: {
// see https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd757358(v=vs.85).aspx
std::lock_guard<std::recursive_mutex> lock(self->devmap_mutex);
self->devmap.erase(static_cast<MCIDEVICEID>(lParam));
return 0;
}
case WM_DESTROY: {
SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG>(self->oldproc));
}
default:
break;
}
return CallWindowProc(self->oldproc, hWnd, Msg, wParam, lParam);
}
};
int main() {
// USE MP3. Forget about WAV.
LPCTSTR filename = TEXT("c:\\Users\\Vyacheslav\\Music\\Ori\\soundtrack\\Racing the Lava.mp3");
#if 0
// without notifications
Player dev1(filename), dev2(filename);
dev1.play();
Sleep(1000);
dev2.play();
Sleep(10000);
#else
// with notifications
{
Repeater rep(filename);
std::thread thread([&rep] {
for (int i = 0; i < 5; ++i) {
rep.play();
Sleep(1000);
}
Sleep(1000);
rep.stop(); // .stop() MUST be called from the same thread as ALL .play() !!!
PostMessage(rep.wnd(), WM_QUIT, 0, 0); // interrupt message processing queue
});
MSG msg;
while (GetMessage(&msg, 0, 0, 0)) {
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
thread.join();
}
Sleep(10000); // silence
#endif
return 0;
}
I can't get Awesomium 1.7.0 to play an html5 video (webm format), even with the sample projects coming along with the SDK (sample_gdi).
sample page: http://www.webmfiles.org/demo-files/
It looks like the video frames are correctly loaded, but the player is stuck on the first frame. Although if I move the progress bar manually, I can browse through the video frames...
I tried with both webViewType window and offscreen, and with enable_gpu_acceleration and enable_web_gl enabled, but every time without success...
My specs: VS2010, windows 7
Any ideas? Thanks!!
Code from the sample project "Sample_gdi" installed automatically by the Awesomium 1.7.0 installer, available in C:\Users[user]\Documents\Visual Studio 2010\Projects\Awesomium\1.7.0.5\BuildSamples\BuildSamples.sln :
Main.cc
#include "../common/application.h"
#include "../common/view.h"
#include <Awesomium/WebCore.h>
#include <Awesomium/STLHelpers.h>
#ifdef _WIN32
#include <Windows.h>
#endif
using namespace Awesomium;
class GDISample : public Application::Listener {
Application* app_;
View* view_;
public:
GDISample()
: app_(Application::Create()),
view_(0) {
app_->set_listener(this);
}
virtual ~GDISample() {
if (view_)
app_->DestroyView(view_);
if (app_)
delete app_;
}
void Run() {
app_->Run();
}
// Inherited from Application::Listener
virtual void OnLoaded() {
view_ = View::Create(800, 600);
view_->web_view()->LoadURL(WebURL(WSLit("http://www.google.com")));
}
// Inherited from Application::Listener
virtual void OnUpdate() {
}
// Inherited from Application::Listener
virtual void OnShutdown() {
}
};
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, wchar_t*,
int nCmdShow) {
GDISample sample;
sample.Run();
return 0;
}
application_win.cc
#include "application.h"
#include "view.h"
#include <Awesomium/WebCore.h>
#include <Awesomium/STLHelpers.h>
#include <string>
using namespace Awesomium;
class ApplicationWin : public Application {
bool is_running_;
public:
ApplicationWin() {
is_running_ = true;
listener_ = NULL;
web_core_ = NULL;
}
virtual ~ApplicationWin() {
if (listener())
listener()->OnShutdown();
if (web_core_)
web_core_->Shutdown();
}
virtual void Run() {
Load();
// Main message loop:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) && is_running_) {
web_core_->Update();
TranslateMessage(&msg);
DispatchMessage(&msg);
if (listener())
listener()->OnUpdate();
}
}
virtual void Quit() {
is_running_ = false;
}
virtual void Load() {
WebConfig config;
web_core_ = WebCore::Initialize(config);
if (listener())
listener()->OnLoaded();
}
virtual View* CreateView(int width, int height) {
return View::Create(width, height);
}
virtual void DestroyView(View* view) {
delete view;
}
virtual void ShowMessage(const char* message) {
std::wstring message_str(message, message + strlen(message));
MessageBox(0, message_str.c_str(), message_str.c_str(), NULL);
}
};
Application* Application::Create() {
return new ApplicationWin();
}
view_win.cc
#include "view.h"
#include <Awesomium/WebCore.h>
#include <Awesomium/STLHelpers.h>
#include <vector>
class ViewWin;
static bool g_is_initialized = false;
static std::vector<ViewWin*> g_active_views_;
const wchar_t szWindowClass[] = L"ViewWinClass";
const wchar_t szTitle[] = L"Application";
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
using namespace Awesomium;
class ViewWin : public View,
public WebViewListener::View {
public:
ViewWin(int width, int height) {
PlatformInit();
WebPreferences webPref;
WebSession *session = WebCore::instance()->CreateWebSession(ToWebString(""),webPref);
web_view_ = WebCore::instance()->CreateWebView(width,
height,
session,
Awesomium::kWebViewType_Window);
web_view_->set_view_listener(this);
// Create our WinAPI Window
HINSTANCE hInstance = GetModuleHandle(0);
hwnd_ = CreateWindow(szWindowClass,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
width + 20,
height + 40,
NULL,
NULL,
hInstance,
NULL);
if (!hwnd_)
exit(-1);
web_view_->set_parent_window(hwnd_);
ShowWindow(hwnd_, SW_SHOWNORMAL);
UpdateWindow(hwnd_);
SetTimer (hwnd_, 0, 15, NULL );
g_active_views_.push_back(this);
}
virtual ~ViewWin() {
for (std::vector<ViewWin*>::iterator i = g_active_views_.begin();
i != g_active_views_.end(); i++) {
if (*i == this) {
g_active_views_.erase(i);
break;
}
}
web_view_->Destroy();
}
HWND hwnd() { return hwnd_; }
static void PlatformInit() {
if (g_is_initialized)
return;
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(0);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWindowClass;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc)) {
exit(-1);
}
g_is_initialized = true;
}
static ViewWin* GetFromHandle(HWND handle) {
for (std::vector<ViewWin*>::iterator i = g_active_views_.begin();
i != g_active_views_.end(); i++) {
if ((*i)->hwnd() == handle) {
return *i;
}
}
return NULL;
}
// Following methods are inherited from WebViewListener::View
virtual void OnChangeTitle(Awesomium::WebView* caller,
const Awesomium::WebString& title) {
std::string title_utf8(ToString(title));
std::wstring title_wide(title_utf8.begin(), title_utf8.end());
SetWindowText(hwnd_, title_wide.c_str());
}
virtual void OnChangeAddressBar(Awesomium::WebView* caller,
const Awesomium::WebURL& url) { }
virtual void OnChangeTooltip(Awesomium::WebView* caller,
const Awesomium::WebString& tooltip) { }
virtual void OnChangeTargetURL(Awesomium::WebView* caller,
const Awesomium::WebURL& url) { }
virtual void OnChangeCursor(Awesomium::WebView* caller,
Awesomium::Cursor cursor) { }
virtual void OnChangeFocus(Awesomium::WebView* caller,
Awesomium::FocusedElementType focused_type) { }
virtual void OnShowCreatedWebView(Awesomium::WebView* caller,
Awesomium::WebView* new_view,
const Awesomium::WebURL& opener_url,
const Awesomium::WebURL& target_url,
const Awesomium::Rect& initial_pos,
bool is_popup) { }
virtual void OnAddConsoleMessage(Awesomium::WebView* caller,
const Awesomium::WebString& message,
int line_number,
const Awesomium::WebString& source) { }
protected:
HWND hwnd_;
};
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
ViewWin* view = ViewWin::GetFromHandle(hWnd);
switch (message) {
case WM_COMMAND:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
case WM_TIMER:
break;
case WM_SIZE:
view->web_view()->Resize(LOWORD(lParam), HIWORD(lParam));
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_QUIT:
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
View* View::Create(int width, int height) {
return new ViewWin(width, height);
}
../common/application.h
#ifndef COMMON_APPLICATION_H_
#define COMMON_APPLICATION_H_
class View;
namespace Awesomium {
class WebCore;
}
// Common class that sets up an application, creates the WebCore, handles
// the Run loop, and abstracts platform-specific details.
class Application {
public:
// Listener interface to be used to handle various application events.
class Listener {
public:
virtual ~Listener() {}
// Event is fired when app (and WebCore) have been loaded.
virtual void OnLoaded() = 0;
// Event is fired for each iteration of the Run loop.
virtual void OnUpdate() = 0;
// Event is fired when the app is shutting down.
virtual void OnShutdown() = 0;
};
virtual ~Application() {}
// Platform-specific factory constructor
static Application* Create();
// Begin the Run loop.
virtual void Run() = 0;
// Ends the Run loop.
virtual void Quit() = 0;
// Create a platform-specific, windowed View
virtual View* CreateView(int width, int height) = 0;
// Destroy a View
virtual void DestroyView(View* view) = 0;
// Show a modal message box
virtual void ShowMessage(const char* message) = 0;
// Get the WebCore
virtual Awesomium::WebCore* web_core() { return web_core_; }
// Get the Listener.
Listener* listener() { return listener_; }
// Set the Listener for various app events.
void set_listener(Listener* listener) { listener_ = listener; }
protected:
Application() { }
virtual void Load() = 0;
Listener* listener_;
Awesomium::WebCore* web_core_;
};
#endif // COMMON_APPLICATION_H_
../common/view.h
#ifndef COMMON_VIEW_H_
#define COMMON_VIEW_H_
namespace Awesomium {
class WebView;
}
// Common class that implements a windowed WebView, handles all input/display,
// and abstracts away all the platform-specific details.
class View {
public:
virtual ~View() {}
// Platform-specific constructor
static View* Create(int width, int height);
// Get the associated WebView
Awesomium::WebView* web_view() { return web_view_; }
protected:
View() { }
Awesomium::WebView* web_view_;
};
#endif // COMMON_VIEW_H_
Looks like I wasn't able to reproduce the bug when deployed on other computers.
There should be something broken on my computer that prevents awesomium to play an html5 video correctly...
I spent a day on this issue and for me it ended up being an audio connection issue. I had to use an HDMI cable or plug speakers into my computer. Once I did that, video did not freeze.
My issues were caused by using a DVI cable and not having anything plugged in to my audio out.