using System.Diagnostics;
using System.Collections;
using Microsoft.VisualBasic;
using System.Collections.Generic;
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Printing;
using System.ComponentModel;
/// <summary>
/// The rich text box print control class was developed by Microsoft, information about
/// this control can be found in your help files at:
/// ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.KB.v10.en/enu_kbvbnetkb/vbnetkb/811401.htm
/// In general, their intent was to create a rich text box control with print capability
/// embedded into the control.
/// </summary>
/// <remarks>This control class replaces the use of the regular RichTextBox control; the
/// purpose of this extension was specifically to facilitate printing the contents
/// of a rich text box control.</remarks>
internal class ExtendedRichTextBox : RichTextBox
{
#region FORMAT_RANGE_CONSTANTS
private const int SCF_SELECTION = 0x0001;
private const int SCF_WORD = 0x0002;
private const int SCF_DEFAULT = 0x0000; // set the default charformat or paraformat
private const int SCF_ALL = 0x0004; // not valid with SCF_SELECTION or SCF_WORD
private const int SCF_USEUIRULES = 0x0008;
#endregion
#region Windows API
private const int WM_SETREDRAW = 0x0B;
private const int EM_SETEVENTMASK = 0x0431;
private const int EM_SETCHARFORMAT = 0x0444;
private const int EM_GETCHARFORMAT = 0x043A;
private const int EM_GETPARAFORMAT = 0x043D;
private const int EM_SETPARAFORMAT = 0x0447;
private const int EM_SETTYPOGRAPHYOPTIONS = 0x04CA;
private const int CFM_UNDERLINETYPE = 0x800000;
private const int CFM_BACKCOLOR = 0x4000000;
private const int CFE_AUTOBACKCOLOR = 0x4000000;
private const int PFM_ALIGNMENT = 0x08;
private const int TO_ADVANCEDTYPOGRAPHY = 0x01;
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, ref CHARFORMAT2 lParam);
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, ref PARAFORMAT2 lParam);
[DllImport("uxtheme.dll", CharSet = CharSet.Unicode)]
private static extern int SetWindowTheme(
HandleRef hWnd,
[MarshalAs(UnmanagedType.LPWStr)]
string pszSubAppName,
[MarshalAs(UnmanagedType.LPWStr)]
string pszSubIdList);
/// <summary>
/// Contains information about character formatting in a rich edit control.
/// </summary>
/// <remarks><see cref="CHARFORMAT"/> works with all Rich Edit versions.</remarks>
[StructLayout(LayoutKind.Sequential)]
private struct CHARFORMAT
{
public int cbSize;
public uint dwMask;
public uint dwEffects;
public int yHeight;
public int yOffset;
public int crTextColor;
public byte bCharSet;
public byte bPitchAndFamily;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] szFaceName;
}
/// <summary>
/// Contains information about character formatting in a rich edit control.
/// </summary>
/// <remarks><see cref="CHARFORMAT2"/> requires Rich Edit 2.0.</remarks>
[StructLayout(LayoutKind.Sequential)]
private struct CHARFORMAT2
{
public int cbSize;
public uint dwMask;
public uint dwEffects;
public int yHeight;
public int yOffset;
public int crTextColor;
public byte bCharSet;
public byte bPitchAndFamily;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] szFaceName;
public short wWeight;
public short sSpacing;
public int crBackColor;
public int LCID;
public uint dwReserved;
public short sStyle;
public short wKerning;
public byte bUnderlineType;
public byte bAnimation;
public byte bRevAuthor;
}
/// <summary>
/// Contains information about paragraph formatting in a rich edit control.
/// </summary>
/// <remarks><see cref="PARAFORMAT"/> works with all Rich Edit versions.</remarks>
[StructLayout(LayoutKind.Sequential)]
private struct PARAFORMAT
{
public int cbSize;
public uint 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 = 32)]
public int[] rgxTabs;
}
/// <summary>
/// Contains information about paragraph formatting in a rich edit control.
/// </summary>
/// <remarks><see cref="PARAFORMAT2"/> requires Rich Edit 2.0.</remarks>
[StructLayout(LayoutKind.Sequential)]
private struct PARAFORMAT2
{
public int cbSize;
public uint 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 = 32)]
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;
}
#endregion
#region Method: OnHandleCreated
/// <summary>
/// Raises the <see cref="HandleCreated"/> event.
/// </summary>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// Enable support for justification
SendMessage(new HandleRef(this, Handle), EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
}
#endregion
/// <summary>
/// Specifies horizontal alignment for a segment of rich text.
/// </summary>
public enum RichTextAlign
{
/// <summary>
/// The text alignment is unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// The text is aligned to the left.
/// </summary>
Left = 1,
/// <summary>
/// The text is aligned to the right.
/// </summary>
Right = 2,
/// <summary>
/// The text is aligned in the center.
/// </summary>
Center = 3,
/// <summary>
/// The text is justified.
/// </summary>
Justify = 4
}
/// <summary>
/// Specifies the underline styles for a segment of rich text.
/// </summary>
public enum UnderlineStyle
{
/// <summary>
/// No underlining.
/// </summary>
None = 0,
/// <summary>
/// Single-line solid underline.
/// </summary>
Normal = 1,
/// <summary>
/// Single-line underline broken between words.
/// </summary>
Word = 2,
/// <summary>
/// Double-line underline.
/// </summary>
Double = 3,
/// <summary>
/// 'Dotted' pattern underline.
/// </summary>
Dotted = 4,
/// <summary>
/// 'Dash' pattern underline.
/// </summary>
Dash = 5,
/// <summary>
/// 'Dash-dot' pattern underline.
/// </summary>
DashDot = 6,
/// <summary>
/// 'Dash-dot-dot' pattern underline.
/// </summary>
DashDotDot = 7,
/// <summary>
/// Single-line wave style underline.
/// </summary>
Wave = 8,
/// <summary>
/// Single-line solid underline with extra thickness.
/// </summary>
Thick = 9,
/// <summary>
/// Single-line solid underline with less thickness.
/// </summary>
HairLine = 10,
/// <summary>
/// Double-line wave style underline.
/// </summary>
DoubleWave = 11,
/// <summary>
/// Single-line wave style underline with extra thickness.
/// </summary>
HeavyWave = 12,
/// <summary>
/// 'Long Dash' pattern underline.
/// </summary>
LongDash = 13,
/// <summary>
/// 'Dash' pattern underline with extra thickness.
/// </summary>
ThickDash = 14,
/// <summary>
/// 'Dash-dot' pattern underline with extra thickness.
/// </summary>
ThickDashDot = 15,
/// <summary>
/// 'Dash-dot-dot' pattern underline with extra thickness.
/// </summary>
ThickDashDotDot = 16,
/// <summary>
/// 'Dotted' pattern underline with extra thickness.
/// </summary>
ThickDotted = 17,
/// <summary>
/// 'Long Dash' pattern underline with extra thickness.
/// </summary>
ThickLongDash = 18
}
/// <summary>
/// Specifies the color of underline for a segment of rich text.
/// </summary>
public enum UnderlineColor
{
/// <summary>
/// No specific underline color specified.
/// </summary>
None = -1,
/// <summary>
/// Black.
/// </summary>
Black = 0x00,
/// <summary>
/// Blue.
/// </summary>
Blue = 0x10,
/// <summary>
/// Cyan.
/// </summary>
Cyan = 0x20,
/// <summary>
/// LimeGreen.
/// </summary>
LimeGreen = 0x30,
/// <summary>
/// Magenta.
/// </summary>
Magenta = 0x40,
/// <summary>
/// Red.
/// </summary>
Red = 0x50,
/// <summary>
/// Yellow.
/// </summary>
Yellow = 0x60,
/// <summary>
/// White.
/// </summary>
White = 0x70,
/// <summary>
/// DarkBlue.
/// </summary>
DarkBlue = 0x80,
/// <summary>
/// DarkCyan.
/// </summary>
DarkCyan = 0x90,
/// <summary>
/// Green.
/// </summary>
Green = 0xA0,
/// <summary>
/// DarkMagenta.
/// </summary>
DarkMagenta = 0xB0,
/// <summary>
/// Brown.
/// </summary>
Brown = 0xC0,
/// <summary>
/// OliveGreen.
/// </summary>
OliveGreen = 0xD0,
/// <summary>
/// DarkGray.
/// </summary>
DarkGray = 0xE0,
/// <summary>
/// Gray.
/// </summary>
Gray = 0xF0
}
#region Property: SelectionUnderlineStyle
/// <summary>
/// Gets or sets the underline style to apply to the current selection or insertion point.
/// </summary>
/// <value>A <see cref="UnderlineStyle"/> that represents the underline style to
/// apply to the current text selection or to text entered after the insertion point.</value>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public UnderlineStyle SelectionUnderlineStyle
{
get
{
CHARFORMAT2 fmt = new CHARFORMAT2();
fmt.cbSize = Marshal.SizeOf(fmt);
// Get the underline style
SendMessage(new HandleRef(this, Handle), EM_GETCHARFORMAT, SCF_SELECTION, ref fmt);
if ((fmt.dwMask & CFM_UNDERLINETYPE) == 0)
{
return UnderlineStyle.None;
}
else
{
byte style = (byte)(fmt.bUnderlineType & 0x0F);
return (UnderlineStyle)style;
}
}
set
{
// Ensure we don't alter the color
UnderlineColor color = SelectionUnderlineColor;
// Ensure we don't show it if it shouldn't be shown
if (value == UnderlineStyle.None)
color = UnderlineColor.Black;
// Set the underline type
CHARFORMAT2 fmt = new CHARFORMAT2();
fmt.cbSize = Marshal.SizeOf(fmt);
fmt.dwMask = CFM_UNDERLINETYPE;
fmt.bUnderlineType = (byte)((byte)value | (byte)color);
SendMessage(new HandleRef(this, Handle), EM_SETCHARFORMAT, SCF_SELECTION, ref fmt);
}
}
#endregion
#region Property: SelectionUnderlineColor
/// <summary>
/// Gets or sets the underline color to apply to the current selection or insertion point.
/// </summary>
/// <value>A <see cref="UnderlineColor"/> that represents the underline color to
/// apply to the current text selection or to text entered after the insertion point.</value>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public UnderlineColor SelectionUnderlineColor
{
get
{
CHARFORMAT2 fmt = new CHARFORMAT2();
fmt.cbSize = Marshal.SizeOf(fmt);
// Get the underline color
SendMessage(new HandleRef(this, Handle), EM_GETCHARFORMAT, SCF_SELECTION, ref fmt);
if ((fmt.dwMask & CFM_UNDERLINETYPE) == 0)
{
return UnderlineColor.None;
}
else
{
byte style = (byte)(fmt.bUnderlineType & 0xF0);
return (UnderlineColor)style;
}
}
set
{
// If the an underline color of "None" is specified, remove underline effect
if (value == UnderlineColor.None)
{
SelectionUnderlineStyle = UnderlineStyle.None;
}
else
{
// Ensure we don't alter the style
UnderlineStyle style = SelectionUnderlineStyle;
// Ensure we don't show it if it shouldn't be shown
if (style == UnderlineStyle.None)
value = UnderlineColor.Black;
// Set the underline color
CHARFORMAT2 fmt = new CHARFORMAT2();
fmt.cbSize = Marshal.SizeOf(fmt);
fmt.dwMask = CFM_UNDERLINETYPE;
fmt.bUnderlineType = (byte)((byte)style | (byte)value);
SendMessage(new HandleRef(this, Handle), EM_SETCHARFORMAT, SCF_SELECTION, ref fmt);
}
}
}
#endregion
#region Property: SelectionBackColor
/// <summary>
/// Gets or sets the background color to apply to the
/// current selection or insertion point.
/// </summary>
/// <value>A <see cref="Color"/> that represents the background color to
/// apply to the current text selection or to text entered after the insertion point.</value>
/// <remarks>
/// <para>A value of Color.Empty indicates that the default background color is used.</para>
/// <para>If the selection contains more than one background
/// color, then this property will indicate it by
/// returning Color.Empty.</para>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Color SelectionBackColor2
{
get
{
CHARFORMAT2 fmt = new CHARFORMAT2();
fmt.cbSize = Marshal.SizeOf(fmt);
// Get the background color
SendMessage(new HandleRef(this, Handle), EM_GETCHARFORMAT, SCF_SELECTION, ref fmt);
// Default to Color.Empty as there could be
// several colors present in this selection
if ((fmt.dwMask & CFM_BACKCOLOR) == 0)
return Color.Empty;
// Default to Color.Empty if the background color is automatic
if ((fmt.dwEffects & CFE_AUTOBACKCOLOR) == CFE_AUTOBACKCOLOR)
return Color.Empty;
// Deal with the weird Windows color format
return ColorTranslator.FromWin32(fmt.crBackColor);
}
set
{
CHARFORMAT2 fmt = new CHARFORMAT2();
fmt.cbSize = Marshal.SizeOf(fmt);
fmt.dwMask = CFM_BACKCOLOR;
if (value.IsEmpty)
fmt.dwEffects = CFE_AUTOBACKCOLOR;
else
fmt.crBackColor = ColorTranslator.ToWin32(value);
// Set the background color
SendMessage(new HandleRef(this, Handle), EM_SETCHARFORMAT, SCF_SELECTION, ref fmt);
}
}
#endregion
#region Property: SelectionAlignment
/// <summary>
/// Gets or sets the text alignment to apply to the current
/// selection or insertion point.
/// </summary>
/// <value>A member of the <see cref="RichTextAlign"/> enumeration that represents
/// the text alignment to apply to the current text selection or to text entered
/// after the insertion point.</value>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new RichTextAlign SelectionAlignment
{
get
{
PARAFORMAT2 fmt = new PARAFORMAT2();
fmt.cbSize = Marshal.SizeOf(fmt);
SendMessage(new HandleRef(this, Handle), EM_GETPARAFORMAT, SCF_SELECTION, ref fmt);
if ((fmt.dwMask & PFM_ALIGNMENT) == 0)
return RichTextAlign.Unknown;
else
return (RichTextAlign)fmt.wAlignment;
}
set
{
PARAFORMAT2 fmt = new PARAFORMAT2();
fmt.cbSize = Marshal.SizeOf(fmt);
fmt.dwMask = PFM_ALIGNMENT;
fmt.wAlignment = (short)value;
// Set the alignment
SendMessage(new HandleRef(this, Handle), EM_SETPARAFORMAT, SCF_SELECTION, ref fmt);
}
}
#endregion
// Convert the unit that is used by the .NET framework (1/100 inch)
// and the unit that is used by Win32 API calls (twips 1/1440 inch)
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; // First character of range (0 for start of doc)
public int cpMax; // Last character of range (-1 for end of doc)
}
[StructLayout(LayoutKind.Sequential)]
private struct FORMATRANGE
{
public IntPtr hdc; // Actual DC to draw on
public IntPtr hdcTarget; // Target DC for determining text formatting
public RECT rc; // Region of the DC to draw to (in twips)
public RECT rcPage; // Region of the whole DC (page size) (in twips)
public CHARRANGE chrg; // Range of text to draw (see above declaration)
}
private const int WM_USER = 0x400;
private const int EM_FORMATRANGE = WM_USER + 57;
private const int PFM_NUMBERING = 20;
private const int BULLET_NUMBER = 2;
public enum ListType
{
Bullet, Number, None
}
public void SetListType()
{
PARAFORMAT2 param = new PARAFORMAT2();
/// you must specify the size of the structure here, using Marshal.SizeOf , in VB6 it was Len( )
param.cbSize = Marshal.SizeOf(param);
/// build up the param structure with the current layout...
SendMessage(new HandleRef(this, Handle), EM_GETPARAFORMAT, 0, ref param);
/// wNumbering
/// Options used for bulleted or numbered paragraphs. To use this member, set the PFM_NUMBERING flag in the dwMask member.
/// PFN_BULLET ( 1 ) = Insert a bullet at the beginning of each selected paragraph.
/// 2 = Uses Arabic numbers (1, 2, 3, ...). , hence BULLET_NUMBER.
param.dwMask = PFM_NUMBERING;
param.wNumbering = BULLET_NUMBER;
/// update the richtextbox...
SendMessage(new HandleRef(this, Handle), EM_SETPARAFORMAT, 0, ref param);
}
[DllImport("USER32", EntryPoint = "SendMessageA", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
// Render the contents of the RichTextBox for printing
// Return the last character printed + 1 (printing start from this point for next page)
public int Print(int charFrom, int charTo, PrintPageEventArgs e)
{
// Mark starting and ending character
CHARRANGE cRange;
cRange.cpMin = charFrom;
cRange.cpMax = charTo;
// Calculate the area to render and print
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);
// Calculate the size of the page
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 = cRange; // Indicate character from to character to
fmtRange.hdc = hdc; // Use the same DC for measuring and rendering
fmtRange.hdcTarget = hdc; // Point at printer hDC
fmtRange.rc = rectToPrint; // Indicate the area on page to print
fmtRange.rcPage = rectPage; // Indicate whole size of page
IntPtr res = IntPtr.Zero;
IntPtr wparam = IntPtr.Zero;
wparam = new IntPtr(1);
// Move the pointer to the FORMATRANGE structure in memory
IntPtr lparam = IntPtr.Zero;
lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lparam, false);
// Send the rendered data for printing
res = SendMessage(Handle, EM_FORMATRANGE, wparam, lparam);
// Free the block of memory allocated
Marshal.FreeCoTaskMem(lparam);
// Release the device context handle obtained by a previous call
e.Graphics.ReleaseHdc(hdc);
// Return last + 1 character printer
return res.ToInt32();
}
}