WebView2 - 选择文本并复制到剪贴板

WebView2 - Selecting text and copying to clipboard

使用 CHtmlView class 我能够 select 所有文本并像这样复制到剪贴板:

void CChristianLifeMinistryHtmlView::CopyToClipboard()
{
    ExecWB(OLECMDID_COPY, OLECMDEXECOPT_DONTPROMPTUSER, nullptr, nullptr);
}

void CChristianLifeMinistryHtmlView::SelectAll()
{
    ExecWB(OLECMDID_SELECTALL, OLECMDEXECOPT_DONTPROMPTUSER, nullptr, nullptr);
}

我正在尝试了解如何使用新的 WebView2 API。


更新

WebView2 控件设计支持:

我找到了一个 VB.NET 解决方案,以编程方式将所有页面复制到剪贴板:

Sub Async GetText()
  v = Await wv.ExecuteScriptAsync("document.body.innerText")
End Sub

但我使用的是 Visual C++,我没有看到此方法公开。另外,我不确定这是我想要的,因为我不想复制为纯文本数据,而是像热键一样复制 HTML 数据(适合在 Word 中粘贴)。我也为此提出了 GitHub 问题。


更新

所以我现在已经尝试了这段代码,但它似乎没有做任何事情:

void CWebBrowser::CopyToClipboard()
{
    if (m_pImpl->m_webView != nullptr)
    {
        m_pImpl->m_webView->ExecuteScript(_T("document.body.innerText"), nullptr);

    }
}

更新

根据这个 article 它指出:

Alternately, for ICoreWebView2::ExecuteScript, you provide an instance that has an Invoke method that provides you with the success or failure code of the ExecuteScript request. Also provide the second parameter that is the JSON of the result of running the script.

正在寻找示例。


更新

我现在明白我需要做这样的事情了:

void CWebBrowser::CopyToClipboard()
{
    if (m_pImpl->m_webView != nullptr)
    {
        m_pImpl->m_webView->ExecuteScript(L"document.body.innerText",
            Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
                [](HRESULT error, PCWSTR result) -> HRESULT
                {
                    if (error != S_OK) {
                        ShowFailure(error, L"ExecuteScript failed");
                    }
                    SetClipboardText(result);
                    AfxMessageBox(L"HTML copied to clipboard!", MB_OK);
                    return S_OK;
                }).Get());


    }
}

变量result有HTML页的内容。但是它不喜欢我现在调用SetClipboardText。这是错误:

这是函数:

void CWebBrowser::SetClipboardText(CString strText)
{
    BYTE* pbyText;
    TCHAR* pcBuffer;
    HANDLE  hText;
    UINT    uLength;

    //USES_CONVERSION ;

    if (::OpenClipboard(nullptr))
    {
        // Empty it of all data first.
        ::EmptyClipboard();

        // Replace previous text contents.
        uLength = strText.GetLength();
        //pcBuffer = T2A( (LPTSTR)(LPCTSTR)strText);
        pcBuffer = strText.GetBuffer(uLength);
        if (pcBuffer != nullptr)
        {
            hText = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, (uLength + 1) * sizeof(TCHAR));
            if (hText != nullptr)
            {
                pbyText = (BYTE*)::GlobalLock(hText);
                if (pbyText != nullptr)
                {
                    // Deliberately not _tcscpy().
                    //strcpy_s( (char *)pbyText, uLength+1, pcBuffer);
                    _tcscpy_s((TCHAR*)pbyText, uLength + 1, pcBuffer);
                    ::GlobalUnlock(hText);

                    ::SetClipboardData(CF_UNICODETEXT, hText);

                    // Don't free this memory, clipboard owns it now.
                }
            }
        }
        ::CloseClipboard();

        strText.ReleaseBuffer(uLength);
    }
}

这是我知道的(迄今为止)复制到剪贴板的唯一方法。所以我这里有两个问题:

  1. 它不会让我使用这个功能。
  2. 我希望它只会复制文本 (CF_UNICODETEXT),我不知道这是否足以将数据作为 HTML 粘贴到 Word 中?

关于问题1,我需要制作方法static。然后它编译并将信息复制到剪贴板。

关于问题 2,正如预期的那样,粘贴的数据被删除 HTML,我只剩下文本。换行符在文本中显示为"\n"。这是一个巨大的段落。所以我确实需要 CF_HTML 格式。

遗憾的是,WebView2 没有公开 复制到剪贴板 功能,它已经可以使用了。


更新

