Skip to content

Instantly share code, notes, and snippets.

@CamelCaseName
Last active July 24, 2023 18:27
Show Gist options
  • Save CamelCaseName/84324c076342c050307af9779a224c3c to your computer and use it in GitHub Desktop.
Save CamelCaseName/84324c076342c050307af9779a224c3c to your computer and use it in GitHub Desktop.
c# TextBox with placeholdertext in different color and a highlight text
using System;
using System.Drawing;
using System.Runtime.Versioning;
using System.Windows.Forms;
namespace CustomComponents
{
public static partial class Winutils
{
public const int WM_LBUTTONDOWN = 0x201;
public const int WM_LBUTTONDBLCLK = 0x203;
public const int WM_PAINT = 0x00f;
public const int WM_ERASEBKGND = 0x00e;
public const int WM_MOUSEMOVE = 0x200;
}
[SupportedOSPlatform("windows")]
public class HighLightTextBox : TextBox
{
private bool customDrawNeeded = false;
private bool showHighlight = false;
public HighLightTextBox() : base()
{
MouseDown += (sender, e) => { customDrawNeeded = true; Invalidate(); };
MouseUp += (sender, e) => { customDrawNeeded = true; Invalidate(); };
MouseDoubleClick += (sender, e) => { customDrawNeeded = true; Invalidate(); };
GotFocus += (sender, e) => { customDrawNeeded = true; Invalidate(); };
Click += (sender, e) => { customDrawNeeded = true; Invalidate(); };
Resize += (sender, e) => { customDrawNeeded = true; Invalidate(); };
LostFocus += (sender, e) => { customDrawNeeded = true; Invalidate(); };
MouseCaptureChanged += (sender, e) => { customDrawNeeded = true; Invalidate(); };
MouseMove += (sender, e) => _ = e.Button == MouseButtons.Left ? customDrawNeeded = true : customDrawNeeded = false;
}
public int SelectionEnd
{
get => base.SelectionStart + base.SelectionLength;
set { if (SelectionStart <= SelectionEnd) base.SelectionLength = value - SelectionStart; else throw new ArgumentOutOfRangeException(nameof(SelectionEnd), "End has to be after SelectionStart"); }
}
public new int SelectionStart { get => base.SelectionStart; set => base.SelectionStart = value; }
public int HighlightStart { get; set; }
public int HighlightEnd { get; set; }
public bool ShowHighlight
{
get => showHighlight;
set { Invalidate(); showHighlight = value; customDrawNeeded = true; }
}
public new string Text
{
get => base.Text;
set { Invalidate(); customDrawNeeded = true; base.Text = value; }
}
public Color PlaceholderColor { get; set; } = SystemColors.GrayText;
public new void Focus() => base.Focus();
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if ((m.Msg == Winutils.WM_PAINT || m.Msg == Winutils.WM_MOUSEMOVE) && IsHandleCreated)
//we have a paint message, send to own handler. only if we have a gdi handle
OnPaint(new PaintEventArgs(Graphics.FromHwnd(m.HWnd), ClientRectangle));
}
public void OnPaintOffset(PaintEventArgs e, Point offset)
{
//let the base handle backgorund and border drawing and so on
base.OnPaint(e);
//if the control isnt focused and we can draw a placeholdertext, do it
if (ShouldDrawPlaceholder())
{
DrawPlaceholderText(e.Graphics, offset);
}
}
protected override void OnPaint(PaintEventArgs e)
{
//let the base handle backgorund and border drawing and so on
base.OnPaint(e);
//if the control isnt focused and we can draw a placeholdertext, do it
if (ShouldDrawPlaceholder())
{
DrawPlaceholderText(e.Graphics);
}
//if we have a WM_PAINT and should display a shighlight somewhere
if (ShouldDrawHighlight())
{
DrawHighlightedText(e.Graphics);
}
}
private bool ShouldDrawPlaceholder()
{
return
!string.IsNullOrEmpty(PlaceholderText)
&& !Focused
&& TextLength == 0;
}
private bool ShouldDrawHighlight()
{
return
customDrawNeeded
&& ShowHighlight
&& HighlightEnd > HighlightStart
&& HighlightStart < Text.Length
&& HighlightEnd <= Text.Length;
}
private void DrawPlaceholderText(Graphics g) => DrawPlaceholderText(g, Point.Empty);
private void DrawHighlightedText(Graphics g)
{
//overlay the other text highlight
//get text positions
int currentHighlightLength = 0, newStartPos = HighlightStart;
while (newStartPos + currentHighlightLength < HighlightEnd)
{
//find length of search term in the current line
currentHighlightLength = Text.AsSpan()[newStartPos..HighlightEnd].IndexOfAny(" ,.-".AsSpan());
if (currentHighlightLength < 0)
currentHighlightLength = HighlightEnd - newStartPos;
else
++currentHighlightLength;//so that we also contain the other character
Point highlightLocation = GetPositionFromCharIndex(newStartPos);
//adjust offset
highlightLocation.X -= 2;
//render highlight in the current line
AdjustTextRegion(out TextFormatFlags flags, ref highlightLocation);
TextRenderer.DrawText(g, Text.AsSpan()[newStartPos..(newStartPos + currentHighlightLength)], base.Font, highlightLocation, SystemColors.MenuText, SystemColors.MenuHighlight, flags);
//move over
newStartPos += currentHighlightLength;
currentHighlightLength = 0;
}
customDrawNeeded = false;
}
private void DrawPlaceholderText(Graphics g, Point offset)
{
AdjustTextRegion(out TextFormatFlags flags, ref offset);
TextRenderer.DrawText(g, PlaceholderText, Font, offset, PlaceholderColor, BackColor, flags);
}
private void AdjustTextRegion(out TextFormatFlags flags, ref Point point)
{
flags = TextFormatFlags.NoPadding | TextFormatFlags.Top |
TextFormatFlags.EndEllipsis;
if (point.IsEmpty) point = ClientRectangle.Location;
if (RightToLeft == RightToLeft.Yes)
{
flags |= TextFormatFlags.RightToLeft;
switch (TextAlign)
{
case HorizontalAlignment.Center:
flags |= TextFormatFlags.HorizontalCenter;
point.Offset(0, 1);
break;
case HorizontalAlignment.Left:
flags |= TextFormatFlags.Right;
point.Offset(1, 1);
break;
case HorizontalAlignment.Right:
flags |= TextFormatFlags.Left;
point.Offset(0, 1);
break;
}
}
else
{
flags &= ~TextFormatFlags.RightToLeft;
switch (TextAlign)
{
case HorizontalAlignment.Center:
flags |= TextFormatFlags.HorizontalCenter;
point.Offset(0, 1);
break;
case HorizontalAlignment.Left:
flags |= TextFormatFlags.Left;
point.Offset(1, 1);
break;
case HorizontalAlignment.Right:
flags |= TextFormatFlags.Right;
point.Offset(0, 1);
break;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment