Click here to Skip to main content
11,705,208 members (49,287 online)
Click here to Skip to main content

Links with arbitrary text in a RichTextBox

, 1 Jan 2005 CPOL 376K 7.3K 86
Rate this:
Please Sign up or sign in to vote.
An extended RichTextBox that allows to enter links <i>not</i> starting with one of the standard protocols.

Sample Image - RichTextBoxLinks.gif


From time to time, somebody asks in the forums if it is possible to add links to a RichTextBox that don't start with http://, ftp:// or another one of the 'standard' protocols.

Well, here's the solution.


The standard RichTextBox has a quite handy property: DetectUrls.

When this property is set, every time the text in the RichTextBox is changed, the text is parsed for URLs and the matching text ranges are formatted as links (underlined, blue foreground by default).

The problem is, only links starting with one of the recognized protocols (http:, file:, mailto:, ftp:, https:, gopher:, nntp:, prospero:, telnet:, news:, wais:, outlookSmile | :) are recognized and reformatted. When you don't want such a reference, you're stumped, because the standard RichTextBox doesn't allow for manually setting the link style at all.

Fortunately, the .NET RichTextBox is only a wrapper around the Win32 RichEdit control, and so its functionality can be extended by adding the necessary wrappers to send the messages the RichEdit control needs.

How the RichEdit control manages style changes

The RichEdit control defines two sets of messages that control how part of the text is being rendered. One set EM_GETCHARFORMAT, EM_SETCHARFORMAT) is responsible for setting and querying formatting options for character ranges inside a paragraph, the other one (EM_GETPARAFORMAT, EM_SETPARAFORMAT) sets/queries formatting options for entire paragraphs (like alignment, for example).

We'll use the first one to set the desired style.

When you look up EM_SETCHARFORMAT's documentation, you'll see that a CHARFORMAT structure is used to transmit the formatting information. For recent versions of the RichEdit control (V2.0 and up), this structure is extended to a CHARFORMAT2 struct holding additional information.

The definition of this CHARFORMAT2 struct as taken from the platform SDK:

