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

问题描述

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

对于我的情况,我想使用 CDialog 作为 WebView2 控件的父级,但我不知道如何执行此操作。当我按照 here 说明进行操作时,它基于 View 样式对象。在说明中它说:

第 3 步 - 在父窗口中创建单个 WebView

向主窗口添加一个 WebView。

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

感谢您对如何处理此问题的指导。

解决方法

这个替代方案 tutorial 帮助了我。我下载了示例项目,编译了它并且它工作了。同样,它基于从 CView 派生的应用程序。但是,我设法制定了所需的原则。

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

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

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

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 类。这在链接教程中可用,我所做的只是调整 SetParentView 方法,使其接受 CWnd 指针。

哦,您将 DestroyWindow 添加到对话框类:

BOOL CMFCTestWebView2Dlg::DestroyWindow()
{
    m_pWebBrowser.reset();

    return CDialogEx::DestroyWindow();
}

CWebBrowser 标题

#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,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,LPARAM lParam,LRESULT* result);

   BOOL CreateHostWindow(
      LPCTSTR lpszClassName,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",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,UINT nID)
{
   if (lpszClassName == nullptr)
      lpszClassName = GetWindowClass();

   if (!CWnd::Create(lpszClassName,lpszWindowName,dwStyle,rect,pParentWnd,nID))
      return FALSE;

   ::SetWindowLongPtr(m_hWnd,(LONG_PTR)this);

   return TRUE;
}

BOOL CWebBrowser::Create(
   LPCTSTR lpszClassName,CCreateContext*)
{
   if (!CreateHostWindow(lpszClassName,nID))
      return FALSE;

   InitializeWebView();

   return TRUE;
}

BOOL CWebBrowser::CreateAsync(
   DWORD dwStyle,CallbackFunc onCreated)
{
   if (!CreateHostWindow(nullptr,nullptr,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};
   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,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,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,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,lParam);
}

bool CWebBrowser::HandleWindowMessage(
   HWND,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)",KEY_READ,&handle);

      if (result != ERROR_SUCCESS)
         result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,LR"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView)",&handle);
   }
   else
   {
      result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge)",LR"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge)",&handle);
   }

   if (result == ERROR_SUCCESS)
   {
      TCHAR buffer[MAX_PATH + 1]{ 0 };
      DWORD type = REG_SZ;
      DWORD size = MAX_PATH;
      result = RegQueryValueEx(handle,L"InstallLocation",&type,reinterpret_cast<LPBYTE>(buffer),&size);
      if (result == ERROR_SUCCESS) 
         path += CString{ buffer };

      TCHAR version[100]{ 0 };
      size = 100;
      result = RegQueryValueEx(handle,L"Version",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,szPath);
   ::PathAppend(szPath,LR"(\Demos\)");
   ::PathAppend(szPath,L"MfcEdgeDemo");
   return CString{ szPath };
}

它有效。最后一个评论,至少对于当前的操作系统...请确保您已安装 WebView2 Runtime