I have
recently been responsible for refactoring the Code Query Language
query editor in NDepend
to fix some imperfections.
The CQL
query editor implementation is based on a class derived from the System.Windows.Controls.RichTextBox
class. It was the opportunity to learn some tricks that I would
like to share in the current post.
Text Coloring
If you
google how to color the text displayed in a RichTextBox, you’ll certainly end up using the coloring selection
trick, using the RichTextBox method Select() and properties SelectionColor, SelectionBackColor:
foreach (Highlight highlight in listOfHighlights) {
this.Select(highlight.m_PosBegin, highlight.m_Length);
this.SelectionColor = highlight.m_ForeColor;
this.SelectionBackColor = highlight.m_BackColor;
}
Using a search
engine is very misleading here. We end up to the conclusion that this approach
comes with extremely bad performance, even on short text with just dozens of word to color.
A much
better way we found is to use the Rtf /
Rich Text Format
capabilities of the RichTextBox. You
just need to format a rtf string and use this code:
this.Clear();
this.SelectedRtf = rtfString;
I won’t
detail the Rtf format here. The Rtf string for the query above looks like:
{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}}
{\colortbl ;\red0\green128\blue0;\red255\green255\blue255;\red230\green255\blue230;\red0\green0\blue255;\red0\green0\blue0;\red0\green0\blue100;\red255\green255\blue153;}\fs20\cf1\highlight2 // <Name>\cf1\highlight3 Method refactored not 100% covered by tests\cf1\highlight2 </Name>\par
\cf4\highlight2 SELECT\cf5\highlight2 \cf4\highlight2 METHODS\cf5\highlight2 \cf4\highlight2 WHERE\cf5\highlight2 \par
\cf6\highlight2 PercentageCoverage\cf5\highlight2 \cf5\highlight2 <\cf5\highlight2 \cf5\highlight7 100\cf5\highlight2 \cf4\highlight2 AND\cf5\highlight2 \cf6\highlight2 CodeWasChanged\cf5\highlight2 \par
\cf4\highlight2 ORDER\cf5\highlight2 \cf4\highlight2 BY\cf5\highlight2 \cf6\highlight2 PercentageCoverage\cf5\highlight2 \cf4\highlight2 DESC\cf5\highlight2 \cf5\highlight2 ,\cf5\highlight2 \par
\cf6\highlight2 NbLinesOfCodeCovered\cf5\highlight2 \cf5\highlight2 ,\cf5\highlight2 \cf6\highlight2 NbLinesOfCodeNotCovered\cf5\highlight2 \par
\par
\cf1\highlight2 // To run this constraint properly 2 analysis must be compared.\par
\cf1\highlight2 // This can be done in throught the menu: \par
\cf1\highlight2 // Start Page -> Compare 2 versions of a code base\par
\cf5\highlight2 \par
\cf1\highlight2 // To run this constraint properly coverage data must be \par
\cf1\highlight2 // gathered from NCover™ or Visual Studio™ Coverage.\par
\cf1\highlight2 // More information on how to import coverage data here:\par
\cf1\highlight2 // \cf1\highlight2 http://www.ndepend.com/Coverage.aspx\cf1\highlight2 \par
Notice that
using the SelectedRtf property is
also a good way to prevent improper formatted text copy/pasted from Microsoft Word or a Browse for example.
Avoid flickering problem
When you update
the content of your RichTextBox, you’ll
certainly notice some pesky flickering. Hopefully, an efficient
solution to this problem can be found here.
Basically the solution consists in disabling text redrawing by calling some
win32 APIs:
private const int WM_SETREDRAW = 0x000B;
private const int WM_USER = 0x400;
private const int EM_GETEVENTMASK = (WM_USER + 59);
private const int EM_SETEVENTMASK = (WM_USER + 69);
[DllImport("user32", CharSet = CharSet.Auto)]
private extern static IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
IntPtr eventMask = IntPtr.Zero;
try {
// Stop redrawing:
SendMessage(richTextBox1.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
// Stop sending of events:
eventMask = SendMessage(richTextBox1.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
// change colors and stuff in the RichTextBox
}
finally {
// turn on events
SendMessage(richTextBox1.Handle, EM_SETEVENTMASK, 0, eventMask);
// turn on redrawing
SendMessage(richTextBox1.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
}
Testing for ScrollBars’
visibility
After
looking for a way to test if the RichTextBox’s ScrollBars are visible or
not, the only way I found is to infer this information from the delta between this.ClientRectangle and this.Size. This is certainly not the cleanest
way but it is working well in every context I tried:
// Determine scrollbar visibility by comparing this.ClientRectangle and this.Size.
private bool HScrollVisible {
get {
Rectangle clientRectangle = this.ClientRectangle;
Size size = this.Size;
return (size.Height - clientRectangle.Height) >=
SystemInformation.HorizontalScrollBarHeight;
}
}
private bool VScrollVisible {
get {
Rectangle clientRectangle = this.ClientRectangle;
Size size = this.Size;
return (size.Width - clientRectangle.Width) >=
SystemInformation.VerticalScrollBarWidth;
}
}
Get/Set the ScrollBars’
positions
To achieve this I came to the conclusion that it must be done throught the good-old win32.
Being able to get and set the ScrollBars’ positions is especially useful to
avoid some pesky automatic RichTextBox content re-locating I notice in some
circumstances, such as inserting or modifying a long text. Here is the code:
private const int SB_HORZ = 0x0;
private const int SB_VERT = 0x1;
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int SB_THUMBPOSITION = 4;
[DllImport("user32.dll", CharSet=CharSet.Auto)]
private static extern int GetScrollPos(int hWnd, int nBar);
[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
internal int HScrollPos {
private get { return GetScrollPos((int)this.Handle, SB_HORZ); }
set {
SetScrollPos((IntPtr)this.Handle, SB_HORZ, value, true);
PostMessageA((IntPtr)this.Handle, WM_HSCROLL, SB_THUMBPOSITION + 0x10000 * value, 0);
}
}
internal int VScrollPos {
private get { return GetScrollPos((int)this.Handle, SB_VERT); }
set {
SetScrollPos((IntPtr)this.Handle, SB_VERT, value, true);
PostMessageA((IntPtr)this.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * value, 0);
}
}
Url Detection
Something
that I wasn’t aware: if you want to display Urls that can be clicked in
your text box, just set the RichTextBox.DetectUrls
property to true. You can then use the RichTextBox.LinkClicked
event to handle the url click.
Posted
Mon, Jul 7 2008 6:55 PM
by
Patrick Smacchia