这是我在网上为 CF_HTML 找到的 clipboard method:

// CopyHtml() - Copies given HTML to the clipboard.
// The HTML/BODY blanket is provided, so you only need to
// call it like CopyHtml("<b>This is a test</b>");
void CopyHTML(char* html)
{
    // Create temporary buffer for HTML header...
    char* buf = new char[400 + strlen(html)];
    if (!buf) return;

    // Get clipboard id for HTML format...
    static int cfid = 0;
    if (!cfid) cfid = RegisterClipboardFormat("HTML Format");

    // Create a template string for the HTML header...
    strcpy(buf,
        "Version:0.9\r\n"
        "StartHTML:00000000\r\n"
        "EndHTML:00000000\r\n"
        "StartFragment:00000000\r\n"
        "EndFragment:00000000\r\n"
        "<html><body>\r\n"
        "<!--StartFragment -->\r\n");

    // Append the HTML...
    strcat(buf, html);
    strcat(buf, "\r\n");
    // Finish up the HTML format...
    strcat(buf,
        "<!--EndFragment-->\r\n"
        "</body>\r\n"
        "</html>");

    // Now go back, calculate all the lengths, and write out the
    // necessary header information. Note, wsprintf() truncates the
    // string when you overwrite it so you follow up with code to replace
    // the 0 appended at the end with a '\r'...
    char* ptr = strstr(buf, "StartHTML");
    wsprintf(ptr + 10, "%08u", strstr(buf, "<html>") - buf);
    *(ptr + 10 + 8) = '\r';

    ptr = strstr(buf, "EndHTML");
    wsprintf(ptr + 8, "%08u", strlen(buf));
    *(ptr + 8 + 8) = '\r';

    ptr = strstr(buf, "StartFragment");
    wsprintf(ptr + 14, "%08u", strstr(buf, "<!--StartFrag") - buf);
    *(ptr + 14 + 8) = '\r';

    ptr = strstr(buf, "EndFragment");
    wsprintf(ptr + 12, "%08u", strstr(buf, "<!--EndFrag") - buf);
    *(ptr + 12 + 8) = '\r';

    // Now you have everything in place ready to put on the clipboard.
    // Open the clipboard...
    if (OpenClipboard(0))
    {
        // Empty what's in there...
        EmptyClipboard();

        // Allocate global memory for transfer...
        HGLOBAL hText = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, strlen(buf) + 4);

        // Put your string in the global memory...
        char* ptr = (char*)GlobalLock(hText);
        strcpy(ptr, buf);
        GlobalUnlock(hText);

        ::SetClipboardData(cfid, hText);

        CloseClipboard();
        // Free memory...
        GlobalFree(hText);
    }
    // Clean up...
    delete[] buf;
}

但它与 PCWSTR 变量不兼容。


更新

我现在在调试后意识到,如果我使用 document.body.innerTextExecuteScript 会 dpe return 实际的 HTML 数据。生成的字符串只是新行带有 \n 个字符的文本。它不是 HTML 格式。所以我回到原点。

只需使用 ICoreWebView2::ExecuteScript.

我将此作为替代方案展示,因为当前方法不包括实际 HTML 内容。我在互联网上找到的关于使用 document.body.innerText.

的信息具有误导性

一个解决方案是使用 document.execCommand() 路由。我在我的浏览器中添加了以下方法 class:

void CWebBrowser::SelectAll()
{
    if (m_pImpl->m_webView != nullptr)
    {
        m_pImpl->m_webView->ExecuteScript(L"document.execCommand(\"SelectAll\")", nullptr);
    }
}


void CWebBrowser::CopyToClipboard2()
{
    if (m_pImpl->m_webView != nullptr)
    {
        m_pImpl->m_webView->ExecuteScript(L"document.execCommand(\"Copy\")", nullptr);
    }
}

然后我将以下按钮处理程序添加到我的对话框中进行测试:

void CMFCTestWebView2Dlg::OnBnClickedButtonSelectAll()
{
    if (m_pWebBrowser != nullptr)
    {
        m_pWebBrowser->SelectAll();
    }
}


void CMFCTestWebView2Dlg::OnBnClickedButtonCopyToClipboard()
{
    if (m_pWebBrowser != nullptr)
    {
        m_pWebBrowser->CopyToClipboard2();
    }
}

这确实有效,而且目前看来是最简单的解决方案。我看到对 Calendar API 的引用是替代方法,但我还不确定如何实施该方法。

我对 execCommand 的唯一担心是我看到它被记录为 已弃用