ChameleonRichTextBox ClassCleanCode C# Libraries v1.2.03 API
Enhances a OnlineRichTextBox with language-specific syntax highlighting, keyword completion (a modest Intellisense), search-and-replace capability, macro support, plus some additional keyboard formatting controls.
Inheritance Hierarchy

OnlineSystem Object
  OnlineSystem MarshalByRefObject
    OnlineSystem.ComponentModel Component
      OnlineSystem.Windows.Forms Control
        OnlineSystem.Windows.Forms TextBoxBase
          OnlineSystem.Windows.Forms RichTextBox
                CleanCode.ChameleonRichTextBoxControls ChameleonRichTextBox

Namespace: CleanCode.ChameleonRichTextBoxControls
Assembly: CleanCode.ChameleonRichTextBoxControls (in CleanCode.ChameleonRichTextBoxControls.dll) Version: (1.2.03)

public class ChameleonRichTextBox : SyntaxHighlightingTextBox, 
	IDisplayCommands, IResourceUser

OnlineChameleonRichTextBox -- click for full size Language reserved words, comments (both bracketed and end-of-line), variables/parameters (those beginning with a fixed character), and strings are each highlighted in a different color and style based on the selected context. Within a context (see "Context Details" below) you may categorize reserved words into different groupings (keywords, functions, etc.) and set different font properties for each. Besides highlighting, all reserved words are also added to the recognition engine for keyword completion, invoked via Control+space. You may even enhance a user experience with general macro expansion using keyword completion by simply adding multiple word expressions to a reserved word list. By default the list of reserved words is empty; you must create a context by setting the EditorContextName property to an existing context or create your own. (See EditorContextName for the list of pre-defined contexts.) Click on the thumbnail at right for a screenshot of the ChameleonRichTextBox.

This syntax-highlighting control is my second generation, replacing my earlier SyntaxHilightTextBox control. I have published an article discussing the practical uses of the earlier control -- Online.NET Building Blocks: Build a RichTextBox-Based Syntax-Highlighting Editor and IDE -- available on the DevX online magazine. Much of it is still relevant as the bulk of the feature set was migrated over. The motivation for this newer version was three-fold:

  • Creating a new context should require only editing an XML file rather than having to instrument and instantiate a new subclass.
  • Increase performance, since the highlighting process involved unacceptable delays with more than a few thousand characters.
  • Add keyword completion.

