Click here to Skip to main content
14,696,499 members
Articles » Desktop Development » Edit Controls » General
Article
Posted 23 Jan 2015

Stats

121.3K views
14.6K downloads
105 bookmarked

EXTENDED Version of Extended Rich Text Box (RichTextBoxEx)

Rate me:
Please Sign up or sign in to vote.
4.95/5 (54 votes)
4 Nov 2019CPOL
I've created an enhanced version of the Extended RichTextBox created by Razi Syed.
This is a custom control for advanced rich-text handling

 

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 8, 2020):

  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"

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, 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 are as follows:

  1. 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.
  2. The "strikethrough" (strike) formatting option now has a toolbar button (with its graphic displayed by its entry in the context menu)
  3. 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.
  4. 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.)
  5. 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.
  6. 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.
  7. 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.
  8. The drag-and-drop capacities of the control are expanded. See below.
  9. It is now possible to use the Find/Replace dialog to overwrite text containing links. See below. 
  10. 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.
  11. A different spell-checker is in use now. (I used to use Paul Welter's "NetSpell - Spell Checker for .NET".)
  12. 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, StrikethroughInsert 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 in order to add aditional functionality through host code.

Notes About Control

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

.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 haven't been able to reliably, let alone transparently, compensate for is RichTextBox's new-found difficulty reliably finding links when there's hidden text. As a result, when the host program is targeted for .NET 4.7 or higher and custom links are used, the final link in a document might not be reliably found and followed when hovered over or clicked on--unless the last link is followed by a substantial amount of non-link text. To play it safe, it's best to target your host application to a pre-.NET 4.7 platform when using custom links, or when one needs to be able to use the "Undo" and "Redo" features; none of the above problems exist when a host app is targeted for the earlier platforms.

The Undo and Redo methods of RichTextBox trigger unhandleable 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!) Finally, 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 above to clipboard-cut or remove the control.

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

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

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.

Making Design-Time Changes to UserControl

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!

Using the Code

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 (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)
  • 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 (NEW!) -- 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 occurrences 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 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 * 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 *(NEW!) -- 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.

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) (NEW!) -- 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) (NEW!) -- 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 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. 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.
  • 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 occurrence 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).  

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!

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 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 methods for handling plain-text when host application is targeted at .NET 4.7 or higher (NEW!) (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 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:

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 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. 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.)
  2. 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.
  3. 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.
  4. 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.
  5. 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.)
  6. 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.
  7. 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 features: 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; 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.
  8. There is now a ruler-bar for the control, the link to the article explaining it is above in the part about "dependencies".
  9. 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)!
  10. 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 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, except in the case of links when the option in the Replace dialog to ignore them is unchecked (see point 17).
  11. 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).
  12. 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.)
  13. 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!!!
  14. 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.
  15. 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.
  16. 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.
  17. 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.
  18. 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 "Some modifications recommended" post (by Robert Gustafson) to the i00SpellCheck article, and 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 4 points, numbered 1 to 4, are the changes.)
  19. 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 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 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 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 "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.

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

 
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 
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 
GeneralRe: Referencing the i00SpellCheck.exe Pin
luka8230-Dec-19 9:34
Memberluka8230-Dec-19 9:34 
GeneralRe: Referencing the i00SpellCheck.exe Pin
Robert Gustafson19-May-20 21:32
MemberRobert Gustafson19-May-20 21:32 
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
Ybeyin17-Oct-19 1:39
MemberYbeyin17-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
mvaChrist 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 

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.