Click here to Skip to main content
14,388,795 members

EXTENDED Version of Extended Rich Text Box (RichTextBoxEx)

Rate this:
4.94 (51 votes)
Please Sign up or sign in to vote.
4.94 (51 votes)
4 Nov 2019CPOL
I've created an enhanced version of the Extended RichTextBox created by Razi Syed.

 

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--November 9, 2019):

  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 addition options to the control's context menu and toolstrip

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 version--EXE and dictionary/definition files 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 are as follows:

  1. The drag-and-drop capacities of the control are expanded. See below.
  2. It is now possible to use the Find/Replace dialog to overwrite text containing links.. See below. 
  3. A Find Previous option (Shift + F3) llows one to search outside the Find dialog in the opposite direction to the direction specifed by the last Find dialog invokation.
  4. A different spell-checker is in use now. (I used to use Paul Welter's "NetSpell - Spell Checker for .NET".)
  5. 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, 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 occurrence or all occurrences from the current position in the specified direction. To exit the Find or Replace dialog, hit Cancel (ESC). To find and select subsequent occurrences 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 revious (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 hyphenateable 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 numeraled, 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.

Using the Code

Notes About Control

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. 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 or Import RichTextBoxEx.PrintRichTextBox in any code files using the methods. (To access spell-checker features outside of this control, Import i00SpellCheck.)

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.

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).

Member Changes

If you already are using this control, note that the 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).

Also, 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 (NEW!). Finally, 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.

Underlying Text Box 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.

Making Design-Time Changes

If 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.)

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!

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 nuilt-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!

---NOTE WHEN USING ON TAB-CONTROLS!!!

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 tag 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

Properties for RichTextBoxEx

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

  • 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 for (defaults to False) whether to use standard "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)
  • 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 (NEW!) -- 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
  • 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
  • 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
  • AreLinkProtected * -- Boolean read-only value indicating whether any custom links are protected (always False when custom links aren't allowed--that is, when DoCustomLinks is False). This can be used to determine whether links are vulnerable to arbitrary edits during events that don't guarantee the links' protection status during them (see below). (If this value is True and one wants to make arbitrary edits, then use the EditWithLinksUnprotected method and EditingWithLinksUnprotected event.)
  • SkipLinksOnReplace -- Boolean value indicating whether any occurrences of search text overlapping custom links are by default to be skipped (True, default) 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 occurrence 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 (NEW!) * and CustomToolstripItems (NEW!) * -- 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 (NEW!) * and CustomToolstripArray (NEW!) * -- 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.

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).

Methods for RichTextBoxEx

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

  • toolstripitems() = GetToolStripItemArray(toolstripitemcollection) (NEW!) -- 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.

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 an RTF-format file. If filename is null or omitted, then a file dialog is invoked with FilePath as the initial directory. If format is -1 or 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.
  • booleanvalue = LoadFile([filename][, format]) -- loads the contents of an RTF-format 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 -1 or 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 = InsertPicture([filename]) -- inserts a picture 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.

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. If a space does not exist at the point of insertion, then one is added before the link. Returns True if successful, False if not. An exception is thrown if customlinkinfovalue 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.
  • 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.)

Events for RichTextBoxEx

Events marked with an asterik ensure that any custom links are protected throughout the duration of the event. If you need to un-protect links temporarily, use the EditWithLinksUnprotected method inside the event and write code that works with the unprotected links in the resulting EditingWithLinksUnprotected event (see below). 

