Introduction
Pretty good for us developers that Microsoft .NET 2.0 team has had a pity on us and developed a completely managed RichTextBox
control which is ported with VS 2005. But it seems there is much work left in that control and that we would have to wait for a SP of VS 2005 or .NET Framework 2.0 for the general additional features which we look forward to in a rich text editor.
Some of the general features include:
- LineNumbering and Bulleting in
richtextBox
(can be achieved by ctrl+shift+L keys)
- Print facilities are not at all present and that too printing in a formatted pattern is a pain in the *** (you know where)
- Search function is given, but it is hard to implement and not quite clear for a newbie
May be, guys at Redmond shores have more mercy on us and think a 1000 times before releasing a control which is not complete and instead of decreasing a developers task increases it.
Background
The main use of RichTextBox
control is that it allows us to format text and allows OLE pictures, along with the advantage of handling extra length of text.
Now people, answer a simple question:
IS A TEXT EDITOR COMPLETE WITHOUT PRINTING FACILITIES ?
I know I am being a bit rude but, in the hurry of releasing a control, our friends at Redmond shores must have forgotten this feature (apart from many others).
The height of blunder is that they have forgotten to implement BulletType
and BulletStyle
properties and just have given a single BulletIndent
property which is useless without having bullets inside the control.
I have tried my best to overcome these problems. I am sorry for being sarcastic but these high heads do need some shaking in order to explain to them that their audience is a developer and that their duty is to make his/her life simpler, not hell, by delopoying incomplete controls just because the release deadline is nearing.
Though there is a small drawback of using LineNumbering
from Richtextbox
, it stops numbering at 255, i.e. any lines beyond 255 are numbered as 255.
If any of you guys know how to overcome that, please let me know as well.
Using the Code
The source code is pretty simple unless you directly use the control.
Yeah, you might have to modify the search region if at all because I have used a case insensitive search option.
For printing, you need to place a PrintDocument
conponent on the form and assign the PrintDocument
property of the RichTextBoxEx
with this component.
Without wasting more of your time, let's jump to the control source code.
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing.Printing;
namespace RichTextBoxEx.Controls
{
public class RichTextBoxEx : RichTextBox
{
#region BULLETING
[StructLayout(LayoutKind.Sequential)]
private class PARAFORMAT2
{
public int cbSize;
public int dwMask;
public short wNumbering;
public short wReserved;
public int dxStartIndent;
public int dxRightIndent;
public int dxOffset;
public short wAlignment;
public short cTabCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
public int[] rgxTabs;
public int dySpaceBefore;
public int dySpaceAfter;
public int dyLineSpacing;
public short sStyle;
public byte bLineSpacingRule;
public byte bOutlineLevel;
public short wShadingWeight;
public short wShadingStyle;
public short wNumberingStart;
public short wNumberingStyle;
public short wNumberingTab;
public short wBorderSpace;
public short wBorderWidth;
public short wBorders;
public PARAFORMAT2()
{
this.cbSize = Marshal.SizeOf(typeof(PARAFORMAT2));
}
}
#region PARAFORMAT MASK VALUES
private const uint PFM_STARTINDENT = 0x00000001;
private const uint PFM_RIGHTINDENT = 0x00000002;
private const uint PFM_OFFSET = 0x00000004;
private const uint PFM_ALIGNMENT = 0x00000008;
private const uint PFM_TABSTOPS = 0x00000010;
private const uint PFM_NUMBERING = 0x00000020;
private const uint PFM_OFFSETINDENT = 0x80000000;
private const uint PFM_SPACEBEFORE = 0x00000040;
private const uint PFM_SPACEAFTER = 0x00000080;
private const uint PFM_LINESPACING = 0x00000100;
private const uint PFM_STYLE = 0x00000400;
private const uint PFM_BORDER = 0x00000800;
private const uint PFM_SHADING = 0x00001000;
private const uint PFM_NUMBERINGSTYLE = 0x00002000;
private const uint PFM_NUMBERINGTAB = 0x00004000;
private const uint PFM_NUMBERINGSTART = 0x00008000;
private const uint PFM_RTLPARA = 0x00010000;
private const uint PFM_KEEP = 0x00020000;
private const uint PFM_KEEPNEXT = 0x00040000;
private const uint PFM_PAGEBREAKBEFORE = 0x00080000;
private const uint PFM_NOLINENUMBER = 0x00100000;
private const uint PFM_NOWIDOWCONTROL = 0x00200000;
private const uint PFM_DONOTHYPHEN = 0x00400000;
private const uint PFM_SIDEBYSIDE = 0x00800000;
private const uint PFM_TABLE = 0x40000000;
private const uint PFM_TEXTWRAPPINGBREAK = 0x20000000;
private const uint PFM_TABLEROWDELIMITER = 0x10000000;
private const uint PFM_COLLAPSED = 0x01000000;
private const uint PFM_OUTLINELEVEL = 0x02000000;
private const uint PFM_BOX = 0x04000000;
private const uint PFM_RESERVED2 = 0x08000000;
public enum AdvRichTextBulletType
{
Normal = 1,
Number = 2,
LowerCaseLetter = 3,
UpperCaseLetter = 4,
LowerCaseRoman = 5,
UpperCaseRoman = 6
}
public enum AdvRichTextBulletStyle
{
RightParenthesis = 0x000,
DoubleParenthesis = 0x100,
Period = 0x200,
Plain = 0x300,
NoNumber = 0x400
}
#endregion
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam,
[In, Out, MarshalAs(UnmanagedType.LPStruct)] PARAFORMAT2 lParam);
private AdvRichTextBulletType _BulletType = AdvRichTextBulletType.Number;
private AdvRichTextBulletStyle _BulletStyle = AdvRichTextBulletStyle.Period;
private short _BulletNumberStart = 1;
public AdvRichTextBulletType BulletType
{
get { return _BulletType; }
set
{
_BulletType = value;
NumberedBullet(true);
}
}
public AdvRichTextBulletStyle BulletStyle
{
get { return _BulletStyle; }
set
{
_BulletStyle = value;
NumberedBullet(true);
}
}
public void NumberedBullet(bool TurnOn)
{
PARAFORMAT2 paraformat1 = new PARAFORMAT2();
paraformat1.dwMask = (int)(PFM_NUMBERING | PFM_OFFSET | PFM_NUMBERINGSTART |
PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
if (!TurnOn)
{
paraformat1.wNumbering = 0;
paraformat1.dxOffset = 0;
}
else
{
paraformat1.wNumbering = (short)_BulletType;
paraformat1.dxOffset = this.BulletIndent;
paraformat1.wNumberingStyle = (short)_BulletStyle;
paraformat1.wNumberingStart = _BulletNumberStart;
paraformat1.wNumberingTab = 500;
}
SendMessage(new System.Runtime.InteropServices.HandleRef(this, this.Handle),
0x447, 0, paraformat1);
}
#endregion
#region Printing
private const double anInch = 14.4;
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct CHARRANGE
{
public int cpMin;
public int cpMax;
}
[StructLayout(LayoutKind.Sequential)]
private struct FORMATRANGE
{
public IntPtr hdc;
public IntPtr hdcTarget;
public RECT rc;
public RECT rcPage;
public CHARRANGE chrg;
}
private const int WM_USER = 0x0400;
private const int EM_FORMATRANGE = WM_USER + 57;
[DllImport("USER32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp,
IntPtr lp);
public int Print(int charFrom, int charTo, PrintPageEventArgs e)
{
RECT rectToPrint;
rectToPrint.Top = (int)(e.MarginBounds.Top * anInch);
rectToPrint.Bottom = (int)(e.MarginBounds.Bottom * anInch);
rectToPrint.Left = (int)(e.MarginBounds.Left * anInch);
rectToPrint.Right = (int)(e.MarginBounds.Right * anInch);
RECT rectPage;
rectPage.Top = (int)(e.PageBounds.Top * anInch);
rectPage.Bottom = (int)(e.PageBounds.Bottom * anInch);
rectPage.Left = (int)(e.PageBounds.Left * anInch);
rectPage.Right = (int)(e.PageBounds.Right * anInch);
IntPtr hdc = e.Graphics.GetHdc();
FORMATRANGE fmtRange;
fmtRange.chrg.cpMax = charTo;
fmtRange.chrg.cpMin = charFrom;
fmtRange.hdc = hdc;
fmtRange.hdcTarget = hdc;
fmtRange.rc = rectToPrint;
fmtRange.rcPage = rectPage;
IntPtr res = IntPtr.Zero;
IntPtr wparam = IntPtr.Zero;
wparam = new IntPtr(1);
IntPtr lparam = IntPtr.Zero;
lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lparam, false);
res = SendMessage(Handle, EM_FORMATRANGE, wparam, lparam);
Marshal.FreeCoTaskMem(lparam);
e.Graphics.ReleaseHdc(hdc);
return res.ToInt32();
}
private PrintDocument _PrintDocument;
public PrintDocument PrintDocument
{
get { return _PrintDocument; }
set
{
_PrintDocument = value;
if (_PrintDocument != null)
{
_PrintDocument.BeginPrint += new PrintEventHandler(
_PrintDocument_BeginPrint);
_PrintDocument.PrintPage += new PrintPageEventHandler(
_PrintDocument_PrintPage);
}
}
}
void _PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
checkPrint = this.Print(checkPrint, this.TextLength, e);
if (checkPrint < this.TextLength)
e.HasMorePages = true;
else
e.HasMorePages = false;
}
void _PrintDocument_BeginPrint(object sender, PrintEventArgs e)
{
checkPrint = 0;
}
int checkPrint = 0;
#endregion
#region Search
int SearchIndex = 0;
public void Search(string SearchText)
{
SearchIndex = (SearchIndex >= 0 ? SearchIndex : 0);
if (SearchIndex >= Text.Length)
MessageBox.Show(
"Reached Page End \nUnable to find Specified Text \""
+ SearchText + "\"");
if (this.Text.Length > 0 && !string.IsNullOrEmpty(SearchText))
{
this.Focus();
SearchIndex = this.Find(SearchText, SearchIndex, RichTextBoxFinds.None);
if (SearchIndex == -1)
MessageBox.Show(
"Reached Page End \nUnable to find Specified Text \""
+ SearchText + "\"");
else
SearchIndex += SearchText.Length;
}
}
public void Lock()
{
this.ReadOnly = true;
this.BackColor = SystemColors.Info;
}
public void Unlock()
{
this.ReadOnly = false;
this.BackColor = SystemColors.Window;
}
#endregion
}
}
History
- 22nd May, 2007: Initial version