RichEdit Friendly Name Hyperlinks

This post is a companion to Automatic RichEdit Hyperlinks. As stated in that post, RichEdit has two kinds of hyperlinks, automatic hyperlinks (autoURLs) and friendly name hyperlinks. A friendly name hyperlink has a name, which is displayed, and a hidden instruction part that contains the actual URL. Such hyperlinks are commonly used when an author wants to display an informative name for a link rather than the URL itself. The present post describes RichEdit’s implementation of friendly name hyperlinks. It’s aimed at programmers, so if you’re not so inclined, you may want to skip it.

A friendly name hyperlink is essentially a field with two parts: an instruction part containing the URL and a result part containing the name. In fact that’s the way it appears in RTF, which has the syntax {\field{\*\fldinst {HYPERLINK "..."}}{\fldrslt{...}}}.

In RichEdit, the hyperlink field entity is represented by character formatting effects, as contrasted to delimiters which are used to structure math objects. As such, these hyperlinks cannot be nested, although in RichEdit 5.0 and later they can be adjacent to one another. The whole hyperlink has the character formatting effects of CFE_LINK and CFE_LINKPROTECTED, while autoURLs only have the CFE_LINK attribute. The CFE_LINKPROTECTED is included for the former so that the autoURL scanner skips over friendly name links. The instruction part, i.e., the URL, has the CFE_HIDDEN attribute as well, since it’s not supposed to be displayed. The URL itself is enclosed in ASCII double quotes and preceded by the string “HYPERLINK “. Since CFE_HIDDEN plays an integral role in friendly name hyperlinks, it cannot be used in the name.

For example, in WordPad, which uses RichEdit, a hyperlink with the name MSN would have the plain text

                HYPERLINK "https://www.msn.com"MSN

The whole link would have CFE_LINK and CFE_LINKPROTECTED character formatting attributes and all but the MSN would have the CFE_HIDDEN attribute.

In RichEdit 4.1, the only way to insert a friendly name hyperlink is to read in the corresponding RTF. For the example above, this RTF could be as simple as

{\rtf1{\field{\*\fldinst{ HYPERLINK " https://www.msn.com"}}{\fldrslt{ MSN} }}}.

Reading in the RTF can work well, but it’s convenient to have a more programmatic approach. Accordingly RichEdit 6.0 added the ITextRange2::SetURL(BSTR bstr) method, which uses the bstr as the URL for the range of text selected by the ITextRange2. The text in the bstr needs to start and end with ASCII double quotes. The word HYPERLINK is inserted in front of the URL by the SetURL() method. RichEdit 7.0 allows you to remove the link status from a friendly name hyperlink by calling SetURL() with a NULL bstr or one that has only the start and end quotes, signifying an empty string.

RichEdit doesn’t allow the CFE_LINKPROTECTED attribute to be set directly by programs. Maybe it will allow it someday after enough testing is completed to ensure that things cannot go awry. Programmatically you can use ITextRange::Expand(tomLink, pDelta) to select the whole friendly name link: hidden URL together with the friendly name. You can move from link to link using ITextRange::Move(tomLink, Count, pDelta).

As for autoURLs, the RichEdit client enables hyperlink notifications (EN_LINK) by sending RichEdit the ENM_LINK flag in the mask included with theEM_SETEVENTMASK message. A notification is sent if the user hits Enter, moves the mouse over it (not doing drag&drop), left clicks (or double clicks) on it. It is also sent if the message EM_HANDLELINKNOTIFICATION is received (OneNote uses this). The ENLINK notification structure contains a CHARRANGE with the start and end character positions of the actual URL (IRI, file path name, email address, etc.) that typically appears in a browser URL window. This doesn't include the "HYPERLINK " string nor the quotes in the hidden part. For the MSN link above, it identifies only the https://www.msn.com characters in the backing store.

In RichEdit 5.0 and later, the client can enable tooltips displaying the URLs by sending the EM_SETEDITSTYLE message with the SES_HYPERLINKTOOLTIPS (8) flag.