InsertRtfText -- allows host program to define custom (RTF) characters or functionality for various keystrokes. This event does not pre-set the protection status of any custom links.

  • 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 "dumb characters" as they are typed in. This event does not pre-set the protection status of any custom links.

  • 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 newly supported)-- 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 and without auto-protecting links. Use this event only when you need to immediately react to any change in the rich-text box whatsoever.

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.

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 occurrence after the event is handled.

  • 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.)

  • 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 is disabled upon entrance to the event, and restored upon exit--even if the event's procedure throws an unhandled exception.

  • 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 * (NEW!) -- fires whenever the user clicks on a custom Toolstrip item that has been added by the host program.

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

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) -- prints text. The PrinterSettings.PrintRange property of the PrintDocument instance determines whether the current page (at the caret), pages with selected text, a range of pages, or the entire text is printed.
  • dialogresult = richtextbox.PrintPreview(PrintPreviewDialog) -- previews text. What's previewed depends, once again, on the 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.
  • 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.

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

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 method for making lists:

  • richtextbox.SetListStyle(liststyle) -- makes the selected text in the rich 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. This method uses the rich-text box's SelectionBullet property to turn on/off listing, then uses SendKeys to set the specific list type.

Extension methods for handling rich-text-format strings:

  • stringvalue = richtextbox.EscapedRtfText(plaintext) -- converts plain-text string into RTF format, escaping any special characters (i.e., "\", "{", "}", or non-ASCII 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 method to enable/disable redrawing of 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:

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)

'   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, ...

'   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 + -

Left single quote (‘)

