智谱
引言:
MFC(Microsoft Foundation Classes)虽然年代久远,但在许多企业级桌面应用中仍然占据重要地位。随着用户对界面美观度和交互体验的要求越来越高,传统的MFC控件已经难以满足需求。WebView2控件的出现,为MFC开发者提供了一条完美的现代化升级路径——用HTML/CSS/JS构建界面,用MFC处理业务逻辑。
本文将详细介绍如何在MFC项目中使用WebView2控件,并完整展示我封装的 CWebView2Helper 类的使用方法,帮助你快速实现MFC与前端页面的双向通信。
界面效果:

环境准备
1. 安装WebView2运行时
WebView2依赖Edge浏览器内核,用户机器需要安装WebView2 Runtime。你可以引导用户从微软官方下载,或者在安装包中嵌入引导程序。
如果系统已安装新版Edge浏览器,通常会自动包含WebView2 Runtime。
2. 配置MFC项目
添加WebView2 SDK引用(推荐使用NuGet):
在Visual Studio中打开项目,右键项目 → “管理NuGet程序包”,搜索 Microsoft.Web.WebView2 并安装。
添加WIL(Windows Implementation Libraries):
同样通过NuGet安装 Microsoft.Windows.ImplementationLibrary,该库提供了智能指针等辅助功能。
包含必要的头文件:
#include "WebView2Helper.h"
CWebView2Helper 类核心接口
类概述
CWebView2Helper 封装了WebView2的完整生命周期管理,提供以下核心能力:
| 功能模块 | 主要接口 |
|---|---|
| 创建与初始化 | Create(), CreateWithHtml(), IsInitialized() |
| 导航控制 | Navigate(), NavigateToHtmlString(), Refresh(), GoBack(), GoForward() |
| JS交互 | ExecuteScript(), CallJsFunction(), PostWebMessage() |
| 窗口布局 | Resize(), SetBounds(), SetVisible() |
| 调试工具 | OpenDevTools(), SetUserAgent(), ClearCookies() |
快速开始
第一步:创建WebView2控件
在对话框类中声明成员变量:
class CMyDlg : public CDialogEx
{
// ...
CWebView2Helper m_webView;
};
在 OnInitDialog() 中初始化:
BOOL CMFCApplication2Dlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
thePtr = this;
// ========== WebView2 初始化代码 ==========
TRACE(_T("开始创建 WebView2...\n"));
// 1. 注册鼠标事件回调
// 获取 exe 所在目录
TCHAR szExePath[MAX_PATH];
GetModuleFileName(NULL, szExePath, MAX_PATH);
CString strExeDir = szExePath;
int nPos = strExeDir.ReverseFind(_T('\\'));
if (nPos > 0) strExeDir = strExeDir.Left(nPos);
// HTML 文件路径
CString strHtmlPath = strExeDir + _T("\\index.html");
CString strUrl = _T("file:///") + strHtmlPath;
// 注册鼠标回调
// 注册鼠标回调
// 创建 WebView2
/* if (!m_webView.Create(GetSafeHwnd(),strUrl))
{
AfxMessageBox(_T("WebView2 创建失败,请检查是否安装了 WebView2 运行时"));
return TRUE;
}*/
// 加载 HTML 资源(返回 UTF-8 字节)
CStringA utf8Html = LoadHtmlFromResourceUtf8(IDR_TEXT1);
// 直接创建并显示 HTML,无需等待
m_webView.CreateWithHtml(GetSafeHwnd(), CString(utf8Html));
// 注册消息回调(用于接收 JS 发来的消息)
m_webView.RegisterMessageCallback(MyWebViewCallback);
Sleep(500);
m_webView.Resize();
// 初始化键盘钩子,拦截F12
if (!InitKeyboardHook())
{
TRACE(_T("键盘钩子安装失败\n"));
}
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
至此已完成html界面创建 接下来只需要编写html代码以及JS代码和MFC通信即可
void MyWebViewCallback(const CString& msg)
{
OutputDebugString(msg + "\r\n");
if (msg.Find("新增用户")!=-1)
{
// 发送 JSON 格式的消息
CString jsonMessage = msg;
thePtr->m_webView.PostWebMessage(jsonMessage);
}
if (msg == "window_close")
{
ExitProcess(0);
}
else if (msg == "click_kaodayalog")
{
ShellExecute(NULL, "open", "https://www.ikdya.com", NULL, NULL, SW_SHOWNORMAL);
}
else if (msg == "按下鼠标")
{
// 获取主窗口句柄(需要你提供,假设是 AfxGetMainWnd() 或者全局变量)
if (g_hWnd == NULL)
{
g_hWnd = AfxGetMainWnd()->GetSafeHwnd(); // 或者你的主窗口句柄
}
POINT ptCursor;
GetCursorPos(&ptCursor);
CRect rcWnd;
GetWindowRect(g_hWnd, &rcWnd);
g_ptDragOffset.x = ptCursor.x - rcWnd.left;
g_ptDragOffset.y = ptCursor.y - rcWnd.top;
g_bDragging = TRUE;
//OutputDebugString("开始拖动, 偏移量=" + std::to_string(g_ptDragOffset.x) + "," + std::to_string(g_ptDragOffset.y) + "\r\n");
}
else if (msg == "鼠标移动")
{
if (g_bDragging && g_hWnd)
{
POINT ptCursor;
GetCursorPos(&ptCursor);
int newX = ptCursor.x - g_ptDragOffset.x;
int newY = ptCursor.y - g_ptDragOffset.y;
SetWindowPos(g_hWnd, NULL, newX, newY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
else if (msg == "释放鼠标")
{
g_bDragging = FALSE;
OutputDebugString("结束拖动\r\n");
}
}
html代码发送消息监听消息示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
* { margin: 0; padding: 0; }
body { background: #f5f5f5; }
#chart { width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="chart"></div>
<script>
let myChart;
// 初始化图表
function initChart() {
myChart = echarts.init(document.getElementById('chart'));
// 可选:监听窗口大小变化
window.addEventListener('resize', () => myChart.resize());
}
// 渲染图表(供C++调用)
function renderChart(optionJson) {
if (!myChart) initChart();
myChart.setOption(JSON.parse(optionJson));
}
// 通知C++页面已就绪
window.chrome.webview.postMessage(JSON.stringify({
type: 'ready',
timestamp: Date.now()
}));
// 监听C++发来的消息
window.chrome.webview.addEventListener('message', function(event) {
const msg = JSON.parse(event.data);
if (msg.type === 'updateChart') {
renderChart(JSON.stringify(msg.option));
}
});
</script>
</body>
</html>
注意事项
1. 线程模型
WebView2的回调默认在UI线程执行,不要在回调中执行耗时操作。如需处理,使用PostMessage转到其他线程或使用异步方式。
2. 初始化是异步的
Create() 和 CreateWithHtml() 是异步操作,调用后需要等待 IsInitialized() 返回TRUE才能执行导航、JS调用等操作。建议在注册消息回调后再创建控件。
3. 内存HTML字符串的编码
使用 CreateWithHtml() 或 NavigateToHtmlString() 时,请确保字符串是UTF-8编码。如果从资源加载,可能需要先转换编码。
4. 临时文件清理
使用 NavigateToHtmlWithTempFile() 时,临时文件会在对象析构或下次调用时自动删除,无需手动清理。
5. 部署依赖
发布程序时,确保目标机器已安装WebView2 Runtime。可以在安装程序中加入检测逻辑:












暂无评论内容