Can my RichEdit Control contain clickable links?

2020-06-28 13:14发布

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?

1条回答
在下西门庆
2楼-- · 2020-06-28 13:28

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

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.

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.

Edit: 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.

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.

Implementing Friendly Name Hyperlinks (Rich Edit 4.1+)

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.

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

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

    StreamInRtf( m_richedit, 
    R"({\rtf1
    {\field{\*\fldinst {HYPERLINK "https://www.stackoverflow.com" }}{\fldrslt {stackoverflow}}}\par
    Some other text\par
    })" );
    
  • To handle clicks you need to enable EN_LINK notifications for the richedit control, e. g.:

    m_richedit.SetEventMask( m_richedit.GetEventMask() | ENM_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
    }
    
  • Beginning with Windows 8, the control can show a tooltip that displays the URL of the link under the mouse cursor. This feature can be enabled by sending the EM_SETEDITSTYLE message to the control:

    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 
    
查看更多
登录 后发表回答