In the true spirit of open source development, I created the solution to all of these by building upon the work of a developer known as "kabwla" (Online who built his improvements upon the work of "uri guy" (Online The original "uri guy" control had the fundamentals of my 3 items above, but it had a number of implementation issues that were handsomely addressed by kabwla. That provided a control with only a few major remaining issues: First, it did not properly handle token parsing when a separator character is part of another token. Example: because a comment begins with "/*" then both the virgule and the asterisk could not be parsed unless there were other separators present. So "select @a / max(foo)" would recognize the keyword "max" but "select @a/max(foo)" would not. Second, the command-completion pop-up recognized only up and down arrow keystrokes; it did not recognize page up/down nor mouse actions. Third, it refreshed after every keystroke rather than when the user paused typing, impeding performance. And finally fourth, the control was not usable "out of the box" except at a very basic level. Putting that another way, it has a low-level interface when I wanted a higher level, more conceptual and much easier to use interface. As it was, each user would have to learn the nuances and idiosyncrasies of the low-level details. I spent considerable effort to enhance the interface, leveraging both its feature set and the feature set of my earlier control to yield a control superior to both, as described in this detailed comparison table.

Configuring the User Experience

There are a variety of properties that may be set to tailor the user experience to your needs. Most of these are adjustable by the user at runtime via the control's context menu while a few (those dealing with character limits) are only settable programmatically. I have published an article that provides an in-depth discussion of the design of the context menu system used by ChameleonRichTextBox -- OnlineUsing LINQ to Manage File Resources and Context Menus -- available on the DevX online magazine.

This user control may be configured to syntax-highlight automatically or manually via the EnableHighlighting property. Automatic highlighting works well up to a length of 100,000 or so characters, while performance for manual or on-demand highlighting is still acceptable up to perhaps 500,000 characters. Above that the delay time in refreshing impacts the user experience. There are several properties that work in conjunction with EnableHighlighting The RefreshDelay property specifies the time interval (in milliseconds) that defines a user pause. Defaulting to one-quarter second, as long as there is less than one-quarter second between user keystrokes the highlighting engine will not engage. The DisableAutoHighlightAbove property (default 100,000) lets you fine-tune the character limit for automatic highlighting. When the text is longer than that length auto-highlighting will not engage. The DisableHighlightAbove property (default 500,000) acts similarly to disable even manual highlighting. Finally, the DisableRegexFlourishesAbove property (default 10,000) disables highlighting of numbers (or any other highlighting aspects controlled by regular expression rather than by parsing), because regex highlighting is more processor intensive.

A number of properties deal with case sensitivity and case matching on both auto-highlighting and auto-completion. These are all settable interactively on the context menu attached to the control (i.e. right-click the control to open the context menu). To use these settings effectively it is important to understand the interrelations between recognition and completion.

The first stage is auto-completion, invoked by pressing Control+Space. A list of keywords appears. The contents of the list depends on the location of the cursor. If it immediately follows whitespace then all keywords are listed. If a partial word or just a single character precedes the cursor (e.g. wher) and there is only one possible match, the pop-up list is skipped and the matching word is immediately inserted in your text. If your partial word has multiple possible matches, then the list shows all keywords beginning with the prefix you have entered. This matching is case-insensitive by default: whether you have typed WHER, wher, or wHeR, they are all treated the same. When you then pick one of the candidates from the list it replaces the prefix you entered with the selected item. The case of the auto-completed keyword depends on the rule you have selected via the context menu. By default, the case of the inserted word is exactly what you see in the list of auto-complete candidate words, whether upper or lower case. You may alter this behavior, though, via the Keyword Completion submenu on the context menu. (These choices adjust the CompletionAction property.) If you select Uppercase, the word is converted to uppercase regardless of the case of the defined word and regardless of the case of the prefix you have typed. Similarly if you select Lowercase, it is converted to lowercase. Finally, if you select Match user case the case of the inserted keyword matches the case of the prefix that you typed before invoking auto-completion.

The second stage, syntax highlighting, occurs once a keyword is entered whether by you manually typing it or by the auto-completion facility inserting it. By default, highlighting affects only the color and style--but not the case--of the word. You may alter this behavior, though, via the Keyword submenu on the Highlighting submenu on the context menu. You have options to convert the keyword to Uppercase or to Lowercase. (These choices adjust the HighlightKeywordAction property.) So in the default situation, whether you type select or SELECT it will be colored and styled but the case will not be altered. If you specify that keywords should all be uppercase, then even if you type select it will come out as SELECT when it is colored and styled.

The trap to be aware of is when you use a non-default option on case for both keyword completion and highlighting. The keyword completion action occurs first, then the highlighting action. So if you instruct keyword completion to complete words in lowercase, but also instruct keyword highlighting to highlight words in uppercase, an auto-completed word will always end up in uppercase. Thus, if you type sel or SEL and auto-complete this to SELECT, the word select will be inserted, in lowercase. A fraction of a second later this word will be highlighted by the recognition engine and converted to uppercase. So some combinations are incompatible but all are available so each user may tailor the user experience as he/she sees fit.

There is one more property that affects keyword highlighting. Highlighting matches keywords independent of case by default. So if the context includes the keyword SELECT and you type select, your typed word will be recognized and highlighted but otherwise unchanged. With the CaseSensitive property you can modify this behavior. Setting this property to true requires the case you type to match the case of the keyword in the context definition: SELECT will then only match SELECT and not select.

Types of Highlighting

The following categories of elements may be stylized by font, color, italics, or bold. See the Context Details section below for further details.

Keywords(default: none)
These are reserved words of the language. Keyword highlighting is adjustable via the Highlighting >> Keyword submenu of the context menu. The setting adjusts the HighlightKeywordAction property. You may specify any number of keywords in a context. You may stylize keywords differently by assigning them to different groups because, for keywords, styles are applied at the group level. Examples: select, where, or join in PL/SQL.
Variables(default: none)
Some languages use a type designator prefix on variable names. A variable name consists of a prefix and its immediately adjacent word. For this control, a variable is simply a token that begins with a prefix that you designate. Variable highlighting is adjustable via the Highlighting >> Variable submenu of the context menu. The setting adjusts the HighlightVariableAction property. You may specify any number of variable tokens in a context. Examples: $scalar and @array in Perl; @param or @@sys_info in T-SQL.
Comments(default: none)
Comments come in two varieties, block comments (those delimited with both a starting and an ending token), and end-of-line comments (those with a starting token and an implicit ending token of the end of the same line). You may specify only one pair of tokens for block comments and one token for an end-of-line comment in a context.
Strings(default: ")
A string is any sequence of characters bracketed on either end by a delimiter. You may specify any number of string tokens in a context, but any given string must use the same delimiter on both ends. Examples: 'any text here' or "12341-!@#$!".
You do not specify any tokens to delineate numbers; a number is automatically recognized as any contiguous sequence of digits. Besides the color of the characters, numbers may additionally specify a background color in the context.
Place holders(defaults: _{ and }_ )
A place holder occurs only when you invoke a macro expansion at runtime. Any word (letters, digits, or underscores) bracketed by the place holder tokens will be highlighted as specified in the context. You may then sequence through them with the F4 key. Besides the color of the characters, place holders may additionally specify a background color in the context. Examples: _{field1}_ or _{table name}_.

User Interaction Enhancements

Just as the base OnlineRichTextBox class has built-in support for certain keystrokes (Control+C for copy, Control+V for paste, etc.) the ChameleonRichTextBox adds several more:

  • Comment lines (Control+Shift+C)
    Appends the end-of-line comment mark to the beginning of each line in the selected range.
  • Uncomment lines (Control+Shift+U)
    Removes the end-of-line comment mark from each line in the selected range, if present.
  • Indent lines (Alt+>)
    Shift the current selection to the right by adding a tab (or spaces depending on the ExpandTab property) to the start of each line. If ExpandTab is true, the number of spaces used is determined by TabSize.
  • Remove indent from lines (Alt+<)
    Shift the current selection to the left by removing a tab (or spaces depending on the ExpandTab property) at the start of each line.
  • Increase font size (Control+>)
    Increase the font size of the current selection. When auto-highlighting is enabled, this works only if all text is selected. With auto-highlighting disabled, works on any selection.
  • Decrease font size (Control+<)
    Decrease the font size of the current selection. When auto-highlighting is enabled, this works only if all text is selected. With auto-highlighting disabled, works on any selection.
  • Refresh Highlighting (Control+Shift+H)
    Reapply syntax highlighting on command if automatic highlighting is not enabled.
  • Delete range by line boundaries(Control+Shift+Delete)
    Deletes complete lines touched by the current selection or, if no selection, the current line containing the cursor.
  • Advance to next place holder (F4)
    Once a macro is inserted by keyword completion, use F4 to move directly among the set of highlighted place holders, if any.
  • Display context details (Ctrl+F10)
    Reveals language details loaded from the current context.

Context Details: Recognizing a Language

Each pre-defined context is stored as a resource in the ChameleonRichTextBoxControls project. Upon first use of a context (by assigning to the EditorContextName property), a context file is created by externalizing the embedded resource to a file. The application stores externalized files in a subdirectory of the system-defined Application Data directory, where the subdirectory name is the base name of the currently running application. For example, if your application that uses a ChameleonRichTextBox is named MyProgram.exe and the user's name is "smith," the folder is ...\smith\Application Data\MyProgram. The initial portion of the path depends upon which version of Windows you are running. The file name has the format Context-YourContextName.xml. Important note: Once the file has been externalized in this fashion, it will never be updated by the ChameleonRichTextBox control. That is, if you modify the master library in the ChameleonRichTextBoxControls project and recompile it, then recompile your application which uses the ChameleonRichTextBoxControls DLL, the Context-*.xml files will not be automatically updated. If you want one to be refreshed from the master copy you must delete the existing ones so that ChameleonRichTextBox will again be triggered to create one on first use. In my article OnlineUsing LINQ to Manage File Resources and Context Menus (available on the DevX online magazine) I discuss in detail a technique to accomplish this purge automatically with just a couple lines of code. The file will be purged each time a user installs a new version of your application unless he/she has made the file read-only to preserve some customized changes.

There are two ways to add your own custom context, at compile-time or at run-time. For a compile-time addition you must add a resource file in Visual Studio with the name Context-YourContextName.xml. (When you add this via the resource page of the properties editor it will automatically name the resource as Context_YourContextName.) Set the properties of the resource file so that it is copied to the executable directory upon building. This context file is then automatically available by setting the EditorContextName property to YourContextName.

For a dynamic, run-time context addition you simply define a context with the DefineContext method specifying a valid context file (see next section), then activate the context by assigning to the EditorContextName property.

Context Definition Summary

The complete documentation for the context schema is available Onlinehere. This section contains an informal summary of it.

A context file is an XML file that specifies all tokens to recognize for a language and how to style each. The XML shown below is part grammar and part example, in order to more concisely and clearly describe it. For example, a block comment--a comment with an opening token and a closing token--requires the BlockCommentStartMark token and the BlockCommentEndMark token. Those are shown with the very common /* and */ values assigned. Square brackets ([ and ]) indicate optional attributes. Font style attributes, used in many nodes, are defined following the XML, and referenced in the XML in each location they may be used. Font style attributes include font-family, color, bold, and italic. You do not need to use all of them; use only those you wish to change from its default.

Under the root EditorContext, there is a Delimiters node containing all the details other than reserved words, and a WordsAndPhrases node containing all the keywords. The Delimiters section allows you to define styles for comments, strings, variables, separators, numbers, and place holders (which are just occurrences of 0 through 9). You may have an arbitrary number of string designators by adding a QuoteToken node for each (typically single quote and double quote). Similarly you may have an arbitrary number of variable designators by adding a VariableToken node for each (e.g. @ for parameters and @@ for system variables in SQL Server).

Reserved words may be specified in one or more WordGroup nodes. Use multiple groups if you want to use multiple text styles, e.g. red and bold for functions, blue and italic for command words, etc. The WordGroup node also takes an optional type attribute whose only purpose is to label the group when you display the context details via Control+F10. If you do not provide a type the groups are simply labeled "Group 1", "Group 2", etc.

 <?xml version="1.0" encoding="UTF-8"?>
     xsi:noNamespaceSchemaLocation="ContextDefinition.xsd"  >
    <CommentTokens [font_style_attributes]+ >
    <EndOfLineCommentTokens [font_style_attributes]+ >
        <QuoteToken [font_style_attributes]+ >'</QuoteToken>
        . . .
        <VariableToken [font_style_attributes]+ >@</VariableToken>
        . . .
    <NumberTokens [font_style_attributes w/bgcolor]+ />
    <PlaceholderTokens [font_style_attributes w/bgcolor]+ >
    <WordGroup [type="some_type_name"] [font_style_attributes]+ >
      <Keyword [alias="some_alias_name"] [whiteSpace_attribute] >SELECT</Keyword>
      . . .
    . . .

 font_style_attributes ::=
     font-family=.NET_font_family_name (e.g. "Verdana", "Arial Black", etc.)
     color=.NET_color_name (e.g. "Blue", "Magenta", etc.)
     bold="true" | "false"
     italic="true" | "false" 
     background-color=.NET_color_name (this attribute only allowed where indicated above)

 whiteSpace_attribute ::=
     whiteSpace="preserve" | "replace" | "collapse"

In the grammar shown, a QuoteToken delimits a string 'like this' or "like this". But that is a mere convention; in a more general sense, you may think of a QuoteToken as surrounding something where the starting delimiter and ending delimiter are the same. Similarly, the BlockCommentStartMark and BlockCommentEndMark delimit something--not necessarily a comment--where the starting and ending delimiters differ. And in the same vein, a VariableToken may be used to demarcate something that has a constant prefix and extends to the end of a word.

Macro Expansion

Typically the text of a Keyword node is a single word with no white space, e.g. <Keyword>SELECT</Keyword>. You may, however, also define macros using Keyword nodes. Set the text of a Keyword node to be any string you like, even with embedded returns, tabs, etc. That string will then be added to the command-completion list like any regular keywords and you can then insert a lengthy string with just a few keystrokes. Note that, unlike regular keywords, any multiple-word entries will not honor the text styling for its containing WordGroup. The words are inserted as a group but then parsed individually and highlighting applied. For example, if you have a group containing SELECT and FROM that specifies blue text, and your macro group specifies a phrase SELECT * FROM SYS with green text, the net result will be SELECT and FROM in blue and * and SYS in black.

A Keyword node takes an optional whiteSpace attribute that allows you to control how white space in the context file is processed for that node. This is relevant when you have a multiple word node. The possible values for this attribute are borrowed exactly from the XML Schema definitions:

  • preserve indicates to retain any white space;
  • replace replaces each occurrence of a non-space white space character (tab, return, newline) with an actual space character;
  • collapse (the default), replaces runs of any white space with a single space and trims leading/trailing white space.

The named macro capability allows you to define and populate an arbitrarily complex query or query fragment. Here is an example template defining a macro named SELECT-FULL; each of the place holders are delimited with _{ and }_ brackets. Note that placeholders may contain only letters, digits, or underscores.

<Keyword alias="SELECT-FULL" whiteSpace="preserve">
select _{fields}_
from _{datasource}_
where _{predicate}_
group by _{field}_
having _{aggregate}_
order by _{field}_

With a regular, non-macro <Keyword> entry the text of the element displays in the keyword completion list when you invoke Ctrl+Space. With a macro entry, on the other hand, the alias attribute is the text displayed in the keyword completion list. (Also, for the user's convenience, a macro entry appears a second time in the keyword completion list. The second instance has the alias attribute prefixed with the value of the MacroPrefixForAutoCompleteList property (default "MACRO: "). That lets the user see all macros together by typing "MAC" then Control-Space.) Both behave the same upon selecting an item from the keyword completion list: the text of the <Keyword> entry is added to the editor pane.

Once the body of the macro is inserted in the editor pane, all of the place holders, if any, will be highlighted according to the style characteristics defined in the current context. You then have two shortcut keys to make it quick and easy to fill out the template. First, F4 advances to the next place holder and selects it, allowing you to just type to replace it. So you do a sequence of F4, typing, F4, typing, etc. to fill in all the placeholders. Second, templates may often have more than you need since it is often quicker to prune than to add. Assuming that the template is designed with place holders on separate lines as in the above example, when you land on a place holder in a line that you do not need, press Ctrl+Shift+Delete to delete the entire line. This shortcut key deletes the selected range but snaps to line boundaries, so even though only the place holder is selected, it will delete the whole line.

Since CleanCode 0.9.26.

See Also