Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / Visual Basic 14

EXTENDED Version of Extended Rich Text Box (RichTextBoxEx)

Rate me:
Please Sign up or sign in to vote.
4.95/5 (64 votes)
11 Nov 2022CPOL77 min read 189.6K   22.1K   130   125
I've created an enhanced version of the Extended RichTextBox created by Razi Syed.
This is a custom control for advanced rich-text handling

NOTE: Important recent changes and fixes--see below.

The RichTextBox of Visual Studio allows a lot of things; the problem is, a host program has to initiate those things in code. The following control, RichTextBoxEx, which features a toolbar and ruler, allows elaborate end-user-initiated functionality in a single WinForms control. Below is the RichTextBoxEx control--plus CheckBoxes to enable/disable custom links, custom options, and spell-check options--and a multi-line TextBox to show the main control's width, scroll position, and character code for any selected text. The control also allows a host program to add additional context-menu/toolstrip items to it, demonstrated as "custom options" here--in fact, the 3 rightmost toolstrip items on the editor below are added by the host program and not intrinsic to the control.

(NOTE: Once you've downloaded the ZIP file[s], you should use Windows Explorer's right-clicked "Extract All" option on the ZIP file[s] to manually extract the folders and files; AOL sometimes has problems un-zipping large files.)

Image 1

Introduction

This UserControl is a WinForms RichTextBox with a Toolstrip and Ruler added to allow the user to do the following things with it (most recent update--August 3, 2023 12:37 PM EST):

  1. Setting color (foreground and background), font attributes, text alignment
  2. Finding and replacing search text
  3. Spell-checking
  4. Picture-inserting
  5. Hyphenation/dehyphenation
  6. Defining custom characters and smart-character conversions
  7. Saving and loading text
  8. Printing
  9. Tracking scroll-bar information
  10. Keeping track of whether recent changes have been made
  11. Setting tabs indents and tabs via a ruler
  12. Text listing (non-nested bulleted, numbered, and lettered lists)
  13. Symbol-inserting (Unicode characters)
  14. Optional "custom links", in which links have arbitrary visible text and hidden hypertext (link destination)
  15. Redraw-mode setting and smart RTF escaping and insertion
  16. Advanced drag-and-drop
  17. Wrinkly-line auto-spell-checking (aka "continuous spell-check")
  18. Add additional options to the control's context menu and toolstrip--allowing one to add additional functionality to the control through external code, as if it were "built-in"
  19. Optional displaying of hard page breaks as dashed lines
  20. Setting which paragraph list-style options are available to the user at design time using a property page
  21. Allows one to get/set paragraph list-style, alignment-style (including full justification), and true superscript/subscript/normal style
  22. Can be set to display page breaks (as dash line between lines of text on adjacent pages)
  23. Supports full justification and advanced typography, as well as true superscript/subscript (NEW!)

This tip features the RichTextBoxEx control--an enhanced version of the "Extended RichTextBox" created by Razi Syed--along with i00's spell-checker, i00 Spell Check and Control Extensions - No Third Party Components Required! (a slight-bug-corrected and enhanced version, with EXE and dictionary/definition files included only)--plus a class library for printing rich text, getting scroll-bar info, setting list-styles, and dealing with RTF (standard or enhanced control), a ruler control specially designed for rich-text boxes, and a small demo program. The most recent updates (starting with the very most recent) are as follows:

  1. The control now doesn't allow the user to insert an optional (syllable) hypen using Ctrl + - when the AllowHyphenation property is set to False. (August 3, 2023 12:37 PM EST)

  2. VERY IMPORTANT! I've fixed a bug in the InferHypertextScheme routine in RichTextBoxExModule.vb that would case a "live:.cid.xxxxxxx" Skype name to be confused with a local file name ("drive:path\file.ext"). (June 23. 2023 8:35 PM EST)

  3. The context menu for the Insert Link dialog box now allows users to append domain names (i.e., ".com"), prepend schemes (i.e., "http://"), and/or insert host headers (i.e., "www.")--with the options for each in separate submenus. (June 21, 2023 1:33 AM EST)

  4. The underlying RichTextBox constituent control, rtb, has been renamed rtbRTB and is now designated as Friend, rather than Public--while a new read-only Public property, rtb, has been created to return a reference to rtbRTB (formerly rtb) and allow the host program to access the rich-text box's members. This should not break the code of any host programs--you won't have to rename source-code references to "rtb" in them--although it will ensure that the reference to the rich-text box can't be re-assigned to another RichTextBox control instance by the host program. Also, the set of URL schemes in the context menu for the Insert Link dialog box now includes "skype:" and "skype:live:.cid." headers for designating Skype names as hyperlinks--and when InferURLScheme is True and a link is inserted/updated (programmatically or by the user), any URL beginning with "live:.cid." has the scheme "skype:" prepended to it. (You'll need to have Skype for Desktop installed on your system to use this feature, of course.) (June 17, 2023 11:08 PM EST)

  5. I've made some minor changes to reduce flicker and avoid unnecessary actions (such as selecting a document when setting it to use advanced-typography formatting). (March 21, 2023)

  6. A new extension method, UseAdvancedTypographyOnText,  has been added to allow one to enforce advanced-typography on a rich text document--which ensures that pre-existing fully-justified text is so displayed, instead of appearing to be left-justified. It's been added to the PrintRichTextBox library (call it after loading or setting RTF text of a rich-text box) and to the corresponding PrintRichTextBox.vb file in the RichTextBoxEx control, and is used by the control whenever the LoadFile method is used or the Rtf property is set. (It is also used by the SetAlignment extension method.) (March 20, 2023 1:20 AM EST)

  7. The demo image is updated to show the features like full-justification and true superscripting/subscripting. (March 20, 2023)

  8. The spell-checker DLL has been modified to intelligently handle hidden and protected text, so that the RichTextBoxEx control no longer has to perform a series of separate spell-checks when the selection includes custom links; the links and any protected text is simply skipped over by the spell-check tools! (March 3, 2023)
  9. Extension methods have been added to support full-justification alignment and true superscript and subscript! The true script-style approach is now used by the control, rather than vertical-alignment shifting using SelectionCharOffset; for backward compatibility with documents using the old style, the context menu options for superscript/subscript will still be checked if either true scripting or vertical alignment is used on a selection. (March 3, 2023) See below.
  10. The control no longer uses Win32 subclasser APIs to intercept the WM_PAINT message and display page breaks; instead it uses class nested within the RichTextBoxEx class--RTBENativeWindow--which inherits from the NativeWindow subclasser class. (March 3, 2023)
  11. The rtb.ContainsProtectedText extension method, when used on selected text only, checks a selection character by character for protected text whenever the .NET version is 4.7 or greater and the selection contains hidden text--in case protected text is also hidden and not returned by rtb.SelectedRtf. (March 3, 2023)
  12. The EnableOrDisableCustomOptions event has been added to allow a programmer to set the enabled/disabled status of any custom options that have been added to the control's context menu and/or toolbar. (March 3, 2023)
  13. The underlying rich-text box now only displays a context menu upon right-clicking when rtb.ReadOnly is set to False, as the menu options involve user-triggered changes, which can't happen when the (sub-)property is set to True. (November 13, 2022 12:52 AM EST)

  14. I've fixed a bug in the SaveFile and LoadFile method logic that triggers an exception with regard to the format. (November 12, 2022 12:50 AM EST)

  15. A new property, AvailableListStyle, has been added to allow one to specify whether the user can, using the toolbar or context menu, set certain list leader types for selected paragraphs (bullets, Arabic numerals, upper-/lowercase letters, and upper-/lowercase Roman numerals can be enabled or disabled; "leaderless" lists are always enabled.) A property page editor and form has been added (in new file ListStyles.vb) to allow this ListStyleCollection-type proerty to be set at design-time. See Notes About Control below. (October 12, 2022)

  16. SetListStyle no longer uses SendKeys to set a specific list style for selected paragraphs, instead the Win32 APIs are used. SetListStyle also allows more parameters to have finer control over listed paragraphs, and a GetListStyle extension method is now available to retrieve the current style and other listing info for selection paragraphs. (October 12, 2022)

  17. The spell-checker no longer, when a region to be spell-checked contains custom links, checks each non-link part of the region separately. Instead, it checks the entire region at once, with the spell-check dialog itself skipping over the links and (any) other protected text. See Points Of Interest below for information about changes to the  EXE library. (October 12, 2022)

  18. The icon overload of InsertPicture calls a new extension method in PrintRichTextBox, InsertIcon--which inserts an icon into the rich-text box using the background color of the current caret position or selection as the transparent color--unless that value is Color.Empty (indicating multiple background colors in a selection), in which case the general background color is used. The file overload inserts a picture file as an icon if its extension is ".ico", as a metafile if its extension is ".wmf", and as a bitmap for any other extension. (January 31, 2022)

  19. The control no longer relies on the clipboard to insert images into a document (January 28, 2022)! The modifications are derived from the source code for  's "Insert Plain Text and Images into RichTextBox at Runtime", and relies on generating the RTF code for spelling out in image wrapped in a Windows Metafile--which is what the RichTextBox control and WordPad do when images are pasted in. Also, the InsertPicture method no longer has the overload that converts an Image to a Bitmap (no longer needed; since Bitmap derives from Image, this change shouldn't break any code), and the PrintRichTextBox class library has a new method, InsertImage--which allows one to place an image into a standard RichTextBox--once again, without using the clipboard.  (InsertPicture calls InsertImage.)

  20. The auxiliary control class TextRuler's events are now implemented by protected overridable "On" methods (December 28, 2021). Smaller revisions were made earlier in December 2021.

  21. I've made fixes and improvements to the i00SpellCheck library program (October 17, 2021) so that the spell-checker properly makes replacements in the case of documents/selections containing invisible (but not necessarily protected) text, and set up the spell-check option in the main program so that it skips over custom-link text within a document/selection (the spell-check dialog is invoked for each non-link region within the text in question). I've also changed the mechanism by which the spell-checker recognizes word-breaks and numbers so that its functionality is more similar to (albeit not identical to) that of Word.
  22. I've subclassed the main rich-text box control (October 17, 2021)--using SetWindowSubclass and RemoveWindowSubclass APIs using a nested subclasser class (RTBENativeWindow, which inherits from NativeWindow) and a Timer component (Do not remove!)--so that when it's redrawn, any hard (forced) page-break characters optionally cause a broken line to be displayed between the adjacent lines of opposing pages (as opposed to looking like a normal carriage return). Two properties are added to facilitate this feaure, as well as optionally  allowing the user to insert hard page- and line-break characters.
  23. I've altered the key-combinations (October 17, 2021) required to hard-insert left and right quotes manually, so that both Ctrl and Alt must be held down (plus Shift for left/right double quote characters) along with the [~`] or ["'] keys, respectively. This is in order to avoid conflicts with pre-defined rich-text-box shortcut keys for creating characters with accents and other diacritics.
  24. I fixed a bug (August 6, 2021) in the i00SpellCheck library which, when a host app is targeted for .NET 4.7 or later, cause the app to crash when one selects multiple words in the rich-text box, then uses the toolstrip combo boxes to change font name/size, then clicks back on the rich-text box.
  25. I have compensated for a bug in the rich-text box when it's running under .NET 4.7 or later which causes it to incorrectly locate visible text when there's "hidden" invisible text embedded in a document--as in the case of custom links. See below.
  26. There are now overloaded methods for incorporating a picture that's given as a Bitmap, Image, or Icon--as well as the (pre-existing) file-name option.
  27. There is now a property, InferURLScheme, that tells the control whether to infer the scheme of an incomplete hyperlink when one is inserted by the user or by the AddCustomLink method. Also, there is a new method, SelectAnyLinkUnderCaret, which will highlight a custom link if the caret is over it but not already highlighting it. This method can be used, optionally with the IsCaretOverALink property, to make sure that when link-protection is off, a link isn't accidentally modified in an syntactically improper manner. 
  28. The hyphenation tool is now able to tell when a word is already hyphenated and won't confuse a hyphenated word-part for a whole word any more.
  29. There is now a property, IsCaretOverALink, that can be used to see if the caret straddles a custom link before doing a paste when protection is off.
  30. The "strikethrough" (strike) formatting option now has a toolbar button (which is graphic displayed by its entry in the context menu)
  31. I altered the behavior of custom-link protection for the control's custom events as well as for the built-in TextChanged and DragDrop events. Five events now explicitly allow link-protection to be (temporarily) turned off and on by the host program in the host program's event-procedure code. The EditWithLinksUnprotected method and its associated EditingWithLinksUnprotected event--which, unlike the revised other events, unprotect all links (not just ones you "select" to unprotect)--are still supported for use with other events, however. See below.
  32. I added context-submenu options setting the text and background colors; now all toolbar features (except the combo boxes for setting font face and size) are available from the context menu. Also, I updated the graphics for the toolbar buttons and included those images on the corresponding context-menu items. (The demo program now also has images for both its custom toolbar items and its corresponding custom menu items.)
  33. I fixed a minor bug that causes the control to crash when the contents of the text box ends with a custom link with no trailing "invisible" hyphen.
  34. Additional modifications and members exist for dealing with host programs targeted at .NET 4.7 or higher, particularly when it comes to reading and searching for text. See below.
  35. There are more printing options: One can specify individual pages to print or skip, or specify whether to print odd or even pages only in a page range.
  36. The drag-and-drop capacities of the control are expanded. See below.
  37. It is now possible to use the Find/Replace dialog to overwrite text containing links. See below. 
  38. A Find Previous option (Shift + F3) allows one to search outside the Find dialog in the opposite direction to the direction specified by the last Find dialog invokation.
  39. A different spell-checker is in use now. (I used to use Paul Welter's "NetSpell - Spell Checker for .NET".)
  40. It is now possible to customize the toolstrip and context menu with additional items. Note that there have been changes back and forth regarding how this is supported! See below.

Features

This version of RichTextBoxEx has the following enhancements to Razi Syed's version:

  1. Buttons for Background Color, Italic, Strikethrough, Insert Picture, Find/Replace, Find Next, Find Previous, Hyphenate, Insert Symbol, and Insert/Edit/Remove Hyperlinks (when custom links are enabled)--and combo boxes for face and size--have been added to the toolstrip. Also, when the control is resized, the constituent controls--toolstrip and text box--are resized to fill it.
  2. The underlying RichTextBox, "rtb", is declared Public so that its properties, methods, and events can be accessed; just be sure to include "rtb" in the object expression.
  3. The underlying "rtb" control has its AllowDrop property set to True, so that rich text can easily be dragged and dropped back and forth with other rich-text apps. HideSelection is set to False so selected text can be viewed while find, replace, and hyphenation dialogs are showing. ShowSelectionMargin is set to True so that the user can easily select whole lines of text.
  4. A context menu with shortcut keys is assigned to the "rtb" control.
  5. The constituent rich-text control's internal KeyDown event defines several "custom characters"--and raises an event to allow you to define your own custom characters or shortcuts; its internal KeyPress event defines "smart characters"--and raises an event to allow you to define your own smart-character replacements. (The text boxes for the Find/Replace dialog box recognize the default custom-character keystrokes now.)
  6. There are top-level properties for allowing/disallowing spell-check/bullets/picture-insertion/insert-text/smart-text by the user, showing/hiding the toolstrip (items' shortcut keys and any custom characters still work), allow/disallow setting text and together--and, as previously mentioned, an event to allow custom key sequences to generate RTF characters/strings.
  7. To search for text, use Find/Replace (Ctrl + F / Ctrl + H) to pull up a dialog box for entering search text, search direction (up or down), and whether or not to make search case-sensitive and/or whole word. If found in the rich-text, the word is selected. If opting to replace, you can replace one occurence or all occurences from the current position in the specified direction. To exit the Find or Replace dialog, hit Cancel (ESC). To find and select subsequent occurences of the same text (using the same direction and criteria), use Find Next (F3). To find and select previous occurences (in the opposite direction but with the same criteria), use Find Previous (Shift + F3).
  8. Pictures can be added. When the Insert Picture button (or the equivalent context-menu item) is selected, a file dialog allows the user to select a picture file. The picture is copied to the clipboard and pasted into the control at the current caret position (replacing any pre-highlighted text).
  9. The control can search for hyphenate-able wrapped text; when such a word is found, a dialog box allows one to position the breaking point, and either hyphenate the word, skip it (leave it alone), or quit. (The word must be a series of alphanumeric characters with enough length/space to leave 2+ characters at the end of the first line and 3+ characters at the beginning of the second line after breaking.)
  10. The control can dehyphenate text: The options to hyphenate text, remove all hyphens, or remove only "hidden" hyphens (hyphens not displayed because text doesn't wrap after them) are in the "Hyphenate" context menu.
  11. A class library is added to facilitate printing of rich text, tracking of the text box's scroll-bar info, handling lists, dealing with RTF, and turnoning on/off a control's redraw mode.
  12. Custom links, which display arbitrary visible text and go to invisible hyperlinks, are supported.
  13. Methods exist for saving and loading text, inserting pictures, and checking for/adding/removing custom links
  14. A ruler can be displayed to allow the user to set first-line/hanging/right indents and tabs.
  15. Lists can be specified as bulletted, Arabic-numbered, upper-case/lower-case Roman-numeral-ed, or upper-case/lower-case lettered. (Lists cannot be "nested"--i.e., for outlining. Unfortunately.)
  16. A dialog exists for finding an inserting symbols that can be represented by printable Unicode characters in various fonts.
  17. This version now uses a more enhanced, nuanced spell-checker. (Be sure to "pre-reference" it before dropping this control onto a form, though.)
  18. The host program can add additional items to the context menu and toolbar of the control in order to add aditional functionality through host code.
  19. Hard page-break characters cause dashed lines to appear between the last line on one page and the first line on the next. (Soft page breaks are not displayed, as they depend on the PrinterSettings for printout.)

Notes About Control

Note when starting or loading a project/solution using this control after working with one that does not (please read)

If you're working with a project/solution that doesn't use this control, and want to load or create one that does, then exit Visual Studio entirely, and re-start it, before loading or creating a project which uses this control; otherwise, forms which use this control on the latter project won't display properly! The problem has something to do with the ListStyleCollection type, which is used for the AvailableListStyle property, inheriting from Collection(Of Boolean)--and it doesn't manifest itself when a project/solution meant to use the control is the first to be loaded/created from within a Visual Studio session or if the previous project/solution uses the control. (If you want to add to solution which doesn't use this control a project which does, then add/create the additional project, making sure that it references the control [don't add it to any forms yet!], then exit Visual Studio and re-start it with the amended solution. You should then be able to add the control to forms and have them display properly.) Sorry about any inconvenience.

Note when removing control from a form after modifying "DoCustomLinks" at design-time

Make sure before "cutting" or removing this control from a form at design-time that DoCustomLinks is set to False in the Properties window; otherwise, trying to remove the control will crash and re-boot the Visual Studio environment! Have this property set to True at design-time only when you're not about to clipboard-cut or remove the control.

Considerations when "host application" is targeted for .NET 4.7 or higher (please read)

Issues resolved:

.NET 4.7 handles the RichTextBox control--and by extension this control--differently (in my personal opinion, unreliably and negatively) when it comes to documents containing "hidden" (invisible) text (!!), which are the basis for "custom links" (links where the displayed "friendly text" is different from the "hyperlink" URL) in my control. In particular, plain-text and character-index-finding members of the underlying RichTextBox control are no longer fully reliable when there's hidden text in a document. For some of the members--particularly, Text, SelectedText, SelectionLength, and Find--I've provided "work-around" extension methods in the PrintRichTextBox module, as well as a slightly-modified format for custom links.

However, one thing I previously hadn't been able to reliably, let alone transparently, compensate for is RichTextBox's new-found difficulty reliably finding links when there's hidden text. I discovered--by chance--that the control in .NET 4.7+ will reliably find and respond to visible text when there's hidden text if additional "visible" characters are padded onto the end of the document. Therefore,the control checks upon instantiation to see if Text returns hidden text--and if not, then it arranges it so that whenever the document contains hidden chartacters, an equal number of "optional" syllable hyphens (not marked as hidden but not displayed because they don't break a word) plus 5 additional ones are padded onto the end of the document. The routines that handle selection ensure that the user can't place the caret over them or make a selection including them (unless the entire document is selected). Also, the ExtendedText, Text, and Rtf properties of this (surface) control omit any hyphen padding when returning text, and the SaveText method omits the padding when saving the document. (The underlying members of the constituent rich-textbox control, however, do return or save the padding, so you shouldn't prefix these members with ".rtb" unless you want to get or save the padding!) No padding is used when the host environment is pre-.NET 4.7, because the control properly handles hidden text in such a case by default.

Also, I fixed a bug in the i00SpellCheck library that caused a host app to crash when using the toolstrip combo boxes to change font name/size and then clicking back to the text box. This is an issue when the host app's target platform is .NET 4.7+. 

Issues still unresolved:

The Undo and Redo methods of RichTextBox trigger unhandle-able exceptions in .NET 4.7+ when they follow a "programmatic" (non-user) edit, and therefore this control is designed not to support undo/redo when the host app's target platform is .NET 4.7+. Also, the control is designed so that when F3 / Shift + F3 are used to find the next/previous occurence of text and the target platform is .NET 4.7+, it merely moves the caret to the start of any found text, rather than highlighting it. (For some reason, the text-box wants to "case-modify" highlighted text after that option is executed!)

Note when using on TabPages of TabControls

If this RichTextBoxEx control is placed on a TabControl's TabPage, and that page is not initially the current page when the form is loaded/displayed, then the text-box part of the control can malfunction and refuse (!) mouse and keyboard input when the user displays the tab-page and activates the control (particularly the second time). To remedy this situation, temporarily make that tab-page the current one in the form's Load or Activated event. (If you create an instance of RichTextBoxEx onto a tab page at run-time, then make sure the tab page is temporarily selected before the code yeilds control to the end user.) For instance:

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
   Dim CurrentTab As TabPage = TabControl1.SelectedTab
   TabControl1.SelectedTab = TabWithRTBEControl 'temporarily select tab with editor
   TabControl1.SelectedTab = CurrentTab 'restore previous tab
End Sub

Control's Reference Dependencies (please read)

To use the RichTextBoxEx UserControl in a project/solution, you must first add references to RichTextBoxEx.dll and i00SpellCheck.exe (!) before adding the control to a project's forms. (The spell-checker is a seperate component, so you have to reference it too.) Make sure that "References" allows "Copy Local" so that both files will get copied to the startup directory path of any project, and that the source directory for RichTextBoxEx.dll when you add my control to the toolbox (via "Choose Items") is the same as the one you will rely on when referencing the control in any project. Before running a host project, you should save it in order to guarantee that the libraries are in its startup path and so you can then add the dictionary (dic.dic) and definition (def.def) files to that directory. If you leave out these text files and invoke the spell-checker anyway, the program will not crash at run-time, as was the case with the earlier spell-checker tool--however, you won't get wrinkly lines under misspelled text or suggestions in the spell-check dialog/context-menu. Also, if you leave out just the definition file, suggestions will display but definitions won't when you hover over them. The spell-checker is pretty much user-proof when it comes to those files, so you can omit them if you're not allowing spell check, or include just the dictionary file if you want spell-check but not definitions. (Do, however, pre-reference i00SpellCheck.exe anyway, even if you're not "using" it, or the control won't properly load on to the form!)

This version of RichTextBox.dll incorporates the main control (RichTextBoxEx), together with the TextRuler constituent control, and the PrintRichTextBox library, all into 1 single project--eliminating the need for as many references as before.

Also, if you plan to use the extension methods of the PrintRichTextBox class library in the host application's code, then you need to Import RichTextBoxEx (when using the custom control RichTextBoxEx) or Import RichTextBoxEx.PrintRichTextBox (when just using an ordinary RichTextBox--in which case you can use PrintRichTextBox.dll in place of RichTextBoxEx.dll and i00SpellCheck.exe) in any code files using the methods. (To access spell-checker features outside of this control, Import i00SpellCheck.)

Each of the auxiliary controls and libraries--PrintRichTextBox, TextRuler, and i00SpellCheck--can be used on their own; to find out how to use the TextRuler, see the article General-purpose RULER Control for Use with RICH-TEXT CONTROLS by yours truly (derivative of 's Advanced Text Editor with Ruler).

Making Design-Time Changes to UserControl itself (please read)

If, when modifying the control library project itself, you see 2 entries in the Error List window saying, for instance, "TextRuler is not a member of RichTextBoxEx" popping up, simply enter the designer file RichTextBoxEx.Designer.vb, remove "RichTextBoxEx." from the object expressions in the offending statements (just click on the error messages to get to the statements), and Save. (This tends to happen when I display the UserControl, make any design changes, and then open up the main code file; I don't know why. It's a minor irritant when editing the source code, but you must make these corrections after any design-time changes or the library won't build!)

Custom-Link Support

I've now added support for links which use arbitrary visible text, and hidden text to indicate the hypertext link to go to when the visible text is clicked. I've taken as inspiration ideas from both mav.northwind's Links with arbitrary text in a RichTextBox (embedding invisible text in a link for the hypertext destination) and Israel Sant'Anna's AnyLinkRichTextBox alternative (text-protecting links and using a list to keep track of them [in my case, a SortedList instead of a Dictionary]). I've tried to make it simple, and have endeavored with my own version to eliminate many of the flaws noticed in both of theirs; it's resulted in a lot of artful coding hacks--custom links in a Visual Studio RichTextBox aren't easy to implement! My explanation regarding the implementation is at the end of the article--I recommend very carefully looking over the related code (particularly variables/constants/procedures/types with "CustomLink" in their names, and the last several private procedures in RichTextBoxEx.vb) before attempting to improve or customize it!

Member Changes

If you already are using this control, note that the following changes since I first created it:

  1. AllowBullets property has been replaced by AllowLists. If you want your pre-existing programs to run or even display the (new version of the) control on the projects' forms, you must change all references to "AllowBullets" to "AllowLists" in all code, including the forms' "designer-code" files! (Simply use the Replace text option with the scope set to "Entire Solution"; or display Error box and click on errors about this to get to and fix the antiquated statements one at a time.) Also, OvertypeMode has been removed, so you must remove references to it from your code (including the designer files).
  2. The CustomContextMenuItems and CustomToolStripItems properties were briefly changed to type List(Of ToolStripItem) instead of ToolStripItemCollection, but are now type TooolStripItemCollection again. I've also removed the ToolStripItemList method from the control, and replaced it with the GetToolStripItemArray method. I've added the CustomContextMenuArray and CustomToolStripArray properties, which are identical to their "xxxItems" counterparts, except that they are of array type ToolStrip() instead of ToolStripItemCollection.  Although these new properties, together with the GetToolStripItemArray method, effectively remove the need for the original properties, I have kept them to avoid breaking anybody's code.
  3. I've removed the property AreLinksProtected and added a new one, IsCaretOverALink, to enable a host program to avoid pasting into a custom link when it's not protected inside certain events.
  4. The new property AvailableListStyles allows one to set whether individual styles for enumerating paragraphs in a list are available to the user when AllowLists is set to True.

Underlying RichTextBox control's Properties, Methods, and Events

The underlying RichTextBox, property rtb, is defined as Public so you can access its properties, methods, and events. To catch events for the underlying rtb, use AddHandler and RemoveHandler to hook procedures to them; using WithEvents and Handles doesn't work with constituent controls! The last line of the main code sample shows an example.

NOTE: This control will no longer allow the underlying rtb control's ContextMenu and ContextMenuStrip properties to be directly changed from outside. You can set these properties in a host project, but doing so will have no effect. This prevents the built-in context menu for the text box from being utterly pre-empted, while allowing other properties to be modified. Use the CustomContextMenuItems or CustomContextMenuArray property to add or remove items to/from the menu. You can add or remove items directly to/from rtb.ContextMenuStrip property's Items collection. However, any "built-in" option that is removed will be immediately re-added. Also, any "custom item" added or removed this way will only have CustomToolStripItemClicked event support added/removed, and be added/removed with respect to the internal CustomContextMenu() array, if the item's Name property is not null. (It no longer has to be already in the array.) "Unnamed" items (such as the "suggestions" that i00SpellCheck includes in the menu when "continuous" spell-check is allowed) can be event-handled directly with the ContextMenuStrip (see below) using AddHandler.

Drag-And-Drop Operations

When an auto-drag-and-drop occurs in the underlying rich-text box (rtb.EnableAutoDragDrop = True), or a regular drop is caught by the text box (rtb.AllowDrop = True), a new property called AutoDragDropInProgress is True for the duration of the DragDrop event of the outer UserControl class--which the programmer can use in the event's procedure to optionally pre-empt any custom drop operations that would occur when the event is raised for any other reason. If the programmer employs custom logic but does not want it to be followed by the rich-text box's automatic drop operation, the programmer can set e.Effects to DragDropEffect.None in the procedure (Don't forget to do this!). Any DragEnter, DragOver, DragLeave, and DragOp events caught by the underlying text box invoke the corresponding events in the UserControl class--but custom links are only protected during the first 3 aforementioned events when they are fired independently of the inner rich-text box (that is, drop occurs on a toolbar) or no buttons are pressed at the time (Note this!). Links are always protected for the DragDrop event--which is where any changes to the text would normally be made. Finally, any attempt to drop information on top of a custom link or selected text containing such links will be canceled; also, no keystrokes (except for modifier keys such as [Ctrl]) are recognized when a mouse button is down.

Customizing Context Menu and Toolstrip

It is now possible to add extra options to the control's context menu and toolstrip in order to implement functionality specific to a particular application's editor. The CustomContextMenuItems and CustomToolStripItems properties can be assigned to ToolStripItemCollection objects that feature additional ToolstripItems. Alternatively, the CustomCoxtextMenuArray and CustomToolStripMenuArray properties, respectively, can take arrays of ToolStripItems. The control catches the Click event for any assigned items, as well as any child or descendant items of them, as the CustomToolStripItemClicked event. This allows additional editing options to be implemented with the appearance of being "built-in" to the editor control! (For an example of handling events with nested drop-down ToolstripItems in general, see my article, Handling Menus and Toolbars Using MDICommandSupport Class Library.)

To manipulate the properties and methods, and other events, of the ToolstripItems, refer to the items directly in your host program. (Note that directly-handled events, unlike this control's CustomToolStripItemClicked event, don't set the protection status of any custom links, so be sure to check the AreLinksProtected property before making arbitrary edits within such other events.)

Theoretically, you can also add or remove custom (not built-in) items directly to the control's ContextMenuStrip property's Items collection, and the control will add/remove CustomToolStripItemClicked event support and add/remove the items to/from the CustomContextMenu() internal array--however, using the CustomContextMenuItems or CustomContextMenuArray property is "safer" (in terms of guaranteeing the desired results); besides, the toolbar can only be modified using CustomToolStripItems/CustomToolStripItems property, so using that latter approach makes for more consistent code. Make sure the Name property of each ToolStripItem is not null!

Using the Code

Properties for RichTextBoxEx

* Properties marked with an asterik are not available at design-time.

  • InferURLScheme -- Boolean (defaults to True) for whether or not the scheme should be inferred when the user or the AddCustomLink method inserts a new custom link and the hyperlink doesn't contain one.
  • MaintainSelection -- Boolean (defaults to True) for whether highlighting should be restored to selected text after a replace, hyphenation, spell-check, or other document operation
  • IsTextChanged * --Boolean for whether text has been changed since it was saved, loaded, or last set to False (set to True to assume changes, False to indicate no recent changes). It is similar to the underlying rich-text box's Modified property except that it is set to True, and the ChangesMade event is fired, when the Text property is set (or Rtf's value is changed), and not when "temporary" changes (like trial hyphenations) are made.
  • SetColorWithFont -- Boolean for whether it should be possible to set text from within the Font dialog
  • ShowToolstrip -- Boolean (defaults to True) for whether to show Toolstrip on top of RichTextBox
  • ShowRuler -- Boolean for (defaults to True) whether to show a ruler for setting indents and tabs
  • DoCustomLinks -- Boolean (defaults to False) for whether to use standard "rtb.DetectUrls" technique for handling links (False) or to use custom links (True). When this property is True, keep the underlying rtb.DetectUrls property set to False!
  • KeepHypertextOnRemove -- Boolean (defaults to False) for whether "invisible" hyperlink text is to be exposed and retained along with main "visible" text of custom links when links are removed; only meaningful (and settable to True) when DoCustomLinks is True
  • AllowTabs -- Boolean (defaults to True) for whether the user is able to set tabs for paragraphs (only meaningful if ShowRuler is True)
  • AllowSpellCheck -- Boolean (defaults to True) for whether control should provide for spell-checking
  • AllowDefaultInsertText -- Boolean (defaults to True) for whether control should provide automatic default custom-character insertions along with any user-defined ones
  • AllowDefaultSmartText -- Boolean (defaults to True) for whether control should provide automatic default smart-character replacements along with any user-defined ones
  • AllowLists -- Boolean (defaults to True) for whether control should allow list bulleting (replaces AllowBullets; see Note above)
  • AvailableListStyle -- collection property (type ListStyleCollection, which inherits from Collection(Of Boolean)) to determine whether, when AllowLists is True, various kinds of list leadeers can be set for selected paragraphs by the user. An index of type RTBListStyle is used to specify which list style is to made settable/unsettable by the user. (One can always programmatically set paragraphs to any style using rtb.SetListStyle.) All styles can be enabled/disabled except for AvailableListStyle(RTBListStyle.NoList), which is always True. (The user can always have unlisted paragraphs.) When styles besides RTBListList.NoList are enabled, the user can use Ctrl + Shift + L to cycle through the available stlyes (unavailable ones are skipped over).
  • AllowHyphenation -- Boolean(defaults to True) for whether control should support hyphenation searches
  • AllowPictures -- Boolean (defaults to True) for whether control should allow pictures to be inserted
  • AllowSymbols -- Boolean (defaults to True) for whether control should allow custom symbols to be inserted using Insert Symbol dialog
  • SpellCheckSettings -- i00SpellCheck's SpellCheckSettings instance with settings for the spell-checker. You can set the spell-check options however you want, but SpellCheckSettings.AllowF7 will always be set to False by this property at run-time, as the rich-text box's context menu already uses the F7 function key as a shortcut to invoke the spell-check dialog. (Import i00SpellCheck to use this property in code.) Any changes to this property are only observable, of course, when spell-checking is enabled (that is, when AllowSpellCheck = True).
  • UnitsForRuler -- TextRuler.Units enumeration for whether Inches (default) or Centimeters should be used for the ruler (Import RichTextBoxEx or Import RichTextBoxEx.TextRuler into any code files that use this property!)
  • RightMargin -- maximum printable width (rtb.RightMargin) for text; the ruler display is updated to reflect the change
  • FilePath -- String for directory the file dialog should start with (when saving text, loading text, or inserting pictures)
  • rtb -- constituent RichTextBox control
  • Text -- String for plain-text contents of text box (rtb.Text); IsTextChanged and rtb.Modified are set to True and ChangesMade event is fired always when set
  • ExtendedText -- read-only String for plain-text contents--including any and all "hidden text"--of text box. The same as Text for host programs target at pre-.NET 4.7 platforms.
  • Rtf -- String for RTF contents of text box (rtb.Rtf); IsTextChanged and rtb.Modified are set, and ChangesMade is fired, only if new value is different from existing value. NOTE: If you have "protection" errors when setting this property to text beginning with a custom link, then set Text to "" (empty string) before setting Rtf. (I don't know why this anomaly sometimes exists, much less why the aforementioned technique cures it.)
  • AutoDragDropInProgress * -- Boolean read-only value indicating whether a drop was caught by the underlying rich-text box, either as an auto-drag-drop or as an "allowed" drop; this value can only be True during the UserControl's DragDrop event
  • SkipLinksOnReplace -- Boolean value indicating whether any occurences of search text overlapping custom links are by default to be skipped (True, default value of property) or replaced (False) when doing a Replace operation. (The end user can change this option when invoking the dialog.) This value can only be set when custom links are allowed (DoingCustomLinks is True). When False and an occurence of search text containing links designated for replacement, the text including the contained links in their entirety is replaced with the replacement text. (See below.)
  • IsSpellCheckingContinuous * -- Boolean value (defaults to True) indicating whether all unrecognized words have wrinkly lines under them and the rich-text box's context menu includes spell-check suggestions when the box is right-clicked on an un-highlighted word. This "continuous spell-check" mode is enabled only when AllowSpellCheck is also True. Most recent change: Setting AllowSpellCheck no longer pre-sets this property's value; also, I fixed a bug in it. (See Points of Interest below.)
  • CustomContextMenuItems * and CustomToolstripItems * -- ToolstripItemCollection objeccts to define additional, custom context-menu and toolbar options, respectively, whose functionality is defined by the host program. A collection can be Nothing or empty to indicate no custom features. Any added items to the toolbar appear to the right of the "built-in-feature" items; any added items to the context menu appear below built-in-feature items and above any spell-check suggestions (when continuous spell-check is on). The CustomToolStripItemClicked event fires when a custom item or one of its drop-down descendant items is clicked; other events for custom items can be caught externally by the items themselves.
  • CustomContextMenuArray * and CustomToolstripArray  * -- Toolstrip() arrays to define additional, custom context-menu and toolbar options, respectively, whose functionality is defined by the host program. They are identical to the 2 "xxxItem" properties above, except that an array of type ToolStrip is taken and returned instead of a ToolStripItemCollection. The rules for how they functionally work are the same. The difference is that items in an array don't have to be "owned" by an existing context-menu or toolstrip control.
  • IsCaretOverALink * -- Boolean read-only value indicating whether the caret is inside a custom link when no text is highlighted. This value is always False when text is selected (highlighted), the caret is outside a link, or when custom links aren't allowed--that is, when DoCustomLinks is False.
  • ShowPageBreaks -- Boolean value (defaults to True) indicating whether a dashed line should be displayed between 2 lines of text when they are separated by a hard page-break character. (Soft [unforced] page breaks depend on PrinterSettings when printing document, and therefore are not displayed. See Points of Interest below.)
  • AllowPageAndLineBreaks -- Boolean value (defaults to True) indicating whether end user can insert page-break and (non-paragraph-breaking) line-break characters. If this property is False, the host program can still programmatically insert such characters, and programmatic page-break characters will be shown as broken lines if ShowPageBreaks is True.
  • rtb * (NEW!) -- read-only instance of the underlying RichTextBox constituent control, allowing one to access the control's members.

NOTES:

  1. Also, the overall control's built-in ContextMenu and ContextMenuStrip properties are Shadow-ed and unavailable at design-time in order to prevent context-menu assignment except using the CustomContextMenuItems/CustomContextMenuArray property above. Both are read-only--ContextMenu returns Nothing, and ContextMenuStrip returns rtb.ContextMenuStrip (the menu of the text box).
  2. The AreLinksProtected property is no longer provided, since the ability of a host program to turn off protection of 1 or more links during certain events makes it not particularly meaningful!!!

Methods for RichTextBoxEx

The following methods--the file methods--can be overridden in any class (control) that ultimately derives from RichTextBoxEx.

  • booleanvalue = SaveFile([filename][, format]) -- saves context of rich-text box to a file. If filename is null or omitted, then a file dialog is invoked with FilePath as the initial directory. If format is omitted, then the file's extension determines the format (RTF if extension is "rtf", else plain text). Returns True if text was saved, False if dialog was canceled. Sets IsTextChanged to False if successful. If saved as "plain text" when custom links are present, the links are output as "visibletext" if KeepHypertextOnRemove is False, "{visibletext|hyperlink}" if the property is True.
  • booleanvalue = SaveFile(data, format) -- saves contents of rich-text box to an IO.Streamdata is the stream. The parameters are required here. Everything else is the same as in the file overload of this method. This overload always returns True.
  • booleanvalue = LoadFile([filename][, format]) -- loads the contents of a file into the rich-text box. If filename is null or omitted, then a file dialog is invoked with FilePath as the initial directory. If format is omitted, then the file's extension determines the format (RTF if extension is "rtf", else plain text). Returns True if text was loaded, False if dialog was canceled. Sets IsTextChanged to False if successful.
  • booleanvalue = LoadFile(data, format) -- saves contents of an IO.Stream into the rich-text box; data is the stream. The parameters are required here. Everything else is the same as in the file overload of this method. This overload always returns True.
  • booleanvalue = InsertPicture([filename]) -- inserts a picture from a file into the rich-text box at the caret (in place of any selected text). If filename is null or omitted, then a file dialog is invoked with FilePath as the initial directory. Returns True if picture was inserted, False if dialog was canceled. Sets IsTextChanged to True, and fires ChangesMade event, if successful. Picture is inserted as icon, metafile, or bitmap, depending on whether extension is ".ico", ".wmf", or anything else, respectively.
  • booleanvalue = InsertPicture(image|icon) -- inserts a image (bitmap or metafile) or icon, respectively, into the rich-text box at the caret (in place of any selected text). Everything else is the same as in the file overload of this method. This overload always returns True. If one is inserting an icon, then it is first converted into a bitmap, using the value of rtb.SelectionBackColor as the transparent color, unless that value is Color.Empty (indicating multiple background colors in selection), in which case rtb.BackColor is used.

The following methods concern "custom links", and are only executable when custom links are enabled; if DoCustomLinks is False and one of those methods is executed, an exception is thrown.

  • customlinkinfovalue = CheckForCustomLink([position][, lookingbackwards]) -- checks to see if character position (defaults to current caret) is over a custom link or at its edge (end if lookingbackwards is True, beginning if False [default]). If one is found, then a CustomLinkInfo class instance is returned with the link's start position, (visible) text, and (invisible) hyperlink; otherwise, Nothing is returned.
  • booleanvalue = AddCustomLink(customlinkinfovalue) -- inserts a custom link at the position and with the visible and hyperlink text indicated by a CustomLinkInfo instance. Returns True if successful, False if not. An exception is thrown if CustomLinkInfo value contains invalid information.
  • booleanvalue = RemoveCustomLink(position) -- deletes a custom link which begins at the specified position, replacing it with ordinary text ("{visibletext|hyperlinktext}" if KeepHypertextOnRemove property is True, otherwise just "visibletext"). Returns True if successful, False if not. An exception is thrown if position is an invalid value.
  • customlinkinfoarray() = GetCustomLinks([inselectiononly]) -- returns an array of all custom links (if inselectiononly = False [default]) or those at the caret or in the selected text (if inselectiononly = True), each element a CustomLinkInfo instance with the position and the visible and hyperlink text of link. This is the Values collection of the SortedList of links, converted to an array; use the previous 2 methods to actually add or remove links.
  • SelectAnyLinkUnderCaret-- checks to see if the caret is straddling a custom link without highlighting it, and if so, higlights it.
  • EditWithLinksUnprotected([parameters]) -- temporarily disables protection for all custom links, fires the EditingWithLinksUnprotected event, then re-protects the links. This method is useful when making formatting changes to text which includes links. The parameters Object optionally specifies information (anything) that is to be given to the event's procedure; it is passed by reference so that it can be modified by the event procedure. (NOTE: If an unhandled exception occurs during the event, the links are still re-protected and any modifications to parameters are still passed back to the host program. Also, the changes made to the events below may render this method unnecessary.)

NOTE: The ToolStripItemList method is no longer supported, but this new method takes its place:

  • toolstripitems() = GetToolStripItemArray(toolstripitemcollection) -- takes a ToolStripItemCollection object and converts its contents to an array of type ToolStripItem. This is useful if you want to alter the collection list from a menu or toolbar control (without affecting the contents of the control itself) by adding, removing, and/or modifying items before adding the list to this control, via CustomContextMenuArray or CustomToolStripArray, respectively.

Events for RichTextBoxEx

InsertRtfText -- allows host program to define custom (RTF) characters or functionality for various keystrokes. This event should only be used to specify text for insertion to the caret/selection, not for making other changes--since whether link-protection is on or off upon invocation, and whether protection can be turned on and off during the host app's event procedure, depends on the internal state of the control at the time.

INPUT:

  • e.KeyEventArgs -- information about keys being pressed (from "rtb"'s underlying KeyDown event).
  • OUTPUT:
    • e.RtfText -- custom text to be inserted (or replaced over selected text); format is RTF so that program can add functions and characters that are supported by the text box only through RTF codes (as opposed to properties and methods); null to allow processing for pre-defined default keystrokes;
    • e.KeyEventArgs.SuppressKeyPress -- set to True to prevent any action or text insertion, even from pre-defined default keystrokes.

SmartRtfText -- allows host program to define smart (RTF) character-replacements for raw "plain characters" as they are typed in. This event should only be used to specify text for insertion to the caret/selection, not for making other changes--since whether link-protection is on or off upon invocation, and whether protection can be turned on and off during the host app's event procedure, depends on the internal state of the control at the time.

  • INPUT:
    • e.KeyPressEventArgs -- information about keys being pressed (from "rtb"'s underlying KeyPress event).
  • OUTPUT:
    • e.RtfText -- custom text to be take the place of the incoming character, and possibly immediately preceding characters before the caret; format is RTF so that program can add functions and characters that are supported by the text box only through RTF codes (as opposed to properties and methods);
    • e.PrecedingCharacterCount -- number of existing characters, preceding the incoming character, which are to be removed before adding smart character(s).

TextChanged (overridden built-in event) -- fires when any change occurs in the rich-text box. It blithefully fires whenever the underlying rtb_TextChanged event fires, without regard to whether changes are internally transient or compounded. Use this event only when you need to immediately react to any change in the rich-text box whatsoever. This event should only be used to update other aspects of an application, not the document itself--since whether link-protection is on or off upon invocation, and whether protection can be turned on and off during the host app's event procedure, depends on the internal state of the control at the time.

ChangesMade -- fires when "significant" changes are made to contents of rich-text box; it differs from TextChanged in that it doesn't fire when "temporary" changes--like when the control does trial hyphenation in order to figure where hyphens can be inserted--take place; when "compound" changes are made--like those involved in inserting or deleting a link, or updating links in general--it fires only once, after the final or most important change. The IsTextChanged property will always be True when this event fires. Links are always protected upon invoking, and protection of any of them can be turned on and off during the host app's event procedure.

TextProtected -- fires when user or host program attempts to modify any protected text, be it custom links or text protected by the host program for other purposes; it differs from rtb_Protected in that it gives the host program the option to allow or suppress a pre-defined error message's occurence after the event is handled. Links are always protected upon invoking, and protection of any of them can be turned on and off during the host app's event procedure.

  • OUTPUT:
    • e.Cancel -- True to skip standard error warning upon exiting event procedure; False to allow it.

HyperlinkClicked -- fires when user clicks on a link, be it a standard "DetectUrls" link or a custom link; it differs from rtb_LinkClicked in that the host program receives a CustomLinkInfo instance with information on the position, (visible) text, and (invisible) hyperlink of the link. (NOTE: When custom links are enabled, the underlying rtb_LinkClicked event, if caught, returns "{visibletext|hyperlinktext}" in e.LinkText.) Links (custom-links) are always protected upon invoking, and protection of any of them can be turned on and off during the host app's event procedure.

  • INPUT:
    • e.CustomLinkInfo -- information on position, visible text, and hyperlink text if custom links are enabled; if not in custom-link mode--or if link is not of my pre-defined format for custom links--then position is -1, and visible/hyperlink text are both the same.

EditingWithLinksUnprotected -- allows host program to edit a region of selected text when it contains custom links. The protection for all links is tempoorarily disabled upon entrance to the event, and restored upon exit. Links are always un-protected upon invoking, and protection of any of them can be turned on and off during the host app's event procedure. (The changes made to the other events may render this one unnecessary.)

  • INPUT:
    • e.Parameters -- Object with information (anything) being passed to the event procedure from the host code through the EditWithLinksUnprotected method.
  • OUTPUT:
    • e.Parameters -- Object with information (anything) being passed from the event procedure back to the host code through the EditWithLinksUnprotected method; information is successfully passed back even if an unhandled exception occurs in the event procedure.

CustomToolStripItemClicked -- fires whenever the user clicks on a custom Toolstrip item that has been added by the host program. Links are always protected upon invoking, and protection of them can be turned on  and off during the host app's event procedure. Links are always protected upon invoking, and protection of any of them can be turned on and off during the host app's event procedure.

  • INPUT:
    • e.ClickedItem -- ToolstripItem which was clicked

DragDrop (overridden built-in event) -- fires whenever something is dropped on the control. Links are always kept protected throughout the duration of this event, ecept while the host app's event procedure is (directly or indirectly) is executing the EditWithLinksUnprotected method (and its associated EditingWithLinksUnprotected event).

EnableOrDisableCustomOptions -- fires whenever the control needs to determine which context-menu and toolbar options to enable/disable, in order to allow programmer to enable and/or disable any custom options which have been added to the context menu (using CustomContextMenuItems / CustomContextMenuArray) and/or toolbar (using CustomToolstripItems / CustomToolstripArray). Fires after the standard options have been enabled/disabled but before any check is made to see which options should always be disabled when the text box is marked read-only. To set a user-supplied option to be enable-able even when the text box is read-only, be sure that the Tag.ToString value of the ToolstripItem / ToolstripMenuItem for the custom option in question ends with the text "~aoro" (short for "allowed on read-only)!

NOTES:

  1. With any other top-level event, as well as any events of the underlying rich-text constituent control rtb (caught using Addhandler) and events of Toolstrip items added to the control's toolbar and context menu (see above and below) , no change is made to custom-link-protection status upon invocation, and the ability to modify that status (without using EditWithLinksUnprotected / EditingWithLinksUnprotected) during its procedure's execution is uncertain (depends on the circumstances which triggered the event). In the case of the a Toolstrip item of an attached toolbar or custom menu being clicked, use CustumToolstripItemClicked insted of the Toolstrip item's native Click event in order to have proper control over link protection.
  2. When links are unprotected, pasting text is safe if text is highlighted (rtb.FullSelectionLength > 0), because the control ensures that if a selection includes links, it includes those links in their entirity--so either they entirely survive or are entirely removed, but aren't modified by the paste. When there is no text highlighted, however, a paste when the caret is over an unprotected link could corrupt it. To avoid this, verify that IsCaretOverALink returns False before pasting!

ListStyleCollection class

This class is used with the AvailableListStyle property to determine what kinds of paragraph list-leaders the user can set during the run of a host program. Its constructor takes an instance of RichTextBoxEx as its first collection, followed optionally by a Boolean array or another instance of ListStyleCollection to specify list-style settings. (By default, all list styles are True for enabled.) The collection is always fixed at 7 elements, with the first one (no list) always being True. The method GetListStyles (or ToArray) can be used to get the current settings as a Boolean array (index can be any valid RTBListStyle value); SetListStyles is used to set the styles to the values of a Boolean array or the settings of another ListStyleCollection instance. The default property is Item, whose index specifies a list style to be enabled (set to True) or disabled (set to False). (Any attempt to set Item(RTBListStyle.NoList) to False is ignored.) The AreAnyAvailable method returns whether any styles beside RTBListStlye.NoList are enabled, and Copy creates and returns a duplicate instance of the current ListStyleCollection object.

Extension Methods for PrintRichTextBox

NOTE: These methods are designed work for a standard RichTextBox, so that they work even if you're not using the RichTextBoxEx control. (When using the RichTextBoxEx control, use its rtb property to reference the underlying RichTextBox. Don't forget to Import RichTextBoxEx or Import RichTextBoxEx.PrintRichTextBox in any code file using the methods! Whenever the UserControl is not included in the project, simply reference and Import PrintRichTextBox to use the stand-alone library with the standard control.)

Extension methods for printout:

  • richtextbox.Print(PrintDocument[, whichpages][, pagelist()]) -- prints text. The whichpages parameter determines whether to print the pages whose 1-based indexes are in pagelist(), print the pages which are not in pagelist(), or print odd, even, or all pages in a range, with the PrinterSettings.PrintRange property of the PrintDocument instance determining the range (current page at caret, pages with selected text, a numerical range of pages, or the entire text).
  • dialogresult = richtextbox.PrintPreview(PrintPreviewDialog[, whichpages][, pagelist()]) -- previews text. What's previewed depends, once again, on whichpages, and on pagelist() or PrinterSettings.PrintRange property of the PrintDocument assigned to the dialog.
  • integerarray() = richtextbox.PageIndexes(PrintDocument) -- returns an array with the start positions within the text box for each successive page. This can be used to determine what text would be printed on what page. The array "page" indexes are 0-based.
  • richtextbox.SetRightMarginToPrinterWidth(PageSettings) -- sets richtextbox.RightMargin property so that text box wraps text at the same horizontal position as the printed page's width (within left and right margins). This method always sets control's WordWrap property to False.
  • richtextbox.SetPrinterWidthToRightMargin(PageSettings) -- sets printed page's right margin so that it wraps text at the same horizontal position as indicated by richtextbox.RightMargin property. This is SetRightMarginToPageWidth in reverse. If the RightMargin property is 0, then no changes are made.

NOTES:

  1. When using either SetRightMarginToPrinterWidth or SetPrinterWidthToRightMargin, you may want to follow it up with a call to the opposing method in order to account for approximation errors and ensure that the line breaks are exactly the same on both the text box and the printed page.
  2. When printing or previewing, the whichpages parameter is a WhichPages enum which determines what to print:
  • PrintSpecifiedPages -- all pages specified in pagelist() array (PrinterSettings.PrintRange is ignored)
  • SkipSpecifiedPages -- all pages not specified in pagelist() array (PrinterSettings.PrintRange is ignored)
  • AllPagesInRange -- all pages in range specified by PrinterSettings.PrintRange (pagelist() is ignored)
  • EvenPagesInRange -- all even-numbered ("left-sided") pages in range specified by PrinterSettings.PrintRange (pagelist() is ignored)
  • OddPagesInRange -- all odd-numbered pages ("right-sided") in range specified by PrinterSettings.PrintRange  (pagelist() is ignored)

Extension methods for tracking scroll bars:

  • scrollposition = richtextbox.GetScrollPosition() -- gets current horizontal and vertical scroll-bar positions in pixels to a System.Drawing.Point structure.
  • richtextbox.SetScrollPosition(scrollposition) -- sets the horizontal and vertical scroll-bars to pixel positions specified in a System.Drawing.Point structure.
  • scrollinfo = richtextbox.GetScrollBarInfo(type[, mask]) -- returns the range, page-size, position, and/or track-position info about a scroll bar to a PrintRichTextBox.ScrollInfo structure. type is a PrintRichTextBox.ScrollBarType enum value specifying Horizontal or Vertical; mask is an optional bit-flag PrintRichTextBox.ScrollBarMask enum value specifying what parameters to retrieve (by default, all 4 items).
  • textwidth = richtextbox.GetMaximumWidth() -- gets maximum horizontal width in pixels for any text. If control's RightMargin property is non-zero, then that is used; otherwise, if WordWrap is True, then control's client-area width is used; otherwise, the width of the widest line in the text is used. It also accounts for whether ShowSelectionMargin is True or False.

Extension methods for making lists:

  • richtextbox.SetListStyle(liststyle[, startingnumber][, numberingsyntax][, indentation]) (syntax and behavior -- makes the selected text in the rich text box a style specified; liststyle is a PrintRichTextBox.RTBListStyle enum value specifying no list, bullets, Arabic numbers, lowercase letters, uppercase letters, lowercase Roman numerals, or uppercase Roman numerals for item headings. startingnumber (defaults to "1", "a", "A", "i", or "I", depending on liststyle) indicates the initial leader value at the beginng of a list with the specified style. numberingsyntax is an RTBNumberingSyntax value that indicates whether a leader is surrounded by paranetheses (i.e., "(1)"), is followed by a right parentheses (i.e., "1)"), is followed by a period (default; i.e., "1."), or is unaccompanied (i.e., "1"). indentation specifies how many pixels the left edge of the paragraph "proper" (after the leader) is indented from the left edge of the leader; by default, the minimum value needed is used.
  • liststyle = richtextbox.GetListStyle([startingnumber][, numberingsyntax][, indentation]) (NEW!) -- retrieves the paragraph list style for the paragraph at the beginning of the selected text in the rich text box as an RTBListStyle enum value. The optional parameters startingnumber, numberingsyntax, and indentation have the same meaning here as they do for SetListStyle;--if included, these paramethers should be variables (!!) as the values for the text box at the beginning of the selection are passed by reference to the host program. NOTE: startingnumber is the value for the first paragraph in the list of the style of first selected paragraph, even if that style of list begins before the selected paragraph. That is, if the selection starts in a paragraph in the middle (rather than the beginning) of a list, so that rich-text box has to go back to an earlier paragraph to find the first one where was the most recent beginning of style noticed at the selection's start, then the enumerated value at that paragraph, rather than value at the first selected paragraph, is returned.

Extension methods for handling alignment and superscript/subscript:

  • richtextbox.SetAlignment(alignment) -- sets the alignment of selected text to an RTBAlginment enum value for left-justified, right-justified, centered, or fully-justified (text flush with both left and right margins). This is identical to setting the SelectionAlignment property except that one can also set for full justification.
  • alignment = richtextbox.GetAlignment() -- retrieves as an RTBAlignment enum value the alignment (left,-justified right-justified, centered, or fully-justified) of the text at the caret; if text is highlighted and alignments are mixed, then it returns the alignment for the first selected paragraph, rather than the value of the SelectionAlignment property (which in such a case would always be the value for left-justification).
  • richtextbox.UseAdvancedTypographyOnText() -- tells rich-text box to use advanced typography when displaying text. This method is useful after setting or loading RTF text that contains fully-justified text, as freshly loaded or set RTF text will display fully-justified text as left-justified by default. The current selection and scroll-position is saved, the advanced-typography option is used to re-format the entire text accordingly, then the selection and scroll position are restored. NOTE: Although one should use this method after using LoadFile or setting Rtf for a regular RichTextBox, it is not necessary to do so when working these methods and properties with the RichTextBoxEx control (unless one is using rtb.LoadFile rather than LoadFile or rtb.Rtf rather than Rtf), as the aforementioned members of the extended control use this method automatically when loading or setting text.  The SetAlignment extension method also sets the text box to use advanced typography.
  • richtextbox.SetScriptStyle(scriptstyle) -- sets the superscript/subscript/normal status of selected text to an RTBScriptStyle enum value. (Any positive value means superscript, any negative value means subscript, and 0 means normal.) The RichTextBoxEx control's superscript/subscript on/off content-menu options now use this method, rather than the SelectionCharOffset property, to set text to true superscript/subscript rather than simply adjusting selected text vertically; for backwards compatibility with documents with script-style set the old way, the "checked" status of the menu options will depend on both the value returned by the GetScriptStyle method (below) and the value of SelectionCharOffset (SelectionCharOffset is looked at if GetScriptStyle returns RTBScriptStyle.Normal).
  • scriptstyle = richtextbox.GetScriptStyle() -- retrieves as an RTBScriptSyle enum value whether the text at the caret is in superscript, subscript, or normal style. If the text is highlighted and the script-style of the selected text is mixed, then the style at the caret (the beginning or end of the selection, depending on which direction the selection is made) is returned.

Extension methods for handling rich-text-format strings:

  • escapedstring = richtextbox.EscapedRtfText(plaintext) -- converts plain-text string into RTF format, escaping any special characters (i.e., "\", "{", "}", or non-ANSI characters).
  • richtextbox.InsertRtf(rtftext[, position]) -- inserts RTF at a given position (current selection by default), incorporating any wrapper RTF needed to make the inserted text "safe". This is better than just setting richtextbox.SelectedRtf to arbitrary text in that it ensures that the editor won't act unpredictably to the insertion.

Extension methods for handling plain-text when host application is targeted at .NET 4.7 or higher (reflection is used to access private RichTextBox members WindowText, curSelStart, and curSelEnd):

  • length = richtextbox.FullSelectionLength -- full length of selected text, including any hidden text. For programs targeted at earlier platforms, this is the same as SelectionLength. (Use that standard property to set the length of a selection.)
  • text = richtextbox.ExtendedText[(selectiononly)] -- complete text, including any hidden text, of current selection (selectiononly = True) or whole document (selectiononly = False or omitted). For programs targeted at earlier platforms, this is the same as SelectedText or Text, respectively. (Use standard properties to set text.)
  • position = richtextbox.ExtendedFind(seachtext[, startpos][, endpos][, options]) -- find seachtext within text-box, with startpos (which defaults to SelectionStart) specifying start-point of search (if endpos is omitted) or start of a search range (if endpos is included to specify end of the range). options (defaults to RichTextBoxFinds.None) is a RichTextBoxFinds enum specifying search direction, matching criteria, and highlight-on-find status. This is identical to the standard Find method except that in programs targeted at .NET 4.7+, it properly accounts for any hidden text.
  • position = richtextbox.ExtendedFind(searchchars()[, startpos][, endpos][, reverse]) -- find first of any character in searchchars(). The other properties behave the same as the search-string overload of this method, except that reverse is True to search backwards, False (default) to search forwards--an option not available with the standard Find method when searching for characters. Like the previous overload, this properly handles any hidden text even when program is targeted at .NET 4.7+.
Extension methods for checking for protected or hidden (invisible) text:
  • booleanvalue = richtextbox.ContainsProtectedText[(selectiononly)] -- returns True if any text within the currect selection (selectiononly = True) or whole document (selectiononly = False or omitted) is protected. This differs from SelectionProtected in that the latter property always applies to a selection, and only returns True if all selected text is protected.
  • booleanvalue = richtextbox.ContainsHiddenText[(selectiononly)] -- returns True if any text within the current selection (selectiononly = True) or whole document (selectiononly = False or omitted) is marked as invisible. (If the "\v" tag isn't found in SelectedRtf/Rtf, then SelectionLength/Text.Length is compared to FullSelectionLength/ExtendedText.Length just in case the .NET platform is 4.7 or higher and the invisible text isn't returned in the RTF.)

Extension methods to handle inserting images into a document:

  • richtextbox.InsertImage(image[, position]) -- inserts an image at a given position (current selection by default) into the document without relying on the clipboard; RTF code is generated to specify the image wrapped in a Windows Metafile for insertion.
  • richtextbox.InsertIcon(icon[, position]) -- inserts an icon at a given position (current selection by default) into the document without relying on the clipboard; icon is converted into a bitmap, then RTF code is generated to specify it wrapped in a Windows Metafile for insertion. The transparent color for the icon is taken as richtextbox.SelectionBackColor, unless it's Color.Empty (indicating multiple background colors in selection), in which case richtextbox.BackColor is used.

Extension method to enable/disable redrawing of any control:

  • control.SetRedrawMode(onoroff) -- turns on (True) or off (False) the automatic repainting of a control (any control, not just RichTextBox here). Turning it off will allow several changes to be made without the user seeing the intermediate effects; turning it on again will force painting of the cumulative effect.

A few code examples are given below:

VB.NET
Imports RichTextBoxEx
Imports RichTextBoxEx.PrintRichTextBox
Imports RichTextBoxEx.TextRulerControl

'   using a property or method

RichTextBoxEx1.AllowSpellCheck = True : RichTextBoxEx1.IsSpellCheckContinuous = False
RichTextBoxEx1.UnitsForRuler = TextRuler.Inches
Dim AutoDrag As Boolean = RichTextBoxEx1.AutoDragDropInProgress
RichTextBoxEx1.CustomContextMenuItems = ContextMenuStrip1.Items
Dim ArrayOfItems() As ToolstripItem = RichTextBoxEx1.GetToolStripItemArray(ToolStrip1.Items)
RichTextBoxEx.CustonToolstripArray = ArrayOfItems
RichTextBoxEx1.SaveFile("My Text.rtf")

RichTextBoxEx1.DoCustomLinks = True
Dim CustomLinkInfo As CustomLinkInfo = _
   New CustomLinkInfo With {.Position = 55, _
      .Text = "Google Maps", .Hyperlink = "http://www.google.com/maps"}
RichTextBox1.AddCustomLink(CustomLinkInfo)

With RichTextBox1
   .AllowLists = True
   .AvailableListStyle(RTBListStyle.Bullets) = False
   .AvailableListStyle(RTBListStyle.Numbers) = True
   .AvailableListStyle(RTBListStyle.LowercaseLetters) = True
   .AvailableListStyle(RTBListStyle.UppercaseLetters) = False
   .AvailableListStyle(RTBListStyle.LowercaseRomanNumerals) = False
   .AvailableListStyle(RTBListSTyle.UppercaseRomanNumerals) = True
End With

'   using extension methods on underlying text box

Dim ScrollPosition As Point = RichTextBoxEx1.rtb.GetScrollPosition()

RichTextBoxEx1.rtb.SetRightMarginToPageWidth(PrintDocument1.DefaultPageSettings)
RichTextBoxEx1.rtb.Print(PrintDocument1)

Dim Pages() As Integer = RichTextBoxEx1.rtb.PageIndexes(PrintDocument1)
RichTextBoxEx1.rtb.Select(Pages(2), Pages(3) - Pages(2)) ' select page (third page)

RichTextBoxEx1.rtb.SetListStyle(RTBListStyle.LowercaseLetters) 'list as a, b, c, ...
Dim StartingNumber, Indentation As Integer, _
   NumberingSyntax As RTBNumberingSyntax
Dim ListStyle As RTBListStyle = _
   RichTextBoxEx1.rtb.GetListStyle(StartingNumber, NumberingSyntax, Indentation)
RichTextBoxEx1.rtb.SetAlignment(RTBAlignment.Full)
RichTextBoxEx1.rtb.SetScriptStyle(RTBScriptStyle.Subscript)

'   catching events

AddHandler RichTextBoxEx1.InsertRtfText, AddressOf RichTextBoxEx1_InsertRtfText
AddHandler RichTextBoxEx1.HyoerlinkClicked, AddressOf RichTextBoxEx1_HyperlinkClicked

AddHandler RichTextBoxEx1.rtb.DoubleClick, _
   AddressOf RTB_DoubleClick ' event for UNDERLYING text box

Shortcut keys are keystroke-defined similar to Word. For instance, Ctrl + X is Cut, Ctrl + I is Italic, Ctrl + F is Find, Ctrl + H is Replace, F3 is Find Next, Shift + F3 is Find Previous, F7 is Spell-Check, etc. Simply click on the control's constituent component, ctmRTB, to see them all. (At run time, the menus can, of course, be invoked by right-clicking the text box. If the ruler is shown, then the user can switch between inches and centimeters by right-clicking the ruler in order to show its menu,)

As for the pre-defined custom characters (when AllowDefaultInsertText is True), here is the list of keystrokes:

Character Key Sequence

Optional (syllable) hyphen
(- ; displays only when breaking
a word at the end of a line)

Ctrl + -

Em dash (—)

Ctrl + Alt + -

En dash (–)

Ctrl + Alt + Shift + -
Ellipsis (…) Ctrl + Alt + .

Left single quote (‘)

Ctrl + Alt + `

Left double quote (“)

Ctrl + Alt + Shift + ~

Right single quote (’)1

Ctrl + Alt + '

Right double quote (”)

Ctrl + Alt + Shift + "

Copyright (©)

Ctrl + Alt + C

Registered trademark (®)

Ctrl + Alt + R

Trademark (™)

Ctrl + Alt + T

Line (non-paragraph)
break2

Shift + Enter
Page break2 Ctrl + Enter

These custom-character-keystrokes (except for hard line and page breaks) can also be used for specifying special search and/or replacement text in the Find/Replace dialog, and now can also be used for visible and hyperlink text in the Insert Link dialog; however, you can't customize their values, add additional custom characters, or specify "special actions" there like you can for the main control.

As for default smart-character conversions (when AllowDefaultSmartText is True):

  1. A regular double quote ("), when typed, is changed to a left double quote (“) if tit occurs at the beginning of a line, or after a space, regular dash, en dash, em dash, tab, or left single quote; otherwise it is changed to a right double quote (”).
  2. A regular single quote ('), when typed, is changed to a left single quote (‘) if it occurs at the beginning of a line, or after a space, regular dash, en dash, em dash, tab, or left double quote; otherwise it is changed to a right single quote (’).
  3. A regular dash (-), when typed immediately after an existing regular dash is replaced (along with its preceding dash) with an em dash (—).
  4. A period (.), when typed immediately after 2 consecutive periods is replaced (along with its 2 preceding periods) with an ellipsis (…).

NOTES:

1Since this is no longer simply Ctrl + ', the end user can now use Ctrl + ' to toggle "smart quotes" manually when AllowDefaultSmartText is False. (Quotes are always "smart" when AllowDefaultSmartText is True.)

2Not supported in Find/Replace or when AllowPageAndLineBreaks is False.

Demo Program

Features include the following:

Character Key Sequence

One-fourth (¼)

Ctrl + 4

One-half (½)

Ctrl + 2

Three-fourths (¾)

Ctrl + 3

Start new page

(The same as Ctrl + Enter)

Ctrl + Shift + Enter

Increase size of selected text by 1 point Ctrl + .
Decrease size of selected text by 1 point Ctrl + ,
Double size of selected text Ctrl + Shift + >
Halve size of selected text Ctrl + Shift + <

The program also converts "1/4", "1/2", "3/4"--when they are typed in--into "¼", "½", and "¾", respectively.

The EditWithLinksUnprotected method and its associated EditingWithLinksUnprotected event is used to allow the user to resize selected text using simple keystrokes (see bottom 4 entries in list above).

The program invokes a Page Setup Dialog at the beginning of the demoprogram to allow page size and margins to be set. It also features check boxes to enable/disable Custom Links and Insert-Picture for the RichTextBoxEx control, and displays the UTF-32 Unicode values for the characters of the RichTextBoxEx's selected text in a (vertically-scrollable) standard TextBox--useful for determining which character codes do what in a RichTextBox--and showing the text box's current scroll position.The program invokes the Print Dialog, followed by Print Preview and Print, when the text box is double-clicked. Also, when exiting the application, the user is given the option to save text if it's been modified.

Note the check boxes: The program also allows you to switch back and forth between standard "DetectUrls" links and custom links. If a link is clicked on, you will be asked if you want to invoke the process indicated by the hyperlink destination you created for it. It also allows you to turn on and off general spell-checking and continuous spell-checking. Finally, it allows you to add or remove the custom context-menu/toolbar items that allow you to Save As (Ctrl + S), Open (load) (Ctrl + O), or Print (Ctrl + P) text. (The latter option, as with double-clicking the editor, invokes PrintDialog, then PrintPreview, then prints. Save As and Open options are top-level items in the toolbar, but are sub-menu items under File Options in the context menu.)

Drag-and-drop: The program also allows drag-and-drop from other rich-text controls/editors and from the (regular) text box displaying the scroll and character information. (Notice that if the source is another rich-text control/editor and the text being dropped is multi-formatted, the formatting is copied over only if the drop occurs on the rich-text box portion of the control; if the drop occurs over a toolbar, it is copied as plain text.)

Points of Interest

  1. The spell-check tool is now designed to skip over hidden and protected text, including custom links. See the "Some issues with text-changing and RECOMMENDED CHANGES" post (by Robert Gustafson) to the i00SpellCheckArticle, and in particular my self-reply post "ADDITIONAL RECOMMENDED CHANGES (for RichTextBox-es)" (and the self-reply to that!), to see what changes I've made to the i00SpellCheck project in order to handle these issues (You only need to look this up if you've downloaded the spell-checker tool separately, from its article rather than this one. In that case--that is, if you make the changes yourself--be sure to build the modified project so that the current ii00SpellCheck.dll incorporates them!) Previously, this program would find all links in the text to be spell-checked, and invoke the spell-check dialog for each non-link region individually--which looks awkward when text includes several links. Now, the entire text or entire selected text is handled with a single invocation of the dialog. The advantage of incorporating the checks for unchangeable text into the spell-check EXE itself is that it also now skips over non-link text that a host program protects for other reasons too. Note that the context menu also doesn't indicate text-replacement options when a word is wholly or partially protected and/or hidden, although the wavy line still appears under "misspelled" such text (i.e., links) when continuous spell-check is enabled. (I could have pre-empted that too, but it slows down the rich-text rendering too much.)
  2. A dashed line is shown for hard page breaks only. To display lines for soft page breaks, one would need to modify the control to have an extra  property to which is assigned a PrinterSettings value, and then use extension method rtb.PageIndexes within the DrawPageBreaks procedure to find the character indexes for all page breaks (all indexes except the first in the array) whenever the text-box is repainted or the PrinterSettings-value property is changed. Mind you, if a document is very long, then looking for and displaying soft page breaks might slow down visual performance! Also, since hard page-break lines should look different than soft ones, for each page index (and the end of the text), check to see if the preceeding character is a form-feed (hard-page-break) character.
  3. The hyphenation tool now recognizes when a word is already hyphenated and finds the beginning of the full word rather than just the part after the most recent hyphen. This, plus the recommended changes to i00SpellChecker, ensures that it's no longer preferable to "dehyphenate" a region of text before updating hyphenation or doing spell-checks. The hyphenation tool will display any pre-existing hyphens in the text box (not in the "broken word" preview), as well as taking them into account when determining if the word can be hyphenated. (When setting a new hyphen, pre-existing hyphens are kept intact; however, it won't let you place a new one immediately before or after an existing one--which would make no sense anyway, because the word in such a case would be already hyphenated there.)
  4. Custom links now have an optional/syllable hyphen character before and after the link information in order to ensure that the selection process properly incorporates the entire link when the host program is targeted at .NET 4.7+. Don't worry about earlier RTF files saved without trailing hyphens; the control will add it automatically (along with any missing leading hyphens) to any links when the file is loaded. It should be noted that the SelectedRtf property of the underlying RichTextBox control sometimes returns different information depending on whether the host's target platform is .NET 4.7 or is earlier. (There are no issues with the Rtf property.)
  5. The Position property of the CustomLinkInfo type now points to the hyphen preceeding a custom link, rather than to the beginning of the link itself. This reduces its character index by 1.
  6. I've Shadow-ed RichTextBoxEx's ContextMenu and ContextMenuStrip properties with read-only versions that are not available at design-time. This prevents the control from having any context menus except those defined for the the underlying controls of the ruler and the rich-text box, the latter whose menu can be augmented using the CustomContextMenuItems/CustomContextMenuArray property. I've also configured the underlying text-box to pre-empt ("ignore") attempts to utterly replace its context menu with a completely different one--as opposed to simply adding additional items via the aforementioned properties--by switching it back when the rtb_ContextMenuChanged or rtb_ContextMenuStripChanged event fires. The control also attempts to synchronize proper support for CustomToolStripItemClicked events when the menu is "directly" altered or when an item with drop-down children is opened, just in case a programmer bypasses the preferred property above.
  7. I fixed a menu-handling bug: I originally wanted to allow this control to have properties to take and return ToolStripItem groups using type List(Of ToolstripItem), but that resulted in "serialization" error when trying to add the control to a host project. I now use ToolStrip() arrrays, using the CustomContextMenuArray and CustomToolStripArray properties, instead. The related ToolStripItemCollection-type properties are retained for backward compatibility, and they simply convert back and forth between the array versions, which are used internally.
  8. I fixed a bug with the IsSpellCheckContinuous property. Previously, if one wanted to start the control with spell-checking allowed but not "continuous"--AllowSpellCheck as True and IsSpellCheckContinuous as False--one needed to either set the latter property False twice in a row when the form loaded, or wait until the form's handle was created to set it; otherwise, continuous spell-check was still enabled even though the property return False. This anomaly no longer occurs. If you simply set IsSpellCheckContinuous as False once in the form's Load event, continuous spell-check will be disabled as desired. (NOTE: Working with i00SpellCheck inside a custom control is tricky, as it requires checking to see if and when the control's container form has a handle--which is not initially guaranteed, especially if the control is loaded with the form. Also, the control has to disable the i00SpellCheck spell-checker on any text box using it before it's Disposed, so that an exception won't be thrown when the control is removed or the project is closed.)
  9. I fixed another bug in which the control had i00SpellCheck turn on continuous spell-check for all text-box related controls on a form, not just this control. Now it only affects instances of this control.
  10. The Find/Replace, Hyphenate, Insert Symbol, Spell-check, and Insert Link dialog boxes are implemented modally, so as to have greater control over any selected text and to simplify my coding. (Hit Cancel [Esc] to exit find or replace dialog, or to exit hyphenation early.) The scope of text subject to search/modification for these and other features (except Insert Link) is the whole text unless a region of text is highlighted, in which case only the selected text is worked with. Replace, Hyphenation, and most other modification feautures restore the highlighting if MaintainSelection is True; Find (and find/replace if the last operation is a simple find) and Spell-check do not. Invoking Find through Ctrl + F displays a "find only" dialog (no replacement options). Finally, Find Next (F3) and Find Previous (Shift + F3) are not limited in scope to highlighted text; it looks through the full text for a subsequent/previous occurence. When the text in the Insert Link dialog box's Hyperlink text box doesn't already start with a scheme or "www.", a context menu is available for the text box from which one can select various leader text to pre-pend to existing textThe Insert Link dialog's hyperlink text box has a context menu whose submenus allow one to append a domain name, pre-pend a scheme and/or insert a host header (NEW!); also, when one leaves the dialog without a scheme at the beginning of the hyperlink text, the dialog offers the chance to pre-pend one that is deemed suitable given the nature of the hypertext.
  11. There is now a ruler-bar for the control, the link to the article explaining it is above in the part about "dependencies".
  12. I fixed the bug which occasionally causes event cascades when setting font face and size using the combo boxes in the toolbar. The key is the SettingFont Boolean, which allows events to guard against redundant setting of font or combo box entries. Consider it a Christmas present (posted December 25, 2018)!
  13. When using custom links, note that while the hidden text of the hyperlink and delimiter characters is not displayed in the text box, it is nevertheless "visible" to its text-handling properties--not just Rtf/rtb.Rtf and rtb.SelectedRtf, but also "plain-text" ExtendedText/rtb.ExtendedText and, if target platform of the host app is below .NET 4.7, rtb.Text and rtb.SelectedText. This affects what functions like Find/Replace and Spell-check will find and try to edit. Any occurences of text that are wholly or partially protected, be it as a custom link or for other purposes (discretion of host program), will be left unmodified, except in the case of links when the option in the Replace dialog to ignore them is unchecked (see point 17).
  14. When in custom-link mode, the editor allows one to create a new link (from scratch or selected text), or to modify/remove an existing one, using the Insert/Edit/Remove toolbar button or context-menu item (Ctrl + K). One can also remove a custom link by pressing the Backspace or Delete key on or at the right or left edge, respectively, of a link (and its preceding hidden hyphen). The context menu also allows one to set whether to keep hypertext in document when links are removed, and to remove all links in some (when selected) or all text (Ctrl + Shift + F9).
  15. The context menu also includes support for the font styles of strikethrough and (through character-offsetting) superscript/subscript. (If you want to use "true" superscript/subscript, use the Win32-driven IsSelectionStyleInEffect and SetSelectionStyle procedures in RichTextBox.vb, adding the necessary constants to facilitate those functions.)
  16. Changes to the Parameters property of ParameterEventArgs in the EditingWithLinksUnprotected event are now passed back to the EditWithLinksUnprotected method and the host program even if an unhandled exception happens during the event's procedure!!!
  17. I have fixed a bug which causes unrecoverable errors to sometimes occur when there are custom links and auto-drag-and-drop takes place within the text box or between it and another text box. The fix disables any custom-link protection whenever a mouse button is down and no key (except possibly a modifier key like [Ctrl]) is pressed, then re-enables it when the mouse button is released, a non-modifier key is pressed, or the underlying text box's rtb_DragDrop event occurs.
  18. Custom-link protection is temporarily off when a non-modifier key is pressed and text is highlighted; this allows pasting and typing over highlighted text with custom links, but not unhighlighted links.
  19. I made a few last-minute additions to make sure that custom-link protection is in place before finding/replacing text, hyphenating/de-hyphenating, and when inserting/removing/modifying links. Also, custom-character-keystrokes are now available for text in the Insert Link dialog as well as the Find/Replace dialog and the main rich-text box.
  20. When custom links are allowed, the end user has the option when using Find/Replace to replace text to skip over links when replacing, or to overwrite links which overlap occurences of search text.  Any link that is overwritten, including text not matching the search text, is overwritten in its entirity--as the control treats custom-links as "one-piece" items. NOTE: If a visible occurence of the search text "straddles" a link--as opposed to being wholly in or out of one--it won't be found, let alone replaced, unless the "hidden text" separating the link from non-link text are specified in the search.
  21. I now use the i00 Spell-Checker. The version supplied here in the ZIP file is slightly modified from the version I downloaded from i00's article--in that a correction is made to a small bug in the spell-check dialog with regards to the "Change All" option, and the ability to suppress auto-addition of the "standard" context menu is provided (good for projects like mine that use non-standard context menus); also, the spell-check is modified so that it won't fail to recognize words broken by hidden (syllable) hyphens. If you already have i00Spell-Checker, then make the following changes to the i00SpellCheck project that I recommend in my, and "Some issues with text-changing and RECOMMENDED CHANGES" post (by Robert Gustafson) to the i00SpellCheckArticle, do the recommended changes (Note: There are several, but they're fairly straightforward. The first point in the post isn't a change to the tool itself, but is useful to know. The other points--numbered 1 through 8--are the changes.)
  22. Since the control, when custom links are enabled, assures that any selected text wholly includes any links straddling the selection boundaries, the user may sometimes be unable to extend a selection backwards ushing Shift + [left-arrow] whenever either no non-link modifications have been mades since the last link insertion/update, or a selection is backed into the beginning of a link. Extending a selection backwards with any other key sequence, or forwards with any key sequence, however, will always work.

About Custom Links

By default, RichTextBoxEx handles links like a standard RichTextBox, detecting links as the user enters URLs. You can now disable this in favor of custom links, where a link with visible text and invisible hyperlink-destination text (which displays in a tooltip when the mouse overs over the link's visible text) is created/modified/destroyed by the user (using the toolbar or context menu) or programmatically. There's a lot of catches, though, when using hidden text. If the user edits the arbitrarily (willy-nilly), the editor can behave strangely, and the control can lose track of the link or improperly handle it when it's clicked. (I have to include the invisible hyperlinks in the document itself in order to ensure that they get copied-and-pasted properly, and can be persisted to a file or database along with the visible text.)

To rectify these issues, I set it up as follows: custom links have the syntax "{visibletext|hyperlinktext}", with all but "visibletext" marked as hidden text. Also, the editor makes sure that every link is preceded and followed by syllable hyphens (not marked as "invisible", but not displaying unless the link causes a link break), with the hyphens and the intervening link being write-protected in order to control the manner in which the user can edit the link. The hyphens ensure the links are never at the very beginning of a paragraph or immediately adjacent to one another--things which can prevent the control from properly keeping track of the links (their contents and whereabouts) as text is edited.

The links are tracked using a SortedList, whose keys are the start-positions of the links and whose values are CustomLinkInfo instances with position, (visible) text, and (invisible) hyperlink info. The list is reconstructed every time text is changed; any occurence of the syntax "{visibletext|hyperlinktext}" that features hidden text in it is considered a custom link. (Such text, and any text already marked as a link, are protected--along with the preceding and following hyphens, which the control guarantees to exist.)

Also, any time text is highlighted, the control expands the selection bounds as needed to make sure that neither the beginning nor end of the selection straddles a link (and its preceding hyphen). This ensures that cut/copy/paste operations don't create/destroy partial, and therefore unreliable, links. Also, when text is not highlighted but the caret is over the invisible text of a link, the caret is moved past the link so that the user doesn't get a protected-text warning when the caret seems not to be over a link.

Custom links, like regular expressions, are complex to explain but relatively simple to actually use.

On final note about this: The RTF used to implement custom links here is obviously very different than the kind used by Word or WordPad. The thing is, the RTF Word uses to handle links doesn't look or behave in a Visual Studio RichTextBox in an even remotely similar way. Ironically, I have to use different RTF code to get my links to look and behave like WordPad/Word links! Don't be surprised if links copied from this control to WordPad/Word, therefore, aren't recognized as links. That's really not such a big deal, as even standard "DetectUrls" non-custom links aren't seen as links by Word (there are by WordPad, though). I suppose every rich-text editor has its own way of handling links.

Another final note: Standard "DetectUrls" typed-in links and custom links are mutually exclusive!! You can't do both types at the same time. (The DoCustomLinks property determines which style is available.)

P.S.

  1. Custom links previously required a (visible) blank space, instead of a hyphen, to precede the link and be protected with it. If you have RTF files saves using this control in its previous incarnation, I recommend opening each RTF file in Notepad, finding any occurence of a "\protect" tag that is not immediately followed by "0", and editing the text that follows each: In each case of protected text that represents a link, replace the blank space (" " or "\ ") just before the first "\v" tag (not a "\v0" tag!) with "\-" (syllable hyphen), and, if you still want a space to show before the link, insert a space before the "\protect" tag that begins the "protected-link" region (not the "\protect0" tag that ends it). (Also, add a trailing "\-" immediately after second "\v0" [which precedes the "\protect0"] in the region, if there isn't already one.)
  2. If you notice getting protection errors when working with RTF text loaded from an old file and trying to apply a change to a selection that spans both link and non-link text, there may be extra hidden (syllable) hyphens before or after the link's "bookending" hyphens but still within the protected region. Open the RTF file in Notepad and remove any surplus "\-" escapes within "protected-link" regions (regions between "\protect" and "\protect0") so that each such region has only 2 "\-" escapes within it--1 at the beginning and 1 at the end.
  3. BTW, you'll notice that the link procedures--and many other procedures in RichTextBoxEx.vb--use special Booleans that conditionally cause event-triggered code to be bypassed. That's on purpose in order to prevent event cascades. There are many constituent controls that interact back and forth with each other, and text- and selection-change events that change the text/selection directly or indirectly--so the code needs to know when processing an event if it's already doing it. Other Booleans prevent cases of endless double-recursion--where procedures could endlessly call each other.
  4. I fixed an error in the i00SpellCheck library that causes a host app to crash when selecting multiple words, using the toolstrip combo boxes to change font name or size, and then clicking back on the rich-text box. If you have the source code for i00SpellCheck, then look at my comment  "Some issues with text-changing and RECOMMENDED CHANGES" post (by Robert Gustafson) to the i00SpellCheck article on the article for the spell-checker solution.

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionAOL Having problems unzipping and zipping Pin
PBorchert31-Jan-22 6:29
PBorchert31-Jan-22 6:29 
AnswerRe: AOL Having problems unzipping and zipping Pin
Robert Gustafson31-Jan-22 17:32
Robert Gustafson31-Jan-22 17:32 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA28-Jan-22 20:51
professionalȘtefan-Mihai MOGA28-Jan-22 20:51 
GeneralRe: My vote of 5 Pin
Infarkt2-Feb-22 11:32
Infarkt2-Feb-22 11:32 
QuestionFind Previous Pin
Emmery Chrisco18-Nov-21 12:36
Emmery Chrisco18-Nov-21 12:36 
AnswerRe: Find Previous Pin
Robert Gustafson19-Nov-21 17:30
Robert Gustafson19-Nov-21 17:30 
PraiseThank you Pin
Emmery Chrisco18-Nov-21 19:45
Emmery Chrisco18-Nov-21 19:45 
GeneralRe: Thank you Pin
Robert Gustafson19-Nov-21 17:34
Robert Gustafson19-Nov-21 17:34 
My article refers you to my post on the "i00SpellCheck" article with the recommended source-code improvements to i00SpellCheck's functionality. I haven't really tested my tool on large documents, so I don't know if i00SpellCheck will slow it down. My enhancements to the spell-checker tool aren't performance-related, however--they're to make the spell-checker act more like Word's spell-checker. BTW, you only need to refer to my post on i00SpellCheck's article if you want to analyze and modify the source code of the spell-checker yourself; the DLL of it that's included in my article already has the aforementioned modifications.
Question[Solved] Error with characters ' and " Pin
N.Enguehard5-Aug-21 11:38
N.Enguehard5-Aug-21 11:38 
AnswerRe: [Solved] Error with characters ' and " Pin
Robert Gustafson5-Aug-21 21:54
Robert Gustafson5-Aug-21 21:54 
QuestionBest richtextbox i have ever found but... Pin
Zulkarneyn Karauce23-Jul-21 22:50
Zulkarneyn Karauce23-Jul-21 22:50 
AnswerRe: Best richtextbox i have ever found but... Pin
Robert Gustafson19-Nov-21 17:37
Robert Gustafson19-Nov-21 17:37 
QuestionInstances of the user control in a tab control Pin
Paul G. Scannell12-Apr-21 7:41
Paul G. Scannell12-Apr-21 7:41 
AnswerRe: Instances of the user control in a tab control Pin
Robert Gustafson5-Aug-21 22:36
Robert Gustafson5-Aug-21 22:36 
Question5 ! Pin
Christ Kennedy4-Mar-21 9:42
mvaChrist Kennedy4-Mar-21 9:42 
AnswerRe: 5 ! Pin
Robert Gustafson19-Nov-21 17:43
Robert Gustafson19-Nov-21 17:43 
QuestionRetrieving formatting from RichTextBoxEx Pin
Paul G. Scannell23-Jan-21 4:47
Paul G. Scannell23-Jan-21 4:47 
AnswerRe: Retrieving formatting from RichTextBoxEx Pin
Robert Gustafson19-Nov-21 17:49
Robert Gustafson19-Nov-21 17:49 
QuestionI have a newer tip for you Pin
Daszbin11-Nov-19 18:13
professionalDaszbin11-Nov-19 18:13 
QuestionCannot edit text when control is placed on a TabControl TabPage Pin
peekster6-Nov-19 11:02
peekster6-Nov-19 11:02 
AnswerRe: Cannot edit text when control is placed on a TabControl TabPage Pin
Robert Gustafson6-Nov-19 18:43
Robert Gustafson6-Nov-19 18:43 
GeneralRe: Cannot edit text when control is placed on a TabControl TabPage Pin
peekster7-Nov-19 6:28
peekster7-Nov-19 6:28 
GeneralRe: Cannot edit text when control is placed on a TabControl TabPage Pin
Robert Gustafson7-Nov-19 19:01
Robert Gustafson7-Nov-19 19:01 
QuestionRequest for Next Version Pin
Jason Bodine6-Nov-19 8:49
Jason Bodine6-Nov-19 8:49 
AnswerRe: Request for Next Version Pin
Robert Gustafson6-Nov-19 18:49
Robert Gustafson6-Nov-19 18:49 

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

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