Comments

  • Anonymous
    October 11, 2009
    Hello, Murray. This is great to have such functionality, but using friendly named hyperlinks leads to strange bug for me. When I try to select the text after the hyperlink, it occurs shifted by the length of the hyperlink address to the beginning of the text. For example, if I have the following text: "To get more info go [here] and find out all the stuff" In square brackets I marked the hyperlink. Now if I try to select the word after, it will be shifted. The shift is varying depending on the hyperlink address length (I mentioned that above). Is there any workaround to that issue? Or maybe I do something wrong?

  • Anonymous
    March 20, 2011
    Murray, your post is such a tease! ;) Searching the eb for ITextRange2 yields no helpful links, nor does CFE_LINKPROTECTED. And I've had only limited success using CFE_LINK, CFE_HIDDEN and CFE_PROTECTED. Any further information you could add would be gratefully received.

  • Anonymous
    March 20, 2011
    Sorry about that. Hopefully the documentation will improve soon. CFE_LINKPROTECTED is defined as 0x00800000. ITextRange2 is still undocumented, but you can use EM_GETCHARFORMAT and EM_SETCHARFORMAT with the flag SCF_ONLYCFEFFECTS (0x0200) to get/set the flag.

  • Anonymous
    October 27, 2013
    String {rtf1{field{*fldinst{ HYPERLINK " http://www.msn.com"}}{fldresult{ MSN} }}} did't work for me. I've spent a day trying to make it work until I finally found that 'fldresult' should be 'fldrslt'. So double check the manuals :)

  • Anonymous
    October 28, 2013
    Many thanks for pointing out the typo. I've fixed it

  • Anonymous
    August 13, 2014
    Hi! is there any way to see the pInvoke declaration of ENLINK? it is working for me Ok in 32 bits, but fails in 64 bits. My c# declaration is:  [StructLayout(LayoutKind.Sequential)]        private struct ENLINK        {            public NMHDR hdr;            public int msg;            public IntPtr wp;            public IntPtr lp;            public CHARRANGE range;        }        [StructLayout(LayoutKind.Sequential)]        private struct CHARRANGE        {            public int cpmin;            public int cpmax;        }  [StructLayout(LayoutKind.Sequential)]        private struct NMHDR        {            public IntPtr hWnd;            public IntPtr id;            public int code;        } (the cpMax element of CHARRANGE returns zero in 64 bits, an cpMin is equal to what it should be cpMax, which I suspect is a problem of alignment) thxs

  • Anonymous
    August 13, 2014
    I think the problem on 64 bit builds is that int is treated as a 64-bit integer. But CHARRANGE is defined to be two 32-bit integers by LONG. The code stores them as 32-bit integers even on 64-bit builds.

  • Anonymous
    May 18, 2015
    @MurrayS3 I'm having the same problem as @Alex, creating a link with a friendly name different than the URL is giving a wrong selection index. I'm using a RichEdit 4.1 control because of the improved table support. Is there a workaround for this bug or some flag that needs to be set? Googling around I found a few more people with this same issue, but no solution.

  • Anonymous
    May 20, 2015
    I don't have any problem selecting the text following a friendly-name link inserted using RTF in my current RichEdit build. But that build is much more recent than RichEdit 4.1. Maybe we fixed something in between Windows 7 and 8. The RichEdit in Windows 8.1 is much more powerful than RichEdit 4.1 and the one in Windows 10 is still more powerful. Hopefully you can upgrade to Windows 10.

  • Anonymous
    May 31, 2015
    Thanks for your reply MurrayS3. Note that I'm using .NET, I've also tried using richedit 6.0 in my application, but the result is the same, so maybe I need to set some option in richedit? I'm also having a problem when later capturing EN_LINK notifications, The CHARRANGE received points to the corresponding location of the hidden text, but EM_GETTEXTRANGE doesn't seem to be able to get it.

  • Anonymous
    May 31, 2015
    Forgot to mention, I tried EM_EXGETSEL as well, but it seems the result is the same.

  • Anonymous
    May 31, 2015
    Automatic selection updates are applied to APIs that use the UI selection, e.g., EM_EXGETSEL. If you want to select hidden text, try using an ITextRange. That's available in RichEdit 4.1 as well as later (and earlier) versions. You can then get the raw text for the range using ITextRange::GetText(). ITextRange2 has more power, but for this purpose ITextRange works well

  • Anonymous
    August 11, 2015
    Hello MurrayS3! I'm surprised these comments are active. In Windows 8 and later the friendly hyperlinks are blue and underlined. How can this be disabled? We use the friendly links like rich buttons, not URLs. For instance a bold B or emboldening text, and an underlined ul for underlining! So it's ridiculous to underline B as well. Furthermore the underlines and blue are clutter elsewhere in breadcrum like headings, and they interfere with Chinese characters and the underlines stick out the sides of fullwidth characters. It's a nightmare!

  • Anonymous
    August 12, 2015
    The comment has been removed

  • Anonymous
    August 12, 2015
    ^Thank you Murray. SendMessage(ed,WM_USER+275,0,0x00000100); sounds perfect, except for the underline remains. Your post doesn't suggest otherwise, but the documentation for the flag mentions underlining. Just for the record on Windows 7 the friendly name links are not styled, so it seems like Microsoft has visually broken applications that use this feature. Maybe you can send out a memo or something? Any fix for the underlining? Adding ul0 to the front of the name doesn't work. Must it be a stylesheet like tactic? If so how to single out friendly name links?

  • Anonymous
    August 13, 2015
    Sending EM_SETEDITSTYLEEX with wparam = 0 and lparam = SES_EX_HANDLEFRIENDLYURL disables the underlining as well in my handy test app reitp2.exe. It's true that having the feature on by default can change the appearance of existing friendly name links. It was done to mimic what Word does.

  • Anonymous
    August 15, 2015
    The comment has been removed

  • Anonymous
    August 16, 2015
    You're right! When LineServices is active, all links get underlined, both auto and friendly-name. The fix to take the SES_EX_HANDLEFRIENDLYURL setting into account is easy, but I don't see a workaround to use in the interim until the fix is shipped. It's surprising no one reported this earlier. Thanks for telling us.

  • Anonymous
    August 16, 2015
    If you have experience that Windows 8/10 has LineServices always on, it's probably safe to assume it's the culprit--underlines wise. I hope there is a fix that includes the MSFTEDIT.DLL/RICHEDIT50W module for regular applications going forward. [I was looking forward to having spellchecking on Windows 10. I followed your other guide to enable it, but it wasn't working, and I didn't have time to investigate while trying Windows 10. Random black screens cut my visit short. I think RichEdit with OS level spellchecking services will have a long shelf life (and deserves better documentation.) Application programmers can't possibly provide spell checkers equally for everyone in the world. Built-in spellchecker should be Windows 8+'s headline selling point :)]

  • Anonymous
    October 14, 2015
    The comment has been removed

  • Anonymous
    December 05, 2016
    Great! The underlines on Windows 8/10 got fixed by the recent 10 "Anniversary Update." I really wasn't expecting it :) 1 year turn around is not bad. Funny no one else noticed/reported it in all that time on Windows 8.