Ctrl + `

Left double quote (“)

Ctrl + Shift + ~

Right single quote (’)

Ctrl + '

Right double quote (”)

Ctrl + Shift + "

Copyright (©)

Ctrl + Alt + C

Registered trademark (®)

Ctrl + Alt + R

Trademark (™)

Ctrl + Alt + T

These custom-character-keystrokes 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, 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, 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 (—).

Demo Program

Features include the following:

Character Key Sequence

One-fourth (¼)

Ctrl + 4

One-half (½)

Ctrl + 2

Three-fourths (¾)

Ctrl + 3

Start new page (RTF code: \page ;
displays in text box only as carriage return,
but will affect printout of pages)

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 (NEW!), 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 Print. 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. 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.
  2. 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.
  3. 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 whenever it's Disposed, so that an exception won't be thrown when the control is removed or the project is closed.)
  4. 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.
  5. A word about the hyphenation and spell-checker tools: They regard all optional/syllable hyphens--visible and invisible alike--as word separators. Therefore, if text is already hyphenated, they may confuse part of a word for a whole word. One solution is to dehyphenate the selected text (using the context menu), do the spell-check, then re-hyphenate it (using the button or context menu). (Another is going to the "i00 Spell-Checker" article with the spell-check utility and modifying it to handle hyphenated words [hyphen character: ChrW(173)]. It could also then be designed to "auto-hyphenate" known words.)
  6. 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 mdodification 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 occurrence. New feature: 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 text.
  7. There is ruler-bar for the control, the link to the article explaining it is above in the part about "dependencies".
  8. 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)!
  9. 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" Text/rtb.Text and rtb.SelectedText. This affects what functions like Find/Replace and Spell-check will find and try to edit. Any occurrences 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.
  10. 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).
  11. 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.)
  12. 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!!!
  13. 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.
  14. 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.
  15. 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.
  16. (NEW!) 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 occurrences 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 occurrence 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.
  17. I now use the i00 Spell-Checker. The version here 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.)  If you already have i00Spell-Checker, then make the following changes to its solution's SpellCheckDialog.vbExtension.vb, and extTextBoxContextMenu.vb files, then re-build the i00SpellCheck solution. Subsequently, "Change All" will work properly (replacing all later occurrences of one word with the specified replacement), and one can avoid the auto-insertion of a "standard" menu by calling  control.IncludeOrExcludeStandardMenu(False) before calling control.EnableControlExtensions(). By default, the parameter is True to allow standard menu insertion.)
'   CHANGES TO "SpellCheckDialog.vb" FILE:

Dim OldTextForChangeAll, NewTextForChangeAll As String 'for ChangeAll

Private Sub StartChangeAll()
    btnAdd.Enabled = False
    ChangeingAll = True
    If mt_ChangeAll IsNot Nothing AndAlso mt_ChangeAll.IsAlive Then
        mt_ChangeAll.Abort()
    End If
    Dim SelectedWord = Me.SelectedWord
   NewTextForChangeAll = txtChangeTo.Text
   OldTextForChangeAll = SelectedWord.OrigWord
   If SelectedWord IsNot Nothing Then
        SelectedWord.Selected = False
    End If
    ShowHideSuggestions(False)

    btnChangeAll.Enabled = False
    mt_ChangeAll = New System.Threading.Thread(AddressOf ChangeAll)
    mt_ChangeAll.Name = "Spell Check - Change all"
    mt_ChangeAll.IsBackground = True
    mt_ChangeAll.Start()
End Sub

Private Sub ChangeAll()

    'Dim st = Now

    Dim WordErrors = (From xItem In HtmlSpellCheck1.Words Where (xItem.SpellCheckState = HTMLSpellCheck.SpellCheckDialogWords.SpellCheckStates.Case OrElse xItem.SpellCheckState = HTMLSpellCheck.SpellCheckDialogWords.SpellCheckStates.Error))
    For Each item In WordErrors.ToArray
    If String.Compare(item.NewWord, OldTextForChangeAll, True) = 0 Then
                '   word in question found
                Dim Suggestions = GetSuggestions(item.NewWord)
                If Suggestions.Count > 0 Then
                    If String.IsNullOrEmpty(NewTextForChangeAll) Then
                        'change to first if no text offered
                        item.NewWord = Suggestions.First
                     Else
                        '   change to offered text
                        item.NewWord = NewTextForChangeAll
                    End If
                     item.SpellCheckState = HTMLSpellCheck.SpellCheckDialogWords.SpellCheckStates.OK
                     If SelectedWord() Is item Then
                          HtmlSpellCheck1_SelectionChanged(HtmlSpellCheck1, New HTMLSpellCheck.HTMLWordEventArgs() With {.Word = item})
                     End If
                End If
            End If
    Next
    btnRevertAll.SafeInvoke(Function(x As Button) InlineAssignHelper(x.Enabled, True))

    'complete... unless...
    If HtmlSpellCheck1.mt_SpellCheck IsNot Nothing AndAlso HtmlSpellCheck1.mt_SpellCheck.IsAlive Then
        '... we are still spell checking :(
    Else
        Dim btnChangeAllEnabled = (From xItem In HtmlSpellCheck1.Words Where (xItem.SpellCheckState = HTMLSpellCheck.SpellCheckDialogWords.SpellCheckStates.Case OrElse xItem.SpellCheckState = HTMLSpellCheck.SpellCheckDialogWords.SpellCheckStates.Error)).Count > 0
        ChangeingAll = False
        If btnChangeAllEnabled = True Then
            '...or we have words left that suggestions could not be found for...
            btnChangeAll.SafeInvoke(Function(x As Button) InlineAssignHelper(x.Enabled, btnChangeAllEnabled))
            btnSkip_Click(btnSkip, EventArgs.Empty)
        Else
            CompleteSpellCheck()
        End If
    End If

    'MsgBox(Now.Subtract(st).TotalMilliseconds)
End Sub
'   ADDITION TO "Extension.vb" FILE:

     Friend IncludeStandardItems As Boolean = True
     <System.Runtime.CompilerServices.Extension()> _
     Public Sub IncludeOrExcludeStandardMenu(ByVal sender As Control, _
        Optional ByVal Include As Boolean = True)
     IncludeStandardItems = Include
     End Sub
'   CHANGES TO "extTextBoxContextMenu.vb" FILE:

Public Sub AddStandardItems(ByVal ContextMenuStrip As ContextMenuStrip)
        If Not IncludeStandardItems Then
            Exit Sub
        End If
    RaiseEvent PreAddingMenuItems(Me, EventArgs.Empty)

    If ContextMenuStrip.Items.Count > 0 Then
        ContextMenuStrip.Items.Add(New StandardToolStripSeparator)
    End If

    Undo = New StandardToolStripMenuItem("&Undo", My.Resources.Undo)
    ContextMenuStrip.Items.Add(Undo)

    Dim RichTextBox = TryCast(TextBox, RichTextBox)
    If RichTextBox IsNot Nothing Then
        Redo = New StandardToolStripMenuItem("&Redo", My.Resources.Redo)
        ContextMenuStrip.Items.Add(Redo)
        Redo.Enabled = RichTextBox.CanRedo
    End If

    ContextMenuStrip.Items.Add(New StandardToolStripSeparator)

    If System.Threading.Thread.CurrentThread.GetApartmentState = Threading.ApartmentState.STA Then
        'allow cut/copy/paste - doesn't work unless STA thread
        Cut = New StandardToolStripMenuItem("Cu&t", My.Resources.Cut)
        ContextMenuStrip.Items.Add(Cut)
        Copy = New StandardToolStripMenuItem("&Copy", My.Resources.Copy)
        ContextMenuStrip.Items.Add(Copy)
        Paste = New StandardToolStripMenuItem("&Paste", My.Resources.Paste)
        ContextMenuStrip.Items.Add(Paste)

        Cut.Enabled = TextBox.SelectionLength > 0
        Copy.Enabled = TextBox.SelectionLength > 0
        Paste.Enabled = Clipboard.GetText <> ""
    End If

    Delete = New StandardToolStripMenuItem("&Delete", My.Resources.Delete)
    ContextMenuStrip.Items.Add(Delete)

    ContextMenuStrip.Items.Add(New StandardToolStripSeparator)

    SelectAll = New StandardToolStripMenuItem("Select &All", My.Resources.SelectAll)
    ContextMenuStrip.Items.Add(SelectAll)

    'enable / disable

    Undo.Enabled = TextBox.CanUndo

    Delete.Enabled = TextBox.SelectionLength > 0

    SelectAll.Enabled = TextBox.SelectionLength <> Len(TextBox.Text)

    RaiseEvent PostAddingMenuItems(Me, EventArgs.Empty)

End Sub

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 by a syllable hyphen (not marked as "invisible", but not displaying unless the link causes a link break), with the hyphen and the following link being write-protected in order to control the manner in which the user can edit the link. The prepended hyphen ensures 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 occurrence 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 hyphen, 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.

A note about events: If you allow for custom links, then the events of ChangesMadeTextProtectedHyperlinkClicked, CustomToolStripItemClicked, and DragDrop turn protection on for the life of the event, then restore the previous protection status upon exit--even if there's an unhandled exception during the event. EditingWithLinksUnprotected, on the other hand, turns off the protection for the life of the event, then restores the previous status--once again, even if an unhandled exception occurs. The InsertRtfText, SmartRtfText, and TextChanged events--as well as other events for this control and events for the underlying rich-text box (rtb)--don't worry about the protection status, which depends at any time on whether text is highlighted, if highlighted text contains links, or whether the control was triggered by the UserControl's or host-program's code. Use the AreLinksProtected property to determine whether a potential edit is "safe". 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 occurrence 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 link. (Ignore "\protect0" and "\v0" tags!)
  2. BTW, you'll notice that the link procedures--and many other procedures in RichTextBoxEx.vb--use special Booleans that conditionally cause event-driven 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.

License

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

Share

About the Author

Robert Gustafson
United States United States
No Biography provided

Comments and Discussions

 
QuestionMessage Closed Pin
21-Nov-19 1:22
MemberDerrick Brian21-Nov-19 1:22 
QuestionI have a newer tip for you Pin
Daszbin11-Nov-19 19:13
professionalDaszbin11-Nov-19 19:13 
QuestionCannot edit text when control is placed on a TabControl TabPage Pin
peekster6-Nov-19 12:02
Memberpeekster6-Nov-19 12:02 
AnswerRe: Cannot edit text when control is placed on a TabControl TabPage Pin
Robert Gustafson6-Nov-19 19:43
MemberRobert Gustafson6-Nov-19 19:43 
GeneralRe: Cannot edit text when control is placed on a TabControl TabPage Pin
peekster7-Nov-19 7:28
Memberpeekster7-Nov-19 7:28 
GeneralRe: Cannot edit text when control is placed on a TabControl TabPage Pin
Robert Gustafson7-Nov-19 20:01
MemberRobert Gustafson7-Nov-19 20:01 
You can add that functionality directly to the RichTextBoxEx UserControl's source code--or you can create a new control that Inherits from RichTextBoxEx, and Override the SaveFile and LoadFile methods, incorporating the functionality into your derived class.

You'll have to look for an RTF/HTML converter tool, however. (And if that's a separate DLL library, then remember to pre-reference it before adding the control to any form.)

modified 8-Nov-19 1:08am.

QuestionRequest for Next Version Pin
Jason Bodine6-Nov-19 9:49
MemberJason Bodine6-Nov-19 9:49 
AnswerRe: Request for Next Version Pin
Robert Gustafson6-Nov-19 19:49
MemberRobert Gustafson6-Nov-19 19:49 
GeneralMy vote of 5 Pin
Member 1111764930-Oct-19 10:26
MemberMember 1111764930-Oct-19 10:26 
QuestionReferencing the i00SpellCheck.exe Pin
Paul G. Scannell29-Oct-19 3:39
MemberPaul G. Scannell29-Oct-19 3:39 
AnswerRe: Referencing the i00SpellCheck.exe Pin
Paul G. Scannell29-Oct-19 3:44
MemberPaul G. Scannell29-Oct-19 3:44 
GeneralRe: Referencing the i00SpellCheck.exe Pin
Robert Gustafson6-Nov-19 19:58
MemberRobert Gustafson6-Nov-19 19:58 
SuggestionI have a tip for you Pin
Daszbin26-Oct-19 5:05
professionalDaszbin26-Oct-19 5:05 
GeneralRe: I have a tip for you Pin
Robert Gustafson28-Oct-19 19:06
MemberRobert Gustafson28-Oct-19 19:06 
QuestionHave you thought about adding emoji support Pin
Daszbin23-Oct-19 6:57
professionalDaszbin23-Oct-19 6:57 
SuggestionHave you tried adding some components from i00 spell check Pin
Daszbin17-Oct-19 3:55
professionalDaszbin17-Oct-19 3:55 
GeneralRe: Have you tried adding some components from i00 spell check Pin
Robert Gustafson17-Oct-19 19:19
MemberRobert Gustafson17-Oct-19 19:19 
Questionmissing files Pin
Member 1350313117-Oct-19 1:39
MemberMember 1350313117-Oct-19 1:39 
AnswerRe: missing files Pin
Richard MacCutchan17-Oct-19 1:45
mveRichard MacCutchan17-Oct-19 1:45 
Questioncannot download files Pin
Member 1192669319-Sep-19 15:49
MemberMember 1192669319-Sep-19 15:49 
Questionnice Pin
Christ Kennedy19-Sep-19 13:36
MemberChrist Kennedy19-Sep-19 13:36 
QuestionTextRuler Pin
Phil Sargeant31-Aug-19 22:49
MemberPhil Sargeant31-Aug-19 22:49 
AnswerRe: TextRuler Pin
Robert Gustafson4-Sep-19 18:27
MemberRobert Gustafson4-Sep-19 18:27 
GeneralRe: TextRuler Pin
Phil Sargeant4-Sep-19 20:48
MemberPhil Sargeant4-Sep-19 20:48 
PraiseTextRuler Pin
Phil Sargeant31-Aug-19 14:58
MemberPhil Sargeant31-Aug-19 14:58 

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.

Article
Posted 23 Jan 2015

Stats

106.1K views
12.2K downloads
100 bookmarked