typedef struct _charformat2 {
    UINT cbSize;
    DWORD dwMask;
    DWORD dwEffects;
    LONG yHeight;
    LONG yOffset;
    COLORREF crTextColor;
    BYTE bCharSet;
    BYTE bPitchAndFamily;
    TCHAR szFaceName[LF_FACESIZE];
    WORD wWeight;
    SHORT sSpacing;
    COLORREF crBackColor;
    LCID lcid;
    DWORD dwReserved;
    SHORT sStyle;
    WORD wKerning;
    BYTE bUnderlineType;
    BYTE bAnimation;
    BYTE bRevAuthor;
    BYTE bReserved1;

Among other information, there are two members controlling formatting aspects that can be expressed as flags, i.e., part of the text has this formatting property set or not. These members are dwMask and dwEffects. dwMask is used to specify whether you want to set/query a given formatting option and dwEffect holds the actual value.

There's a flag CFE_LINK that does just what we want: give part of the text the link appearance and behaviour.

Wrapping up the structs and messages

In order to tell the RichEdit control that we want to assign a given character format to part of the text, you need to send the Windows message EM_SETCHARFORMAT to the control. If you're interested in how exactly the Win32 struct has been wrapped and how SendMessage() has been declared, please take a look at the source code.

Rewriting the struct declarations was quite straightforward, the only member that requires a little thought is szFaceName, because C# structs can't be declared to hold a character array of a given size. Microsoft accounted for this case, however, by supplying the SizeConst field to the MarshalAs attribute.

Using the class

When you use the extended RichTextBox, you'll have several new methods available:

public void InsertLink(string text);
public void InsertLink(string text, int position);

to insert a link at a given position (or at the current insert position if not specified).

In case the link text is not suitable as a result of the LinkClicked event (for example, if you have several identical link text that you want to reference different hyperlinks), there are two additional methods where you can specify an additional hyperlink string:

public void InsertLink(string text, string hyperlink);
public void InsertLink(string text, string hyperlink, int position);

They behave like the previous two methods, but after the link text itself, the hyperlink string is added invisibly, separated by a hash ('#'). For example, calling:

InsertLink("online help", "myHelpFile.chm");

will give "online help#myHelpFile.chm" in the LinkClickedEventArgs. That way, you can manage link texts and hyperlinks independently.

The base methods to set or clear the link character format are:

public void SetSelectionLink(bool link);
public int GetSelectionLink();

Both operate on the current selection. The return value of GetSelectionLink() has been made int instead of bool because the current selection can contain link - and regular parts and thus yield inconsistent results. This is reflected by returning -1, whereas consistent link style yields 1 and consistent non-link style yields 0.


There's one detail to take care of. By default, the DetectUrls property of the standard RichTextBox is set, so whatever you type is reformatted automatically.

My extension turns this property off by default because it can interfere with links that have been added programmatically. When the DetectUrls property is set to true and you modify the text adjacent to one of your links, the link formatting will be lost. This does not happen when DetectUrls is set to false, so I recommend you leave it switched off.

Possible extensions

Just like I added support for CFE_LINK, it should be a breeze to add support for other formatting flags as well (for example, CFE_SUBSCRIPT or CFE_SUPERSCRIPT) to add new formatting options not available out of the box.

The necessary flag definitions are included in the source code already so that you don't have to look them up in the platform SDK anymore.

Modification History

  • 02.01.2005

    Initial release.

  • 03.01.2005

    Added support for invisible hyperlink strings.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Software Developer (Senior) 4voice AG
Germany Germany
No Biography provided

You may also be interested in...

Comments and Discussions

QuestionIs there an update? Pin
Al_Pennyworth24-Feb-11 5:27
memberAl_Pennyworth24-Feb-11 5:27 
GeneralThank you. Pin
Member 183329124-Dec-10 12:00
memberMember 183329124-Dec-10 12:00 
Generalvote 5 Pin
Cem Usta9-Nov-10 16:24
memberCem Usta9-Nov-10 16:24 
GeneralMy vote of 5 Pin
Lomis22-Aug-10 22:48
memberLomis22-Aug-10 22:48 
GeneralMy vote of 5 Pin
Sammy Hale1-Aug-10 12:33
memberSammy Hale1-Aug-10 12:33 
GeneralThis does not work with Outlook Form Regions Pin
BillyBox30-Apr-10 13:49
memberBillyBox30-Apr-10 13:49 
GeneralMy vote of 2 Pin
Urs Scherrer14-Apr-10 4:06
memberUrs Scherrer14-Apr-10 4:06 
GeneralRe: My vote of 2 Pin
Urs Scherrer15-Apr-10 4:36
memberUrs Scherrer15-Apr-10 4:36 
I extended the Tool to enable restoration of the Links.

I only attached parts that changed. (Sorry our CodingStyles expect german Names and comments...)

/// <summary>
       /// Fügt an der aktuellen Stelle den Link ein
       /// Eine Selektion wird ersetzt!
       /// </summary>
       public void LinkEinfügen(string pText, string pHyperlink)
           LinkEinfügen(pText, pHyperlink, this.SelectionStart);
       // Link-Kennung wird hidden eingefügt, so dass die Links wieder gefunden werden.
       // Wird beim draifklicken wieder entfernt
       private const string mLinkDecorator = @"@@##@@";
       /// <summary>
       /// Fügt den Link an der aktuellen Stelle ein und ersetzt die Selektion durch den Link
       /// </summary>
       private void LinkEinfügen(string pText, string pHyperlink, int pTextPosition)
           if (pTextPosition < 0 || pTextPosition > this.Text.Length)
               throw new ArgumentOutOfRangeException("position");
           // Selektion setzen
           this.SelectionStart = pTextPosition;
           // RTF-Code erzeugen: @@##@@TITEL#
           //                    ^^^^^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
           //                    hidden             hidden
           string lRTF = string.Format(@"\rtf1\ansi  \v {0}\v0 {1}\v#{2}\v0 \v {0}\v0 ",
                                               mLinkDecorator, pText.Replace(@"\", @"\\"), pHyperlink.Replace(@"\", @"\\"));
           this.SelectedRtf = "{" + lRTF + "}";
           // Den Link als Link formatieren
           int lLängeDesLinks = mLinkDecorator.Length + pText.Length + 1 + pHyperlink.Length + mLinkDecorator.Length;
           this.Select(pTextPosition + 1, lLängeDesLinks);
           // Ein stelle hinter den Link hüpfen
           this.Select(pTextPosition + lLängeDesLinks + 1, 0);

       /// <summary>
       /// Macht die Links wieder zu Links!
       /// </summary>
       internal void LinksRestaurieren()
           int lStartIndex = 0;
           int lEndIndex = 0;
               // Link gefunden?
               if (this.SucheLinkInText(this.Text, ref lStartIndex, ref lEndIndex))
                   // Alles selektieren, inklusvie Decorators;
                   this.Select(lStartIndex, lEndIndex - lStartIndex + mLinkDecorator.Length+1);
                   if (string.IsNullOrEmpty(this.SelectedText))
                   { }
                   // Neuer Start hinter dem End-Decorator
                   lStartIndex = lEndIndex + 1;
           } while (true);

       /// <summary>
       /// Liefert true, wenn im Text eine Link gefunden wurde
       /// </summary>
       /// <param name="pText">der Text (nicht RTF!)</param>
       /// <param name="pStartIndex">Start-Position -> neue Startposition</param>
       /// <param name="pEndIndex">Gefundene Endposition</param>
       /// <returns></returns>
       public bool SucheLinkInText(string pText, ref int pStartIndex, ref int pEndIndex)
           // Nur den Text duchsuchen (nicht den Rtf). Die hidden Texte erscheinen auch.
           pStartIndex = pText.IndexOf(mLinkDecorator, pStartIndex);
           // Anfang gefunden?
           if (pStartIndex >= 0)
               pEndIndex = pText.IndexOf(mLinkDecorator, pStartIndex + 1);
               // Ende gefunden?
               if (pEndIndex >= 0)
                   pEndIndex -= 1;
                   return true;
                   // Kein suaberes Ende!
                   // Start-Link entfernen
                   pText.Remove(pStartIndex, mLinkDecorator.Length);
                   return false;
               return false;

       /// <summary>
       /// Links extrahieren und dann dieses schicken!
       /// </summary>
       protected override void OnLinkClicked(LinkClickedEventArgs e)
           string lTitel = ""; // Wird nicht benötigt;
           LinkClickedEventArgs lNeueArgs = new LinkClickedEventArgs(LinkExtrahieren(e.LinkText, ref lTitel));

       /// <summary>
       /// Zerlegt den Link Text und liefert nur den Hyperlink
       /// </summary>
       public string LinkExtrahieren(string pLinktText, ref string pTitel)
           // Nullstrings abfangen
           if (string.IsNullOrEmpty(pLinktText))
               return "";
               pLinktText = pLinktText.Replace(mLinkDecorator, "");
               string[] lTeile = pLinktText.Split('#');
               string lLink;
               if (lTeile.Length > 1)
                   pTitel = lTeile[0];
                   lLink = lTeile[1];
                   if (lTeile.Length > 0)
                       pTitel ="";
                       lLink = lTeile[0];
                       pTitel ="";
                       lLink = "";
               return lLink;

GeneralRe: My vote of 2 Pin
Valeriant13-May-11 5:51
memberValeriant13-May-11 5:51 
GeneralRe: My vote of 2 Pin
rayden549-Dec-14 8:22
memberrayden549-Dec-14 8:22 
GeneralLoadFile method Pin
Colin Vella6-Jan-10 6:02
memberColin Vella6-Jan-10 6:02 
GeneralExcellent man Pin
i_islamian19-Oct-09 1:52
memberi_islamian19-Oct-09 1:52 
QuestionMajor bug? Pin
Erik Jonsson9-Oct-09 2:07
memberErik Jonsson9-Oct-09 2:07 
AnswerRe: Major bug? Pin
Erik Jonsson9-Oct-09 3:23
memberErik Jonsson9-Oct-09 3:23 
QuestionLinks with different foreground colors? Pin
psouza4micronet13-Aug-09 14:29
memberpsouza4micronet13-Aug-09 14:29 
GeneralLitle fix Pin
chris-ex12-Aug-09 11:10
memberchris-ex12-Aug-09 11:10 
RantRe: Litle fix Pin
[CC]20-Sep-10 0:10
member[CC]20-Sep-10 0:10 
GeneralRe: Litle fix Pin
Member 348099828-Jun-12 0:52
memberMember 348099828-Jun-12 0:52 
JokeBeautiful work! Pin
psouza4micronet7-Aug-09 3:59
memberpsouza4micronet7-Aug-09 3:59 
GeneralUniCode Links FIXED ! Pin
dady_jabery1718-Jul-09 20:06
memberdady_jabery1718-Jul-09 20:06 
GeneralRe: UniCode Links FIXED ! Pin
bitedge6-Mar-13 0:49
memberbitedge6-Mar-13 0:49 
GeneralGreat work Pin
chris-ex10-Jul-09 1:02
memberchris-ex10-Jul-09 1:02 
GeneralUNICODE LINK [modified] Pin
RoxanaZhi2-Jul-09 2:47
memberRoxanaZhi2-Jul-09 2:47 
dady_jabery1718-Jul-09 20:02
memberdady_jabery1718-Jul-09 20:02 
GeneralDo you have version of this code. Pin
itshibu200630-Jun-09 21:39
memberitshibu200630-Jun-09 21:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150819.1 | Last Updated 2 Jan 2005
Article Copyright 2005 by mav.northwind
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid