This control was derived from the TextRuler UserControl of Aleksei Karimov's Advanced Text Editor with Ruler, with the ruler-control's code converted to VB.NET and with numerous modifications and enhancements. This new version is designed to be a general-purpose stand-alone which can then be added/integrated to any rich-text-based UserControl or project. In this article we look at events, methods, and a code sample from the project.
ABOVE: A TextRuler control is placed above host RichTextBox and Label controls.
Last updated: September 9th, 2024
Introduction
This TextRuler
is a UserControl
that can be integrated with any rich-text-box control to allow the user to create, remove, and move margins, indents, and tabs for the rich-text control.
- It allows the programmer to set the effective width of the ruler (not to be confused with the control's width!), as well as what kind of markers (margins, indents, tabs) can be manipulated by the user by clicking/dragging.
- It has properties and events that allow synchronization of margin/indent/tab values of a rich-text control and those of this control.
- The ruler can be configured to measure in inches (tick marks every 1/8 in) or centimeters (tick marks every 5 mm [1/2 cm]).
- It supports tooltips, including default "special" tooltip text for whenever the user positions the mouse over a marker.
- It supports dealing with whether the display should be scrolled in order to make sure that its markers line up with the host rich-text control's margin/indent/tab values.
- It can be scaled by a
ZoomFactor
to synchronize with such enlargement/shrinkage of text in a rich-text control. - The 5 events are now raised by protected "On" methods to facilitate overriding for any derived class (December 28, 2021).
This control was derived from the "TextRuler
" UserControl
of Aleksei Karimov's Advanced Text Editor with Ruler, with the ruler-control's code converted to VB.NET and with numerous modifications and enhancements. This new version is designed to be a general-purpose
"stand-alone" which can then be added/integrated to any
rich-text-based UserControl
or project.
Using the Code
Properties for TextRuler
Properties are as follows:
MarkerUnderMouse / TabUnderMouse -- MarkerUnderMouse
returns a MarkerType
enumeration value indicating what kind of marker the mouse is currently positioned over. If it's a tab, then TabUnderMouse
returns the position in pixels for the tab under the mouse. TabsEnabled
-- Gets or sets whether or not the ruler will display, or allow the user to set, tab stops MaximumTabs
-- Gets or sets maximum allowed tabs. NOTE: On setting, if the value is less than the current number of tabs, then this property is not changed. (Property was present but undocumented before 9/9/2024.) TabPositions
/ TabPositionsInUnits
-- Gets or sets any Integer
/ Single
any array of tab positions in pixels (TabPositions
) or the current Units-
property type (TabPositionsInUnits
). The comparable standard RichTextBox
property to TabPositions
is SelectionTabs
. PixelsPerCentimeter
/ PixelsPerInch
-- Read-only--returns the number of pixels per centimeter/inch for use with making conversions; these values are Single
in order to facilitate accurate conversions for large values. Units
-- Gets or sets a UnitType
enumeration value for the current type of units to display the ruler using; either Inches
(default) or Centimeters
. ToolTipText
-- Gets or sets the control's tooltip String
. If UsingSmartTooltips
is True
, then default descriptive names will display whenever the mouse is over a margin/indent/tab marker. UsingSmartToolTips
-- Gets or sets whether the tooltip text should describe the marker type whenever the mouse is over one. If you want to specify for yourself what kind of tooltips are used for markers, set this value to False
and use the MarkerUnderMouse
property in the event code to determine what the user's over. BorderColor
/ BaseColor
-- Gets or sets the color for the ruler's border or background within the border. RulerWidth
/ PrintableWidth
-- Gets or sets how much horizontal space in pixels--with/without any margins--should be handled by the ruler. The comparable RichTextBox
property to PrintableWidth
is the rich-text control's RightMargin
property (when that property is non-zero). RulerWidth
is PrintableWidth
plus any left and right margin space. (NOTE: These are not the control's Width
-type properties.) ScrollingOffset
-- Gets or sets how many pixels after the start of the ruler to start drawing it and tracking markers on it. This is to allow the ruler markers to line up with the rich-text whenever the text is horizontally scrolled. NoMargins
-- Gets or sets whether the ruler should contain space on the left and/or right that is off-limits to markers being set or dragged to. Setting this value to True
sets the LeftMargin
and RightMargin
properties to zero. (For the basic RichTextBox
control, which doesn't display off-limits space, set this value to True
.) LeftMargin
/ RightMargin
-- Gets or sets how many pixels at the left / right side of the ruler should be off-limits for markers. NoMargins
must be False
for in order to be able to set these properties. (NOTE: The RightMargin
property here measures leftward from the right edge of the ruler, whereas the RightMargin
property of the RichTextBox
measures rightward from the left edge of a line of text.) FirstLineIndent
/ LeftIndent
-- Gets or sets how many pixels after the left margin the first line of a paragraph should start. The difference with these properties is when they are set: Setting FirstLineIndent
changes the position of only the first line of paragraph text while leaving subsequent lines at their current positions (and HangingIndent
is adjusted accordingly), while setting LeftIndent
moves all lines an equal distance left or right (HangingIndent
remains the same). The comparable RichTextBox
property is SelectionIndent
. HangingIndent
-- Gets or sets how many pixels after a paragraph's first-line indent the indentation of subsequent paragraph-lines should be. A negative value makes subsequent lines in a paragraph less indented than the first, a positive value makes them more indented than the first line, and zero lines them all up. The comparable RichTextBox
property is SelectionHangingIndent
. RightIndent
-- Gets or sets how many pixels before the right margin a paragraph line should stop at. The comparable RichTextBox
property is SelectionRightIndent
. - ZoomFactor -- Gets or sets how much to enlarge or shrink the ruler display; corresponds to the
RichTextBox
property of the same name. As with the RichTextBox
, ZoomFactor
only affects the display size of margins, indents, and tabs--the pixel/unit values of the associated properties are what they would be if ZoomFactor
was 1. That is, their positions when set are scaled to the ZoomFactor
for the display, and de-scaled when read from the display. The RulerWidth
/PrintableWidth
properties, like the RichTextBox
's RightMargin
property, are not affected by the scaling, however, so scaling the display up/down will decrease/increase the logical range of the ruler, just as zooming in the RichTextBox
affects how much physical text will fit on a line.
Methods for TextRuler
Methods as follows:
- value =
PixelsToUnits(pixels)
-- Converts a given number of pixels to their equivalent in inches or centimeters, depending on the Units
property's value. - value =
UnitsToPixels(units)
-- Converts a given number of inches or centimeters, depending on the value of the Units
property, to their equivalent in pixels.
Events for TextRuler
Events as follows:
IndentsChanged/ MarginsChanged
-- fire whenever the user changes an indent's or margin's value
INPUT:
MarginOrIdentEventArgs.MarkerType
-- MarkerType
value for the kind of indent or margin was moved
TabAdded / TabRemoved / TabChanged
-- fire whenever the user adds, removes, or moves, respectively, a tab position.
INPUT:
TabEventArgs.OldPosition
-- original position in pixels of tab that was moved or deleted TabEventArgs.NewPosition
-- new position in pixels of tab that was moved or added
NOTE
These events only fire when the user changes a margin/indent/tab--not when the programmer changes these values by code. Also, they fire after a change is already made, and therefore don't directly support cancellation of changes.
Sample Code Snippets
A few code examples are given below:
Imports TextRuler
Private Sub RTBToRuler()
TextRuler1.NoMargins = True : TextRuler1.PrintableWidth = RichTextBox1.RightMargin
TextRuler1.Units = MarkerType.Inches
TextRuler1.ScrollingOffset = TextRuloer1.UnitsToPixels(1/4)
TextRuler1.FirstLineIndent = RichTextBox1.SelectionIndent
TextRuler1.HangingIndent = RichTextBox1.SelectionHangingIndent
TextRuler1.RightIndent = RichTextBox1.SelectionRightIndent
TextRuler1.TabPositions = RichTextBox1.SelectionTabs
TextRuler1.ZoomFactor = RichTextBox1.ZoomFactor
End Sub
Private Sub RulerToRTB()
RichTextBox1.SelectionIndent = TextRuler1.FirstLineIndent
RichTextBox1.SelectionHangingIndent = TextRuler1.HangingIndent
RichTextBox1.SelectionRightIndent = TextRuler1.RightIndent
RichTextBox1.SelectionTabs = TextRuler1.TabPositions
End Sub
Public Sub IndentsChanged(sender As Object, e As MarginOrIndentEventArgs) _
Handles TextRuler1.IndentsChanged
RulerToRTB()
Dim s As String = e.MarkerType.ToString.Replace("_", " ") _
& " changed. Right Indent = " _
& TextRuler1.PixelsToUnits(TextRuler1.RightIndent).ToString _
If TextRuler1.Units = UnitType.Inches Then
s &= " inches"
Else
s &= " centimeters"
End If
Label1.Text = s
End Sub
Demo Program
The sample application shows a rich text box with a ruler above it and a label below it (see top of article); the latter shows the current indent and tab positions in inches or centimeters. (Right-click the ruler to choose between units.) The form's code file includes a Module
inside of which an extension method, GetScrollPosition
is defined for getting the horizontal scroll position whenever text in the text box is scrolled so that the ruler's markers are horizontally synchronized accordingly. (It uses an unmanaged Win32 API, SendMessage
.) Finally, I should note that the program accounts for whether or not the rich-text control shows an 8-pixel "selection margin" left of any text when aligning the ruler to it.
Points Of Interest
- The control will
not
allow the user or the programmer to set margins, indents, and tabs in such a way that indents or tabs are outside the "printable-width" area (that is, out in the margins' space) or the rightmost left indent and the right indent are jammed together. Also, it won't let one create 2 or more tab positions that overlap or move existing tabs out of order (that is, move one tab over and past another). These rules are partly to keep the control from confusing one marker for another when tracking the mouse, and partly for the sake of coding simplicity. To allow for "margin release"--in which tabs or indents can appear in the margins--carefully
study the source code. All information is internally stored in pixels with indents represented in absolute distance from the left/right ends (including the margins) and tabs represented relative to the scrolling offset and zoom factor. (NOTE: No exception is thrown if the user/programmer tries to set a value to something unacceptable; instead the attempted change is simply ignored.) - If you want to allow program code to "cancel" user changes in advance, I recommend creating 5 additional events that fire before the change and use argument classes that have the same properties as
MarginOrIndentEventArgs
and TabEventArgs
, but which inherit from CancelEventArgs
. - I'd updated my own text-editor
UserControl
, EXTENDED Version of Extended Rich Text Box (RichTextBoxEx), to incorporate this ruler as a constituent control.