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

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):
- Setting color (foreground and background), font attributes, text alignment
- Finding and replacing search text
- Spell-checking
- Picture-inserting
- Hyphenation/dehyphenation
- Defining custom characters and smart-character conversions
- Saving and loading text
- Printing
- Tracking scroll-bar information
- Keeping track of whether recent changes have been made
- Setting tabs indents and tabs via a ruler
- Text listing (non-nested bulleted, numbered, and lettered lists)
- Symbol-inserting (Unicode characters)
- Optional "custom links", in which links have arbitrary visible text and hidden hypertext (link destination)
- Redraw-mode setting and smart RTF escaping and insertion
- Advanced drag-and-drop
- Wrinkly-line auto-spell-checking (aka "continuous spell-check")
- 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:
- The drag-and-drop capacities of the control are expanded. See below.
- It is now possible to use the Find/Replace dialog to overwrite text containing links.. See below.
- 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.
- A different spell-checker is in use now. (I used to use Paul Welter's "NetSpell - Spell Checker for .NET".)
- 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:
- 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.
- 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. - 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. - A context menu with shortcut keys is assigned to the "
rtb
" control. - 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.) - 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.
- 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).
- 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).
- 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.)
- 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.
- 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.
- Custom links, which display arbitrary visible text and go to invisible hyperlinks, are supported.
- Methods exist for saving and loading text, inserting pictures, and checking for/adding/removing custom links
- A ruler can be displayed to allow the user to set first-line/hanging/right indents and tabs.
- 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.)
- A dialog exists for finding an inserting symbols that can be represented by printable Unicode characters in various fonts.
- This version now uses a more enhanced, nuanced spell-checker. (Be sure to "pre-reference" it before dropping this control onto a form, though.)
- 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 Aleksei Karimov'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 CustomContextMenuItem
s and CustomToolStripItem
s properties can be assigned to ToolStripItemCollection
objects that feature additional ToolstripItem
s. Alternatively, the CustomCoxtextMenuArray
and CustomToolStripMenuArray
properties, respectively, can take arrays of ToolStripItem
s. 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 ToolstripItem
s in general, see my article, Handling Menus and Toolbars Using MDICommandSupport Class Library.)
To manipulate the properties and methods, and other events, of the ToolstripItem
s, 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
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)
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))
RichTextBoxEx1.rtb.SetListStyle(RTBListStyle.LowercaseLetters)
AddHandler RichTextBoxEx1.InsertRtfText, AddressOf RichTextBoxEx1_InsertRtfText
AddHandler RichTextBoxEx1.HyoerlinkClicked, AddressOf RichTextBoxEx1_HyperlinkClicked
AddHandler RichTextBoxEx1.rtb.DoubleClick, _
AddressOf RTB_DoubleClick
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
):
- 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 (”).
- 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 (’).
- 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
- 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. - 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. - 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.) - 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.
- 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.)
- 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. - There is ruler-bar for the control, the link to the article explaining it is above in the part about "dependencies".
- 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)! - 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. - 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).
- 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.) - 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!!! - 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. - 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.
- 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.
- (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.
- 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.vb, Extension.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 ChangesMade
, TextProtected
, HyperlinkClicked
, 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.
- 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!) - 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.