Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Comparable

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

4.88/5 (16 votes)
9 Sep 2024CPOL8 min read 31.4K   1.1K  
A UserControl that allows rich-text applications to have a ruler with support for margins, indents, and tabs
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.

 

Image 1

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.

  1. 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.
  2. It has properties and events that allow synchronization of margin/indent/tab values of a rich-text control and those of this control.
  3. 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]).
  4. It supports tooltips, including default "special" tooltip text for whenever the user positions the mouse over a marker.
  5. 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.
  6. It can be scaled by a ZoomFactor to synchronize with such enlargement/shrinkage of text in a rich-text control.
  7. 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 '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:

VB.NET
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

  1. 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.)
  2. 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.
  3. I'd updated my own text-editor UserControl, EXTENDED Version of Extended Rich Text Box (RichTextBoxEx), to incorporate this ruler as a constituent control.

License

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