General-purpose RULER Control for Use with RICH-TEXT CONTROLS






4.88/5 (16 votes)
A UserControl that allows rich-text applications to have a ruler with support for margins, indents, and tabs
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 aMarkerType
enumeration value indicating what kind of marker the mouse is currently positioned over. If it's a tab, thenTabUnderMouse
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 stopsMaximumTabs
-- 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 anyInteger
/Single
any array of tab positions in pixels (TabPositions
) or the currentUnits-
property type (TabPositionsInUnits
). The comparable standardRichTextBox
property toTabPositions
isSelectionTabs
.PixelsPerCentimeter
/PixelsPerInch
-- Read-only--returns the number of pixels per centimeter/inch for use with making conversions; these values areSingle
in order to facilitate accurate conversions for large values.Units
-- Gets or sets aUnitType
enumeration value for the current type of units to display the ruler using; eitherInches
(default) orCentimeters
.ToolTipText
-- Gets or sets the control's tooltipString
. IfUsingSmartTooltips
isTrue
, 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 toFalse
and use theMarkerUnderMouse
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 comparableRichTextBox
property toPrintableWidth
is the rich-text control'sRightMargin
property (when that property is non-zero).RulerWidth
isPrintableWidth
plus any left and right margin space. (NOTE: These are not the control'sWidth
-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 toTrue
sets theLeftMargin
andRightMargin
properties to zero. (For the basicRichTextBox
control, which doesn't display off-limits space, set this value toTrue
.)LeftMargin
/RightMargin
-- Gets or sets how many pixels at the left / right side of the ruler should be off-limits for markers.NoMargins
must beFalse
for in order to be able to set these properties. (NOTE: TheRightMargin
property here measures leftward from the right edge of the ruler, whereas theRightMargin
property of theRichTextBox
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: SettingFirstLineIndent
changes the position of only the first line of paragraph text while leaving subsequent lines at their current positions (andHangingIndent
is adjusted accordingly), while settingLeftIndent
moves all lines an equal distance left or right (HangingIndent
remains the same). The comparableRichTextBox
property isSelectionIndent
.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 comparableRichTextBox
property isSelectionHangingIndent
.RightIndent
-- Gets or sets how many pixels before the right margin a paragraph line should stop at. The comparableRichTextBox
property isSelectionRightIndent
.- ZoomFactor -- Gets or sets how much to enlarge or shrink the ruler display; corresponds to the
RichTextBox
property of the same name. As with theRichTextBox
,ZoomFactor
only affects the display size of margins, indents, and tabs--the pixel/unit values of the associated properties are what they would be ifZoomFactor
was 1. That is, their positions when set are scaled to theZoomFactor
for the display, and de-scaled when read from the display. TheRulerWidth
/PrintableWidth
properties, like theRichTextBox
'sRightMargin
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 theRichTextBox
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 theUnits
property's value. - value =
UnitsToPixels(units)
-- Converts a given number of inches or centimeters, depending on the value of theUnits
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 valueINPUT:
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 deletedTabEventArgs.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
' getting info from rich-text box
Private Sub RTBToRuler()
TextRuler1.NoMargins = True : TextRuler1.PrintableWidth = RichTextBox1.RightMargin
TextRuler1.Units = MarkerType.Inches
TextRuler1.ScrollingOffset = TextRuloer1.UnitsToPixels(1/4) 'scroll past first quarter-inch
TextRuler1.FirstLineIndent = RichTextBox1.SelectionIndent
TextRuler1.HangingIndent = RichTextBox1.SelectionHangingIndent
TextRuler1.RightIndent = RichTextBox1.SelectionRightIndent
TextRuler1.TabPositions = RichTextBox1.SelectionTabs
TextRuler1.ZoomFactor = RichTextBox1.ZoomFactor
End Sub
' setting info into rich-text box
Private Sub RulerToRTB()
RichTextBox1.SelectionIndent = TextRuler1.FirstLineIndent
RichTextBox1.SelectionHangingIndent = TextRuler1.HangingIndent
RichTextBox1.SelectionRightIndent = TextRuler1.RightIndent
RichTextBox1.SelectionTabs = TextRuler1.TabPositions
End Sub
' event
Public Sub IndentsChanged(sender As Object, e As MarginOrIndentEventArgs) _
Handles TextRuler1.IndentsChanged
RulerToRTB()
' display what was changed and current right-indent value in current units
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
andTabEventArgs
, but which inherit fromCancelEventArgs
. - I'd updated my own text-editor
UserControl
, EXTENDED Version of Extended Rich Text Box (RichTextBoxEx), to incorporate this ruler as a constituent control.