在上一篇文章中,我们创建了一个窗口,但并没有介绍窗口类Win的代码,下面就是Win的头文件:
#pragma once
#include <rapidjson/document.h>
#include <Windows.h>
#include <wrl.h>
#include <wil/com.h>
#include <WebView2.h>
#include <vector>
#include "HostImpl.h"
class Win
{
public:
Win(rapidjson::Value& config);
~Win();
int x, y, w, h;
HWND hwnd;
private:
static LRESULT CALLBACK RouteWindowMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
void initSizeAndPos();
void initWindow();
bool createPageController();
HRESULT pageCtrlCallBack(HRESULT result, ICoreWebView2Controller* controller);
rapidjson::Value& config;
std::vector<wil::com_ptr<ICoreWebView2Controller>> ctrls;
std::vector<wil::com_ptr<ICoreWebView2>> webviews;
wil::com_ptr<Host> hostObj;
};
下面时窗口类的构造函数和第一个执行的方法:
#include "Win.h"
#include "Util.h"
#include <format>
#include <windowsx.h>
#include <dwmapi.h>
#include <WebView2.h>
#include "App.h"
using namespace Microsoft::WRL;
Win::Win(rapidjson::Value& config):config{config}
{
initSizeAndPos();
initWindow();
createPageController();
}
void Win::initSizeAndPos()
{
w = config["w"].GetInt();
h = config["h"].GetInt();
auto centerOfScreen = config["position"]["centerOfScreen"].GetBool();
if (centerOfScreen) {
RECT rect;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
x = (rect.right - w) / 2;
y = (rect.bottom - h) / 2;
}
else {
x = config["position"]["x"].GetInt();
y = config["position"]["y"].GetInt();
}
}
构造函数的输入参数是配置文件config.json中windows数组的一项,你可以在上一篇中会看到config.json的代码
initSizeAndPos方法根据配置文件的信息确定窗口的位置和大小
initWindow方法负责创建窗口,代码如下:
void Win::initWindow()
{
auto hinstance = GetModuleHandle(NULL);
static int num = 0;
WNDCLASSEX wcx{};
wcx.cbSize = sizeof(wcx);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = &Win::RouteWindowMessage;
wcx.cbWndExtra = sizeof(Win*);
wcx.hInstance = hinstance;
wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
auto clsName = std::format(L"wv2js_{}", num++);
wcx.lpszClassName = clsName.c_str();
if (!RegisterClassEx(&wcx))
{
MessageBox(NULL, L"注册窗口类失败", L"系统提示", NULL);
return;
}
auto title = convertToWideChar(config["title"].GetString());
hwnd = CreateWindowEx(NULL, wcx.lpszClassName, title.c_str(), WS_VISIBLE | WS_OVERLAPPEDWINDOW| WS_CLIPCHILDREN,
x, y, w, h, NULL, NULL, hinstance, static_cast<LPVOID>(this));
if (!config["frame"].GetBool()) {
if (config["shadow"].GetBool()) {
DWMNCRENDERINGPOLICY policy = DWMNCRP_ENABLED;
DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &policy, sizeof(policy));
MARGINS margins = { 1,1,1,1 };
DwmExtendFrameIntoClientArea(hwnd, &margins);
}
}
}
这里需要注意的是,如果配置文件中frame设置成false,则窗口就是无边框窗口。
下面是窗口的消息处理方法:
LRESULT CALLBACK Win::RouteWindowMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg == WM_NCCREATE)
{
CREATESTRUCT* pCS = reinterpret_cast<CREATESTRUCT*>(lParam);
LPVOID pThis = pCS->lpCreateParams;
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
auto that = static_cast<Win*>(pThis);
that->hwnd = hWnd;
}
else if (auto obj = reinterpret_cast<Win*>(GetWindowLongPtr(hWnd, GWLP_USERDATA))) {
return obj->wndProc(hWnd, msg, wParam, lParam);
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
LRESULT CALLBACK Win::wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_NCCALCSIZE:
{
if (config["frame"].GetBool()) {
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
return 0;
}
case WM_GETMINMAXINFO:
{
if (config["frame"].GetBool()) {
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
else
{
MINMAXINFO* mminfo = (PMINMAXINFO)lParam;
RECT workArea;
SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
mminfo->ptMinTrackSize.x = 1200;
mminfo->ptMinTrackSize.y = 800;
mminfo->ptMaxSize.x = workArea.right - workArea.left - 2;
mminfo->ptMaxSize.y = workArea.bottom - workArea.top - 2;
mminfo->ptMaxPosition.x = 1;
mminfo->ptMaxPosition.y = 1;
}
return 0;
}
case WM_EXITSIZEMOVE: {
RECT rect;
GetWindowRect(hWnd, &rect);
this->x = rect.left;
this->y = rect.top;
return 0;
}
case WM_SIZE: {
this->w = LOWORD(lParam);
this->h = HIWORD(lParam);
rapidjson::Value& wvs = config["webviews"].GetArray();
for (size_t i = 0; i < ctrls.size(); i++)
{
auto rect = areaToRect(wvs[i]["area"], w, h);
ctrls[i]->SetBoundsAndZoomFactor(rect, 1.0);
}
return 0;
}
case WM_DESTROY: {
PostQuitMessage(0);
return 0;
}
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
其中值得注意的就是WM_SIZE消息,在窗口大小改变的时候,我们也改变了WebView2控件的大小。(一个窗口中可能有多个WebView2控件)
这里用到了一个areaToRect方法,用于把配置文件中的配置信息转化成RECT。如下所示:
RECT areaToRect(rapidjson::Value& area, const int& w, const int& h)
{
auto left{ area["left"].GetInt() };
auto right{ area["right"].GetInt() };
auto top{ area["top"].GetInt() };
auto bottom{ area["bottom"].GetInt() };
auto width{ area["width"].GetInt() };
auto height{ area["height"].GetInt() };
if (right < 0) {
right = left + width;
}
else {
right = w - right;
}
if (bottom < 0) {
bottom = top + height;
}
else
{
bottom = h - bottom;
}
if (left < 0) {
left = right - width;
}
if (top < 0) {
top = bottom - height;
}
return RECT{ left,top,right,bottom };
}
接下来看以下构造函数调用的第三个方法的代码:
bool Win::createPageController()
{
rapidjson::Value& wvs = config["webviews"].GetArray();
auto env = App::getWebViewEnv();
auto callBackInstance = Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(this, &Win::pageCtrlCallBack);
for (size_t i = 0; i < wvs.Size(); i++)
{
auto result = env->CreateCoreWebView2Controller(hwnd, callBackInstance.Get());
if (FAILED(result)) {
return false;
}
}
return true;
}
在这个方法里,我们异步创建了WebView2控件,异步方法的回调函数是:pageCtrlCallBack,这个方法的代码为:
HRESULT Win::pageCtrlCallBack(HRESULT result, ICoreWebView2Controller* controller)
{
HRESULT hr;
wil::com_ptr<ICoreWebView2> webview;
hr = controller->get_CoreWebView2(&webview);
webviews.push_back(webview);
wil::com_ptr<ICoreWebView2Settings> settings;
webview->get_Settings(&settings);
settings->put_IsScriptEnabled(TRUE);
settings->put_AreDefaultScriptDialogsEnabled(TRUE);
settings->put_IsWebMessageEnabled(TRUE);
rapidjson::Value& wvs = config["webviews"].GetArray();
auto index = ctrls.size();
auto rect = areaToRect(wvs[index]["area"], w, h);
hr = controller->put_Bounds(rect);
ctrls.push_back(controller);
hr = webview->Navigate(L"https://www.baidu.com");
return hr;
}
析构函数回收资源:
Win::~Win()
{
for (size_t i = 0; i < webviews.size(); i++)
{
webviews[i]->Release();
}
for (size_t i = 0; i < ctrls.size(); i++)
{
ctrls[i]->Release();
}
}
运行程序得到如下界面。