using System; using System.Collections.Generic; //using System.Text; using System.Windows.Forms; using System.Drawing; using System.ComponentModel; using System.Windows.Forms.Design; namespace FancyControls { /// <summary> /// textbox can show background text and be decorated with controls on left and right side /// </summary> [ToolboxBitmapAttribute(typeof(TextBox))] [DesignerAttribute(typeof(FancyTextBoxDesigner))] public partial class FancyTextBox : TextBox { #region private members private TextBoxCustomize customize = null; // inside control painter private List<Control> onLeft = null; // inside controls on left private List<Control> onRight = null; // inside controls on right private string backText = String.Empty; // background text private bool backTextShowOnFocus = true; // show background text when active // do repaint when handle created private void Repaint() { // should have customize object when handle created if (customize != null) { // repaint inside controls foreach (Control ctl in this.Controls) // remove event handler to avoid infinite loop // as repoaint methos might change location ctl.LocationChanged -= new EventHandler(control_LocationChanged); customize.Repaint(); foreach (Control ctl in this.Controls) // restore event handler ctl.LocationChanged += new EventHandler(control_LocationChanged); } } // add control right/left based on location private void AddControl(Control control) { if (control.Location.X <= Size.Width / 2) // which side to add control { // add on left if (onRight.IndexOf(control) >= 0) { // if it was on right side remove from right side onRight.Remove(control); } if (onLeft.IndexOf(control) < 0) { // if control is not already on left add it onLeft.Add(control); } } else { // add on right if (onLeft.IndexOf(control) >= 0) // if it was on left side remove from left side onLeft.Remove(control); if (onRight.IndexOf(control) < 0) // if control is not already on right add it onRight.Add(control); } Repaint(); } // handler when control is moved from left to right or vice versa private void control_LocationChanged(object sender, EventArgs e) { AddControl((Control)sender); } // handler to reset edit box margins when control hidden/shown private void control_VisibleChanged(object sender, EventArgs e) { Repaint(); } #endregion #region public properties /// <summary> /// get/set backgorund text /// to show purpose of textbox when textbox is empty /// </summary> [Category("Appearance")] [Description("Background text displays gray text when textbox is empty")] [Localizable(true)] public string BackText { get { return (this.backText); } set { if (this.backText != value) { this.backText = value; Repaint(); // paint new background text } } } /// <summary> /// get/set whether to show background text /// when control is in focus /// </summary> [Category("Appearance")] [Description("Background text still displays when in focus")] [DefaultValue(true)] public bool BackTextShowOnFocus { get { return (this.backTextShowOnFocus); } set { if (this.backTextShowOnFocus != value) { this.backTextShowOnFocus = value; Repaint(); } } } /// <summary> /// is control on left or right side /// </summary> /// <param name="ctl">control to check</param> /// <returns>true - on right/false - on left</returns> public bool IsRightSide(Control ctl) { return (this.onRight.IndexOf(ctl) >= 0); } #endregion #region protected methods overrides /// <summary> /// set customization object handle /// </summary> /// <param name="e"></param> protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); if (customize == null) customize = new TextBoxCustomize(this); // create new custom painter object else customize.SetHandle(this.Handle); } /// <summary> /// release customization object handle /// </summary> /// <param name="e"></param> protected override void OnHandleDestroyed(EventArgs e) { customize.ReleaseHandle(); base.OnHandleDestroyed(e); } /// <summary> /// Insert new control on right or left side of textbox /// </summary> /// <param name="e"></param> protected override void OnControlAdded(ControlEventArgs e) { base.OnControlAdded(e); AddControl(e.Control); e.Control.LocationChanged += new EventHandler(control_LocationChanged); e.Control.VisibleChanged += new EventHandler(control_VisibleChanged); } /// <summary> /// Remove control from textbox /// </summary> /// <param name="e"></param> protected override void OnControlRemoved(ControlEventArgs e) { base.OnControlRemoved(e); onLeft.Remove(e.Control); onRight.Remove(e.Control); e.Control.LocationChanged -= new EventHandler(control_LocationChanged); e.Control.VisibleChanged -= new EventHandler(control_VisibleChanged); Repaint(); } #endregion #region constructor /// <summary> /// default constructor /// </summary> public FancyTextBox() : base() { // create lists to handle right/left controls onLeft = new List<Control>(); onRight = new List<Control>(); } #endregion #region Designer /// <summary> /// Render FancyTextBox as container control at design time /// to allow dropping of other controls into it /// </summary> internal class FancyTextBoxDesigner : ParentControlDesigner { private Control dropControl = null; // dropped control /// <summary> /// Catches control being dropped into FancyTextBox /// </summary> /// <param name="control">dropped control</param> /// <returns>true/false</returns> public override bool CanParent(Control control) { // NOTE: OnDragDrop event does not give control being dropped // so CanParent fires just before DragDrop where control is // captured and set into dropControl private var. dropControl = control; return base.CanParent(control); } /// <summary> /// Set dropped control on left/right depending where it was dropped /// in FancyTextBox /// </summary> /// <param name="de">event agrs</param> protected override void OnDragDrop(DragEventArgs de) { try { if (dropControl != null) { // set control location to dropped point dropControl.Location = this.Control.PointToClient(new Point(de.X, de.Y)); if (this.Control.Controls.IndexOf(dropControl) >= 0) { // if control already belongs to FancyTextBox // reset side based on new location ((FancyTextBox)this.Control).AddControl(dropControl); } else { // add new c ontrol (handled in OnControlAdded) base.OnDragDrop(de); } dropControl = null; } else base.OnDragDrop(de); } catch { de.Effect = DragDropEffects.None; } } } #endregion } /// <summary> /// FancyTextBox custom painter /// This Control was inspired by code found in /// http://www.vbaccelerator.com/ /// </summary> internal class TextBoxCustomize : NativeWindow { #region UnmanagedMethods [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; } [DllImport("user32", CharSet = CharSet.Auto)] private extern static int SendMessage( IntPtr hwnd, int wMsg, int wParam, int lParam); [DllImport("user32", CharSet = CharSet.Auto)] private extern static int GetWindowLong( IntPtr hWnd, int dwStyle); [DllImport("user32")] private extern static IntPtr GetDC( IntPtr hwnd); [DllImport("user32")] private extern static int ReleaseDC( IntPtr hwnd, IntPtr hdc); [DllImport("user32")] private extern static int GetClientRect( IntPtr hwnd, ref RECT rc); [DllImport("user32")] private extern static int GetWindowRect( IntPtr hwnd, ref RECT rc); private const int EC_LEFTMARGIN = 0x1; private const int EC_RIGHTMARGIN = 0x2; private const int EC_USEFONTINFO = 0xFFFF; private const int EM_SETMARGINS = 0xD3; private const int EM_GETMARGINS = 0xD4; private const int WM_PAINT = 0xF; private const int WM_SETFOCUS = 0x7; private const int WM_KILLFOCUS = 0x8; private const int WM_SETFONT = 0x30; private const int WM_MOUSEMOVE = 0x200; private const int WM_LBUTTONDOWN = 0x201; private const int WM_RBUTTONDOWN = 0x204; private const int WM_MBUTTONDOWN = 0x207; private const int WM_LBUTTONUP = 0x202; private const int WM_RBUTTONUP = 0x205; private const int WM_MBUTTONUP = 0x208; private const int WM_LBUTTONDBLCLK = 0x203; private const int WM_RBUTTONDBLCLK = 0x206; private const int WM_MBUTTONDBLCLK = 0x209; private const int WM_KEYDOWN = 0x0100; private const int WM_KEYUP = 0x0101; private const int WM_CHAR = 0x0102; private const int GWL_EXSTYLE = (-20); private const int WS_EX_RIGHT = 0x00001000; private const int WS_EX_LEFT = 0x00000000; private const int WS_EX_RTLREADING = 0x00002000; private const int WS_EX_LTRREADING = 0x00000000; private const int WS_EX_LEFTSCROLLBAR = 0x00004000; private const int WS_EX_RIGHTSCROLLBAR = 0x00000000; #endregion #region control to customize and SetHandle private FancyTextBox fancyTextBox = null; /// <summary> /// Set Native Window handle /// </summary> /// <param name="Hwnd">Edit Box Handle</param> public void SetHandle(IntPtr Hwnd) { if(!this.Handle.Equals(Hwnd)) { if (!Hwnd.Equals(IntPtr.Zero)) { this.ReleaseHandle(); } this.AssignHandle(Hwnd); Repaint(); } } #endregion #region Unamanaged Methods Wrappers // Checks Right to left (not implemented) private static bool IsRightToLeft(IntPtr handle) { int style = GetWindowLong(handle, GWL_EXSTYLE); return ( ((style & WS_EX_RIGHT) == WS_EX_RIGHT) || ((style & WS_EX_RTLREADING) == WS_EX_RTLREADING) || ((style & WS_EX_LEFTSCROLLBAR) == WS_EX_LEFTSCROLLBAR)); } // get edit box right margin private static int GetRightMargin(IntPtr handle) { return (SendMessage(handle, EM_GETMARGINS, 0, 0) / 0x10000); } // set edit box right box margin private static void SetRightMargin(IntPtr handle, int margin) { SendMessage(handle, EM_SETMARGINS, EC_RIGHTMARGIN, margin * 0x10000); } // get edit box left margin private static int GetLeftMargin(IntPtr handle) { return (SendMessage(handle, EM_GETMARGINS, 0, 0) & 0xFFFF); } // set edit box left margin private static void SetLeftMargin(IntPtr handle, int margin) { SendMessage(handle, EM_SETMARGINS, EC_LEFTMARGIN, margin & 0xFFFF); } // RightMargin property simplifies handling of edit box margin private int RightMargin { get { return (this.Handle != (IntPtr)0 ? GetRightMargin(this.Handle) : 0); } set { if (this.Handle != (IntPtr)0) SetRightMargin(this.Handle, value); } } // LeftMargin property simplifies handling of edit box margin private int LeftMargin { get { return (this.Handle != (IntPtr)0 ? GetLeftMargin(this.Handle) : 0); } set { if (this.Handle != (IntPtr)0) SetLeftMargin(this.Handle, value); } } #endregion #region Repaint routines /// <summary> /// Calls the base WndProc and performs WM_PAINT /// processing to repaint inside controls when necessary /// </summary> /// <param name="m">Windows Message</param> protected override void WndProc(ref Message m) { base.WndProc(ref m); switch (m.Msg) { case WM_SETFONT: case WM_PAINT: case WM_SETFOCUS: case WM_KILLFOCUS: case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK: case WM_KEYDOWN: case WM_CHAR: case WM_KEYUP: Repaint(); break; case WM_MOUSEMOVE: if (!m.WParam.Equals(IntPtr.Zero)) { Repaint(); } break; } } /// <summary> /// Paints inside controls and background text when necessary: /// </summary> public void Repaint() { // get client rect of edit box RECT rcClient = new RECT(); GetClientRect(this.Handle, ref rcClient); RectangleF clientrect = new RectangleF( rcClient.left, rcClient.top, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); // fixed single border is painted inside edit box // in this case reduce rect to exclude border if (fancyTextBox.BorderStyle == BorderStyle.FixedSingle) { clientrect.X += 1; clientrect.Y += 1; clientrect.Width -= 2; clientrect.Height -= 2; } // check of edit box margins changed due to inside controls changes if (MarginChanged(clientrect)) { // margin change triggers repaint event again return; } // get DC for painting IntPtr handle = this.Handle; IntPtr hdc = GetDC(handle); Graphics gfx = Graphics.FromHdc(hdc); int leftMargin = 0; int rightMargin = 0; // repaint inside controls foreach (Control ctl in fancyTextBox.Controls) { if (ctl.Visible) { if (fancyTextBox.IsRightSide(ctl)) { // repaint control on right // and calculate next control position rightMargin += RepaintControl(gfx, ctl, clientrect, rightMargin, fancyTextBox.IsRightSide(ctl)); } else { // repaint control on left // and calculate next control position leftMargin += RepaintControl(gfx, ctl, clientrect, leftMargin, fancyTextBox.IsRightSide(ctl)); } } } // repaint background text if on Text present in FancyTextBox if ((this.fancyTextBox.Multiline && this.fancyTextBox.Lines.Length == 0) || (!this.fancyTextBox.Multiline && this.fancyTextBox.Text == String.Empty)) { RepaintBackGround(gfx, clientrect); } // cleanup DC gfx.Dispose(); ReleaseDC(handle, hdc); } private int RepaintControl(Graphics gfx, Control control, RectangleF clientrect, int margin, bool isrightside) { // scale control to fit exactly inside FancyTextBox height SizeF drawSize = ScaledSize(clientrect, control.Size); // control's new location Point drawLoc = new Point(); // set control size control.Width = (int)drawSize.Width; control.Height = (int)drawSize.Height; // set control Y loc inside FancyTextBox centered vertically drawLoc.Y = (int)(clientrect.Top + (clientrect.Height - drawSize.Height) / 2); if (isrightside) { // if control is no right side set control X loc drawLoc.X = (int)(clientrect.X + clientrect.Width - margin - drawSize.Width); } else { // if control is no left side set control X loc drawLoc.X = (int)(clientrect.X + margin + drawLoc.Y); } // move control to new location inside FancyTextBox control.Location = drawLoc; control.BringToFront(); return ((int)(drawSize.Width + drawLoc.Y * 2)); } private void RepaintBackGround(Graphics gfx, RectangleF clientrect) { // calculate background text size SizeF textSize = new SizeF(); if (fancyTextBox.BackText != String.Empty) textSize = gfx.MeasureString(fancyTextBox.BackText, fancyTextBox.Font); else // if backgoround text is empty calc largest letter textSize = gfx.MeasureString("W", fancyTextBox.Font); // clip rect for background text RectangleF cliprect = new RectangleF(); // start from left margin and top cliprect.X = LeftMargin; cliprect.Y = clientrect.Y; if (fancyTextBox.Multiline) { // keep text on top of multiline cliprect.Height = textSize.Height; } else { // center vertically in singleline cliprect.Height = clientrect.Height; } // set width between margins cliprect.Width = clientrect.Width - LeftMargin - RightMargin; // background text start location PointF textLoc = new PointF(); // set text start X based on TextAlign value switch (fancyTextBox.TextAlign) { case HorizontalAlignment.Left: textLoc.X = LeftMargin; break; case HorizontalAlignment.Right: textLoc.X = clientrect.Width - RightMargin - textSize.Width; break; case HorizontalAlignment.Center: textLoc.X = (clientrect.Width - textSize.Width) / 2; break; } // center Y vertically textLoc.Y = cliprect.Top + (cliprect.Height - textSize.Height) / 2; // draw text in this rect RectangleF stringrect = new RectangleF(textLoc, textSize); // adjust string rect for single border if (fancyTextBox.BorderStyle == BorderStyle.FixedSingle) { cliprect.X += 1; cliprect.Y += 1; cliprect.Width -= 2; cliprect.Height -= 2; } gfx.SetClip(cliprect); // erase old background text // (this sometimes wipes out design move container handle partially) EraseBackground(gfx); // draw background text when appropriate if (fancyTextBox.BackTextShowOnFocus || !fancyTextBox.Focused) { Brush stringbrush = new SolidBrush(SystemColors.GrayText); gfx.DrawString(fancyTextBox.BackText, fancyTextBox.Font, stringbrush, stringrect); stringbrush.Dispose(); } gfx.ResetClip(); } private void EraseBackground(Graphics gfx) { // Fill with background color SolidBrush brush = new SolidBrush(fancyTextBox.BackColor); gfx.FillRegion(brush, gfx.Clip); brush.Dispose(); } // calculate new margins private bool MarginChanged(RectangleF clientrect) { int rightMargin = 0; int leftMargin = 0; foreach (Control ctl in fancyTextBox.Controls) { if (ctl.Visible) { SizeF scaledSize = ScaledSize(clientrect, new SizeF(ctl.Size)); if (fancyTextBox.IsRightSide(ctl)) { rightMargin += (int)scaledSize.Width; if (scaledSize.Height < clientrect.Height) rightMargin += (int)(clientrect.Height - scaledSize.Height); } else { leftMargin += (int)scaledSize.Width; if (scaledSize.Height < clientrect.Height) leftMargin += (int)(clientrect.Height - scaledSize.Height); } } } bool result = false; if (LeftMargin != leftMargin) { result = true; LeftMargin = leftMargin; } if (RightMargin != rightMargin) { result = true; RightMargin = rightMargin; } return (result); } // scale control to fit FancyTextBox private SizeF ScaledSize(RectangleF clientrect, SizeF size) { SizeF scaledSize = new SizeF(size); scaledSize.Width *= clientrect.Height / scaledSize.Height; scaledSize.Height = clientrect.Height; return (scaledSize); } #endregion #region Constructors /// <summary> /// Constructs a new instance of this class /// </summary> public TextBoxCustomize() : base() { } /// <summary> /// Constructs instance with FancyTextBox attached /// </summary> /// <param name="ftb">FancyTextBox</param> public TextBoxCustomize(FancyTextBox ftb) : base() { this.fancyTextBox = ftb; this.SetHandle(ftb.Handle); } #endregion } }