如何将 WebView2 控件添加到 MFC 项目中的 CDialog 资源?

How do I add a WebView2 control to a CDialog resource in a MFC project?

我已经能够从 Microsoft 下载并构建示例项目。我可以 运行 Win32 项目,它在视图中显示一个 WebView2 对象并显示功能。

对于我的情况,我想使用 CDialog 作为 WebView2 控件的父控件,但我不知道该怎么做。当我按照说明 here 进行操作时,它基于视图样式对象。在说明中它说:

Step 3 - Create a single WebView within the parent window

Add a WebView to the main window.

我在这里迷路了,不知道如何将控件添加到我的基本 CDialog 项目中。

感谢您指导如何处理这个问题。

这个选择 tutorial 帮助了我。我下载了示例项目,对其进行了编译并成功运行。同样,它基于从 CView 派生的应用程序。但是,我已经设法制定出所需的原则。

毫无疑问,随着我继续调整我正在制作的测试应用程序,还会涉及更多内容。

  1. 使用锅炉位置代码创建基于对话框的应用程序。
  2. 将 C++ 语言标准设置为 ISO C++ 17。
  3. 安装两个 NuGet 包:
  1. InitInstance 更改为使用 CoInitialize
  2. 添加 ExitInstance 处理程序并调用 UnCoinitialize

出于测试目的,我只使用了对话框的尺寸。首先向对话框添加一个变量 class:

std::unique_ptr<CWebBrowser> m_pWebBrowser;

然后,在OnInitDialog中你可以这样做:

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);
                });
        });
}

为了测试,我还添加了 OnSize 处理程序:

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());
    }

}

它的核心是 CWebBrowser class。这在链接教程中可用,我所做的只是调整 SetParentView 方法,使其接受 CWnd 指针。

哦,你在对话框中添加 DestroyWindow 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 来源

#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 };
}

有效。最后的评论,至少对于当前的操作系统......确保你已经安装了 WebView2 Runtime.