我的 RichEdit 控件可以包含可点击的链接吗?

2022-01-12 00:00:00 winapi c++ mfc visual-studio-2010

我想向编辑控件或 Rich Edit 2.0 控件显示一系列字符串.之后,我希望显示的一些文本带有下划线和蓝色.然后可以单击这些带下划线的文本以打开另一个对话框或某种对话框.

I want to display a series of strings to an Edit Control or Rich Edit 2.0 Control. After that, I want some of the text displayed to be underlined and in blue. These underlined texts can then be clicked to open another dialog or some sort.

有没有办法做到这一点?

Is there a way to do this?

推荐答案

Rich Edit 2.0 仅支持 自动 RichEdit 超链接 而 Rich Edit 4.1 和更高版本 (msftedit.dll) 支持 友好名称超链接.

Rich Edit 2.0 only supports Automatic RichEdit Hyperlinks while Rich Edit 4.1 and newer (msftedit.dll) supports Friendly Name Hyperlinks.

您可以通过使用 CFE_LINKCFE_HIDDEN 字符格式标志.使用 CFE_LINK 标记文本并通过应用 CFE_HIDDEN 隐藏 URL.处理 EN_LINK 通知以对点击做出反应.此时,您必须进行一些解析以从富文本中提取隐藏的 URL.

You can emulate friendly name hyperlinks in Rich Edit 2.0 by using a combination of the CFE_LINK and CFE_HIDDEN character formatting flags. Mark the text with CFE_LINK and hide the URL by applying CFE_HIDDEN. Handle the EN_LINK notification to react on clicks. At this point you would have to do some parsing to extract the hidden URL from the rich text.

或者只使用 CFE_LINK 作为文本并使用 std::map 将文本映射到 URL.只要存在文本到 URL 的 1:1 映射,这将起作用.

Alternatively just use CFE_LINK for the text and use a std::map to map text to URLs. This will work as long as there is a 1:1 mapping of text to URL.

我刚刚注意到您只是想在单击链接时打开另一个对话框",因此在您的情况下应用 CFE_LINK 就足够了.

I just noted that you just want "to open another dialog" when a link is clicked, so just applying CFE_LINK should be good enough in your case.

编辑 2: 如果您不需要显示格式化文本并且也不需要滚动,我建议使用 SysLink 控件.SysLink 控件显示的链接比 RichEdit 控件中的链接具有更好的可访问性.前者支持 TAB 键来浏览各个链接,而后者不支持.

Edit 2: If you don't need to display formatted text and you also don't need scrolling, I suggest to use the SysLink control. Links displayed by the SysLink control have better accessibility than links in the RichEdit control. The former supports the TAB key to navigate through the individual links whereas the latter does not.

免责声明:以下代码已在 Win 10 下测试,并带有创作者更新.我还没有时间在旧操作系统版本下测试它.

Disclaimer: The following code has been tested under Win 10 with creators update. I haven't found time yet to test it under older OS versions.

  • 如果您的 Visual Studio 版本支持,请在 CWinApp 派生类的 InitInstance() 方法中调用 AfxInitRichEdit5().否则调用 LoadLibraryW(L"msftedit.dll").
  • 确保richedit 控件使用正确的窗口类.资源编辑器默认创建一个 RichEdit 2.0.您需要使用文本编辑器手动编辑 .rc 文件,并将 RichEdit20ARichEdit20W 替换为 RichEdit50W.W 代表控件的 Unicode 版本.
  • 调用 CRichEditCtrl::StreamIn() 以插入包含超链接的 RTF.下面我提供了一个辅助函数 StreamInRtf(),它简化了将字符串流式传输到控件的任务:

  • In the InitInstance() method of your CWinApp-derived class, call AfxInitRichEdit5() if your version of Visual Studio supports it. Otherwise call LoadLibraryW(L"msftedit.dll").
  • Make sure the richedit control uses the right window class. The resource editor creates a RichEdit 2.0 by default. You need to manually edit the .rc file using a text editor and replace RichEdit20A or RichEdit20W by RichEdit50W. The W stands for the Unicode version of the control.
  • Call CRichEditCtrl::StreamIn() to insert the RTF containing the hyperlink(s). In the following I provide a helper function StreamInRtf() that simplifies the task of streaming a string into the control:

struct StreamInRtfCallbackData
{
    char const* pRtf;
    size_t size;
};

DWORD CALLBACK StreamInRtfCallback( DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb )
{
    StreamInRtfCallbackData* pData = reinterpret_cast<StreamInRtfCallbackData*>( dwCookie );

    // Copy the number of bytes requested by the control or the number of remaining characters
    // of the source buffer, whichever is smaller.
    size_t sizeToCopy = std::min<size_t>( cb, pData->size );
    memcpy( pbBuff, pData->pRtf, sizeToCopy );

    *pcb = sizeToCopy;

    pData->pRtf += sizeToCopy;
    pData->size -= sizeToCopy;

    return 0;
}

DWORD StreamInRtf( CRichEditCtrl& richEdit, char const* pRtf, size_t size = -1, bool selection = false )
{
    StreamInRtfCallbackData data;
    data.pRtf = pRtf;
    data.size = ( size == -1 ? strlen( pRtf ) : size );

    EDITSTREAM es;
    es.dwCookie    = reinterpret_cast<DWORD_PTR>( &data );
    es.dwError     = 0;
    es.pfnCallback = StreamInRtfCallback;

    int flags = SF_RTF | ( selection ? SFF_SELECTION : 0 );

    richEdit.StreamIn( flags, es );

    return es.dwError;
}

示例用法(在此处使用原始字符串文字使 RTF 更具可读性):

Example usage (using a raw string literal here to make the RTF more readable):

StreamInRtf( m_richedit, 
R"({tf1
{field{*fldinst {HYPERLINK "https://www.stackoverflow.com" }}{fldrslt {stackoverflow}}}par
Some other textpar
})" );

  • 要处理点击,您需要为 Richedit 控件启用 EN_LINK 通知,例如.g.:

    m_richedit.SetEventMask( m_richedit.GetEventMask() | ENM_LINK );
    

    EN_LINK 的处理程序添加到您的消息映射:

    Add a handler for EN_LINK to your message map:

    BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
        ON_NOTIFY( EN_LINK, IDC_RICHEDIT1, OnLink )
    END_MESSAGE_MAP()
    

    定义事件处理方法来处理鼠标点击和返回键:

    Define the event handler method to handle mouse clicks and the return key:

    void CMyDialog::OnLink( NMHDR* pnm, LRESULT* pResult )
    {
        ENLINK* pnml = reinterpret_cast<ENLINK*>( pnm );
    
        if(   pnml->msg == WM_LBUTTONDOWN || 
            ( pnml->msg == WM_KEYDOWN && pnml->wParam == VK_RETURN ) )
        {
            CString url;
            m_richedit.GetTextRange( pnml->chrg.cpMin, pnml->chrg.cpMax, url );
            AfxMessageBox( L"URL: "" + url + L""" );
    
            *pResult = 1; // message handled
        }
    
        *pResult = 0;  // enable default processing
    }
    

  • 从 Windows 8 开始,控件可以显示工具提示,在鼠标光标下显示链接的 URL.可以通过向控件发送 EM_SETEDITSTYLE 消息来启用此功能:

    DWORD style = SES_HYPERLINKTOOLTIPS | SES_NOFOCUSLINKNOTIFY;
    m_richedit.SendMessage( EM_SETEDITSTYLE, style, style );
    

    如果你错过了定义,这里是:

    In case you are missing the defines, here they are:

    #ifndef SES_HYPERLINKTOOLTIPS
        #define SES_HYPERLINKTOOLTIPS   8
    #endif
    #ifndef SES_NOFOCUSLINKNOTIFY
        #define SES_NOFOCUSLINKNOTIFY   32
    #endif 
    

  • 相关文章