I am showing a little tooltip, but if I change the selecteditem/text in the dropdownmenu, tooltip shows the old text and the new text. I want it to show only the new text.
private void optionsvalueComboBox_MouseHover(object sender, EventArgs e)
{
ToolTip buttonToolTip = new ToolTip();
buttonToolTip.ToolTipTitle = "Value";
buttonToolTip.UseFading = true;
buttonToolTip.UseAnimation = true;
buttonToolTip.IsBalloon = true;
buttonToolTip.ShowAlways = true;
buttonToolTip.AutoPopDelay = 5000;
buttonToolTip.InitialDelay = 1000;
buttonToolTip.ReshowDelay = 0;
buttonToolTip.SetToolTip(optionsvalueComboBox, optionsvalueComboBox.Text);
}
Assuming what you don't like is the tooltip text changing from the old text to the new text...
The reason it's doing that is because you are creating a new tooltip instance on every hover event. Every time the hover event is fired, the old tooltip instance is replaced with a new one which is why you see both. To fix this, put the declaration outside the event, like this:
ToolTip buttonToolTip = new ToolTip();
private void optionsvalueComboBox_MouseHover(object sender, EventArgs e)
{
buttonToolTip.ToolTipTitle = "Value";
buttonToolTip.UseFading = true;
buttonToolTip.UseAnimation = true;
buttonToolTip.IsBalloon = true;
buttonToolTip.ShowAlways = true;
buttonToolTip.AutoPopDelay = 5000;
buttonToolTip.InitialDelay = 1000;
buttonToolTip.ReshowDelay = 0;
buttonToolTip.SetToolTip(optionsvalueComboBox, optionsvalueComboBox.Text);
}
Now the same tooltip is being used with the wording simply being replaced. Let me know if this works for you!
I've tried digging in the MouseHover event of a ComboBox and looks like it doesn't work normally as we expect. The MouseHover is in fact fired only when you move the mouse over the drop down button if your ComboBox has style of dropdown. The simplest solution for this is change your combobox style to dropdownlist like this:
comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
However that kind of style will make the ComboBox readonly. If that's not what you want, there is a work-around for you is to use the event MouseMove with a Timer to mimic the MouseHover, here is the code for you:
public partial class Form1 : Form {
public Form1(){
InitializeComponent();
t.Interval = 600;
t.Tick += (se, ev) => {
buttonToolTip.SetToolTip(comboBox1, (string)comboBox1.SelectedItem);
t.Stop();
};
//init the buttonToolTip
buttonToolTip.ToolTipTitle = "Value";
buttonToolTip.UseFading = true;
buttonToolTip.UseAnimation = true;
buttonToolTip.IsBalloon = true;
buttonToolTip.ShowAlways = true;
buttonToolTip.AutoPopDelay = 5000;
buttonToolTip.InitialDelay = 1000;
buttonToolTip.ReshowDelay = 0;
//register MouseMove event handler for your comboBox1
comboBox1.MouseMove += (se, ev) => {
//Restart the timer every time the mouse is moving
t.Stop();
t.Start();
};
}
Timer t = new Timer();
ToolTip buttonToolTip = new ToolTip();
}
A complete working example:
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public RECT(Rectangle rect)
{
Left = rect.Left;
Top = rect.Top;
Right = rect.Right;
Bottom = rect.Bottom;
}
public Rectangle Rect
{
get
{
return new Rectangle(Left, Top, Right - Left, Bottom - Top);
}
}
public Point Location
{
get
{
return new Point(Left, Top);
}
}
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public class ToolTipComboBox: ComboBox
{
#region Fields
private ToolTip toolTip;
private bool _tooltipVisible;
private bool _dropDownOpen;
#endregion
#region Types
[StructLayout(LayoutKind.Sequential)]
// ReSharper disable once InconsistentNaming
public struct COMBOBOXINFO
{
public Int32 cbSize;
public RECT rcItem;
public RECT rcButton;
public ComboBoxButtonState buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
public enum ComboBoxButtonState
{
// ReSharper disable once UnusedMember.Global
StateSystemNone = 0,
// ReSharper disable once UnusedMember.Global
StateSystemInvisible = 0x00008000,
// ReSharper disable once UnusedMember.Global
StateSystemPressed = 0x00000008
}
[DllImport("user32.dll")]
public static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
#endregion
#region Properties
private IntPtr HwndCombo
{
get
{
COMBOBOXINFO pcbi = new COMBOBOXINFO();
pcbi.cbSize = Marshal.SizeOf(pcbi);
GetComboBoxInfo(Handle, ref pcbi);
return pcbi.hwndCombo;
}
}
private IntPtr HwndDropDown
{
get
{
COMBOBOXINFO pcbi = new COMBOBOXINFO();
pcbi.cbSize = Marshal.SizeOf(pcbi);
GetComboBoxInfo(Handle, ref pcbi);
return pcbi.hwndList;
}
}
[Browsable(false)]
public new DrawMode DrawMode
{
get { return base.DrawMode; }
set { base.DrawMode = value; }
}
#endregion
#region ctor
public ToolTipComboBox()
{
toolTip = new ToolTip
{
UseAnimation = false,
UseFading = false
};
base.DrawMode = DrawMode.OwnerDrawFixed;
DrawItem += OnDrawItem;
DropDownClosed += OnDropDownClosed;
DropDown += OnDropDown;
MouseLeave += OnMouseLeave;
}
#endregion
#region Methods
private void OnDropDown(object sender, EventArgs e)
{
_dropDownOpen = true;
}
private void OnMouseLeave(object sender, EventArgs e)
{
ResetToolTip();
}
private void ShowToolTip(string text, int x, int y)
{
toolTip.Show(text, this, x, y);
_tooltipVisible = true;
}
private void OnDrawItem(object sender, DrawItemEventArgs e)
{
ComboBox cbo = sender as ComboBox;
if (e.Index == -1) return;
// ReSharper disable once PossibleNullReferenceException
string text = cbo.GetItemText(cbo.Items[e.Index]);
e.DrawBackground();
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
TextRenderer.DrawText(e.Graphics, text, e.Font, e.Bounds.Location, SystemColors.Window);
if (_dropDownOpen)
{
Size szText = TextRenderer.MeasureText(text, cbo.Font);
if (szText.Width > cbo.Width - SystemInformation.VerticalScrollBarWidth && !_tooltipVisible)
{
RECT rcDropDown;
GetWindowRect(HwndDropDown, out rcDropDown);
RECT rcCombo;
GetWindowRect(HwndCombo, out rcCombo);
if (rcCombo.Top > rcDropDown.Top)
{
ShowToolTip(text, e.Bounds.X, e.Bounds.Y - rcDropDown.Rect.Height - cbo.ItemHeight - 5);
}
else
{
ShowToolTip(text, e.Bounds.X, e.Bounds.Y + cbo.ItemHeight - cbo.ItemHeight);
}
}
}
}
else
{
ResetToolTip();
TextRenderer.DrawText(e.Graphics, text, e.Font, e.Bounds.Location, cbo.ForeColor);
}
e.DrawFocusRectangle();
}
private void OnDropDownClosed(object sender, EventArgs e)
{
_dropDownOpen = false;
ResetToolTip();
}
private void ResetToolTip()
{
if (_tooltipVisible)
{
// ReSharper disable once AssignNullToNotNullAttribute
toolTip.SetToolTip(this, null);
_tooltipVisible = false;
}
}
#endregion
}
Related
I have a window on which I have some blur effects running. I want this window to be maximized so I set the Window State field to be Maximized in the designer.But the Window is not maximized and leaves some uncovered area in the top left corner. Ive tried multiple Start Position settings but none of them solve the problem.
The Settings
The Window
The code for the blurry Window
using System.Runtime.InteropServices;
namespace WF4
{
public partial class Form1 : Form
{
public Form1()
{
this.EnableBlur();
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.LimeGreen;
TransparencyKey = Color.LimeGreen;
InitializeComponent();
FormBorderStyle = FormBorderStyle.None;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
MessageBox.Show("Hllo");
}
}
}
public static class WindowExtension
{
[DllImport("user32.dll")]
static internal extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
public static void EnableBlur(this Form #this)
{
var accent = new AccentPolicy();
accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND;
var accentStructSize = Marshal.SizeOf(accent);
var accentPtr = Marshal.AllocHGlobal(accentStructSize);
Marshal.StructureToPtr(accent, accentPtr, false);
var Data = new WindowCompositionAttributeData();
Data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY;
Data.SizeOfData = accentStructSize;
Data.Data = accentPtr;
SetWindowCompositionAttribute(#this.Handle, ref Data);
Marshal.FreeHGlobal(accentPtr);
}
}
enum AccentState
{
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_INVALID_STATE = 4
}
struct AccentPolicy
{
public AccentState AccentState;
public int AccentFlags;
public int GradientColor;
public int AnimationId;
}
struct WindowCompositionAttributeData
{
public WindowCompositionAttribute Attribute;
public IntPtr Data;
public int SizeOfData;
}
enum WindowCompositionAttribute
{
WCA_ACCENT_POLICY = 19
}
}
I tested Hans's suggestion and it worked well.
The solution is to set the FormBorderStyle property to None.
Make sure the background color is Control.
Calling EnableBlur() after InitializeComponent():
InitializeComponent();
this.EnableBlur();
Output:
I found on the internet how to create your own checkbox. Unfortunately, there is a problem with the fact that you have to click very slowly to react to a change, and when I click on the text, nothing changes. I presented the whole action in the film. Please help.
https://www.youtube.com/watch?v=s6xmVAoUVJ8
Here are the files: https://drive.google.com/file/d/10pj6DRjCvfQc8_s0rUufJpN1kdft2sml/view?usp=sharing
CheckBox.cs:
class NowyCheckbox : Control
{
#region Private members
public bool IsChecked = false;
private Label CheckBoxLabel;
private Rectangle CheckBoxRectangle;
private bool MouseOver = false;
#endregion
#region Public Members (in Attributes)
private Color CheckBoxCharColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxCharColor
{
get
{
return CheckBoxCharColorValue;
}
set
{
if (CheckBoxCharColor != value)
{
CheckBoxCharColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxCharHighlightColorValue = Color.FromArgb(0, 120, 215);
public Color CheckBoxCharHighlightColor
{
get
{
return CheckBoxCharHighlightColorValue;
}
set
{
if (CheckBoxCharHighlightColor != value)
{
CheckBoxCharHighlightColorValue = value;
Invalidate();
}
}
}
private Font CheckBoxCharFontValue;
public Font CheckBoxCharFont
{
get
{
return CheckBoxCharFontValue;
}
set
{
if (CheckBoxCharFont != value)
{
CheckBoxCharFontValue = value;
Invalidate();
}
}
}
private string CheckBoxCharValue = "!";
public string CheckBoxChar
{
get
{
return CheckBoxCharValue;
}
set
{
if (CheckBoxChar != value)
{
CheckBoxCharValue = value;
RefreshLabel();
}
}
}
private Font CheckBoxFontValue;
public Font CheckBoxFont
{
get
{
return CheckBoxFontValue;
}
set
{
if (CheckBoxFont != value)
{
CheckBoxFontValue = value;
RefreshLabel();
}
}
}
private string CheckBoxTextValue = "Nowy CheckBox";
public string CheckBoxText
{
get
{
return CheckBoxTextValue;
}
set
{
if (CheckBoxText != value)
{
CheckBoxTextValue = value;
RefreshLabel();
}
}
}
private int CheckBoxSizeValue = 12;
public int CheckBoxSize
{
get
{
return CheckBoxSizeValue;
}
set
{
if (CheckBoxSize != value)
{
CheckBoxSizeValue = value;
RefreshLabel();
Invalidate();
}
}
}
private int CheckBoxFrameStrengthValue = 1;
public int CheckBoxFrameStrength
{
get
{
return CheckBoxFrameStrengthValue;
}
set
{
if (CheckBoxFrameStrengthValue != value)
{
CheckBoxFrameStrengthValue = value;
Invalidate();
}
}
}
#region Public Member
private int CheckBoxCharOffsetXValue = 0;
public int CheckBoxCharOffsetX
{
get
{
return CheckBoxCharOffsetXValue;
}
set
{
if (CheckBoxCharOffsetX != value)
{
CheckBoxCharOffsetXValue = value;
Invalidate();
}
}
}
private int CheckBoxCharOffsetYValue = 0;
public int CheckBoxCharOffsetY
{
get
{
return CheckBoxCharOffsetYValue;
}
set
{
if (CheckBoxCharOffsetY != value)
{
CheckBoxCharOffsetYValue = value;
Invalidate();
}
}
}
private int CheckBoxOffsetXValue = 0;
public int CheckBoxOffsetX
{
get
{
return CheckBoxOffsetXValue;
}
set
{
if (CheckBoxOffsetX != value)
{
CheckBoxOffsetXValue = value;
RefreshLabel();
}
}
}
private int CheckBoxOffsetYValue = 0;
public int CheckBoxOffsetY
{
get
{
return CheckBoxOffsetYValue;
}
set
{
if (CheckBoxOffsetY != value)
{
CheckBoxOffsetYValue = value;
RefreshLabel();
}
}
}
#endregion
#region Public Colors
private Color CheckBoxFrameColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxFrameColor
{
get
{
return CheckBoxFrameColorValue;
}
set
{
if (CheckBoxFrameColorValue != value)
{
CheckBoxFrameColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxFrameHighlightColorValue = Color.FromArgb(0, 120, 250);
public Color CheckBoxFrameHighlightColor
{
get
{
return CheckBoxFrameHighlightColorValue;
}
set
{
if (CheckBoxFrameHighlightColorValue != value)
{
CheckBoxFrameHighlightColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxBackColorValue = Color.FromArgb(255, 255, 255);
public Color CheckBoxBackColor
{
get
{
return CheckBoxBackColorValue;
}
set
{
if (CheckBoxBackColorValue != value)
{
CheckBoxBackColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxBackHighlightColorValue = Color.FromArgb(255, 255, 255);
public Color CheckBoxBackHighlightColor
{
get
{
return CheckBoxBackHighlightColorValue;
}
set
{
if (CheckBoxBackHighlightColorValue != value)
{
CheckBoxBackHighlightColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxForeHighlightColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxForeHighlightColor
{
get
{
return CheckBoxForeHighlightColorValue;
}
set
{
if (CheckBoxForeHighlightColorValue != value)
{
CheckBoxForeHighlightColorValue = value;
RefreshLabel();
}
}
}
private Color CheckBoxForeColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxForeColor
{
get
{
return CheckBoxForeColorValue;
}
set
{
if (CheckBoxForeColorValue != value)
{
CheckBoxForeColorValue = value;
RefreshLabel();
}
}
}
#endregion
#endregion
#region Constructor
public NowyCheckbox()
{
DoubleBuffered = true;
Size = new Size(112, 18);
CheckBoxFont = this.Font;
CheckBoxCharFont = this.Font;
var midHeight = 8 - Font.Height / 2;
CheckBoxLabel = new Label()
{
Font = CheckBoxFont,
Size = new Size(this.Width - 16, this.Height),
Location = new Point(16, midHeight),
Text = CheckBoxText
};
Controls.Add(CheckBoxLabel);
CreateMouseEvents();
}
#endregion
#region Create mouse events
private void CreateMouseEvents()
{
MouseEnter += (sender, e) =>
{
OnCustomMouseEnter(e);
};
MouseLeave += (sender, e) =>
{
OnCustomMouseLeave(e);
};
MouseDown += (sender, e) =>
{
OnCustomMouseDown(e);
};
CheckBoxLabel.MouseEnter += (sender, e) =>
{
OnCustomMouseEnter(e);
};
CheckBoxLabel.MouseLeave += (sender, e) =>
{
OnCustomMouseLeave(e);
};
CheckBoxLabel.MouseDown += (sender, e) =>
{
OnCustomMouseDown(e);
};
}
#endregion
#region Mouse Events
private void OnCustomMouseDown(EventArgs e)
{
IsChecked = !IsChecked;
Invalidate();
}
private void OnCustomMouseEnter(EventArgs e)
{
if (MouseOver == false)
{
MouseOver = true;
Invalidate();
RefreshLabel();
}
}
private void OnCustomMouseLeave(EventArgs e)
{
if (MouseOver == true)
{
MouseOver = false;
Invalidate();
RefreshLabel();
}
}
#endregion
#region Paint NowyCheckbox
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
PaintRectangle(e);
if (IsChecked == true)
{
PaintArrowChar(e);
}
}
#endregion
#region Paint NowyCheckboxRectangle
private void PaintRectangle(PaintEventArgs e)
{
var midHeight = Height / 2 - CheckBoxSize / 2;
CheckBoxRectangle = new Rectangle(0, midHeight, CheckBoxSize, CheckBoxSize);
var fillColor = MouseOver == true ? CheckBoxBackHighlightColor : CheckBoxBackColor;
var frameColor = MouseOver == true ? CheckBoxFrameHighlightColor : CheckBoxFrameColor;
using (var pen = new Pen(frameColor, CheckBoxFrameStrength))
{
var brush = new SolidBrush(fillColor);
e.Graphics.FillRectangle(brush, CheckBoxRectangle);
e.Graphics.DrawRectangle(pen, CheckBoxRectangle);
}
}
#endregion
#region Paint Checkbox Arrow
private void PaintArrowChar(PaintEventArgs e)
{
var charColor = MouseOver == true ? CheckBoxCharHighlightColor : CheckBoxCharColor;
var midX = CheckBoxSize / 2 - 3 + CheckBoxCharOffsetX;
var midY = Height / 2 - CheckBoxCharFont.Height / 2 + CheckBoxCharOffsetY;
using (var brush = new SolidBrush(charColor))
{
e.Graphics.DrawString(CheckBoxChar, CheckBoxCharFont, brush, new Point(midX, midY));
}
}
#endregion
#region [OnResize]
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
RefreshLabel();
}
#endregion
#region Refresh Label
private void RefreshLabel()
{
if (CheckBoxLabel == null) return;
CheckBoxLabel.Font = CheckBoxFont;
CheckBoxLabel.Text = CheckBoxText;
CheckBoxLabel.ForeColor = MouseOver == true ? CheckBoxForeHighlightColor : CheckBoxForeColor;
var offsetWidth = Width - CheckBoxSize;
CheckBoxLabel.Size = new Size(offsetWidth, Height);
var offsetX = CheckBoxSize + 6 + CheckBoxOffsetX;
var midHeight = Height / 2 - CheckBoxFont.Height / 2 + CheckBoxOffsetY;
CheckBoxLabel.Location = new Point(offsetX, midHeight);
Invalidate();
}
#endregion
}
}
Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void nowyCheckbox1_Click(object sender, EventArgs e)
{
if (nowyCheckbox1.IsChecked)
{
label2.Text = "Jestem włączony";
}
else
{
label2.Text = "Jestem wyłączony";
}
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
label3.Text = "Jestem włączony";
}
else
{
label3.Text = "Jestem wyłączony";
}
}
}
An object like your custom check box, should have a mechanism used to notify a subscriber (like a Form) that something has happened. Notifications like mouse clicks, keystrokes, property changes...etc. In event-driven programming, your control should generate and publish an event to be received and handled by a subscriber through an Event Handler. Your custom control is missing that mechanism and this is the problem.
Let's apply that and create a simple check box control.
[DefaultProperty("Checked")]
[DefaultEvent("CheckedChanged")]
[DesignerCategory("Code")]
[ToolboxBitmap(typeof(CheckBox))]
public class NowyCheckBox : Control
{
Class members to keep:
The bounds of the check box and text areas.
The points of a check mark.
The current mouse state.
private enum MouseState : int
{
None,
Over,
Down
}
private Rectangle ChkRec, TxtRec;
private PointF[] ChkPoints;
private MouseState mouseState = MouseState.None;
In the constructor, set the shown below styles to reduce the flickering and to use the control in a container with a background image or gradient colors.
public NowyCheckBox() : base()
{
SetStyle(
ControlStyles.SupportsTransparentBackColor |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
UpdateStyles();
}
Add a method to create the drawing objects. Call it from the setters of the relevant properties
(such as CheckBoxSize) and methods (like OnSizeChanged) as shown next.
private void ResetDrawingObjects()
{
if (Width == 0 || Height == 0) return;
ChkRec = new Rectangle(new Point(0, (Height - checkBoxSize.Height) / 2), CheckBoxSize);
TxtRec = new Rectangle(ChkRec.Right + 5, 0, Width - ChkRec.Right - 10, Height);
if (this.RightToLeft == RightToLeft.Yes)
{
ChkRec.X = Width - 1 - ChkRec.Width;
TxtRec.X = ChkRec.X - 5 - TxtRec.Width;
}
var r = Rectangle.Inflate(ChkRec, -3, -3);
ChkPoints = new[]
{
new PointF(r.X, r.Y + (r.Height / 2f)),
new PointF(r.X + (r.Width / 2f) - 1, r.Bottom),
new PointF(r.Right, r.Y)
};
Invalidate();
}
Create a custom event and raise it whenever the Checked property changes.
public event EventHandler CheckedChanged;
protected virtual void OnCheckedChanged(EventArgs e) => CheckedChanged?.Invoke(this, e);
Add the properties.
private bool _checked;
[DefaultValue(false)]
public bool Checked
{
get => _checked;
set
{
if (_checked != value)
{
_checked = value;
Invalidate();
OnCheckedChanged(new EventArgs());
}
}
}
private Size checkBoxSize = new Size(12, 12);
[DefaultValue(typeof(Size), "12, 12")]
public Size CheckBoxSize
{
get => checkBoxSize;
set { checkBoxSize = value; ResetDrawingObjects(); }
}
// Add the rest...
Override the following methods to:
Update the mouseState variable and refresh the drawing by calling the Invalidate method if needed.
Call the ResetDrawingObjects method whenever the drawing objects need to resize and/or reposition.
Like the default CheckBox control, toggle the Checked property on Keys.Space press.
Draw the control.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (mouseState != MouseState.Over)
{
mouseState = MouseState.Over;
// Pass ChkRec to update the checkbox area only.
Invalidate(ChkRec);
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left)
{
Focus();
mouseState = MouseState.Down;
Invalidate(ChkRec);
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
Checked = !Checked;
mouseState = MouseState.Over;
Invalidate(ChkRec);
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
mouseState = MouseState.None;
Invalidate(ChkRec);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.Space) Checked = !Checked;
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Invalidate();
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
ResetDrawingObjects();
}
protected override void OnRightToLeftChanged(EventArgs e)
{
base.OnRightToLeftChanged(e);
ResetDrawingObjects();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
// Get your colors to fill and draw...
var clr = mouseState == MouseState.Down
? // down color...
: mouseState == MouseState.Over
? // over color...
: // default color...;
using (var pn = new Pen(clr)) g.DrawRectangle(pn, ChkRec);
if (Checked)
{
clr = mouseState == MouseState.Down
? // down color...
: mouseState == MouseState.Over
? // over color...
: // default color...;
using (var pn = new Pen(clr, 2))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawLines(pn, ChkPoints);
g.SmoothingMode = SmoothingMode.None;
}
/*
* In case you prefere drawing glyphs. Example:
using (var fnt = new Font("Marlett", 12))
{
var r = ChkRec;
r.Offset(2, -2);
TextRenderer.DrawText(g, "a", fnt, r, clr,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter);
}
*/
}
var flags = TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis;
if (this.RightToLeft == RightToLeft.Yes)
flags |= TextFormatFlags.Right;
else
flags |= TextFormatFlags.Left;
TextRenderer.DrawText(g, Text, Font, TxtRec, ForeColor, flags);
}
}
Put it together, rebuild, drop an instance, double click on it to subscribe to the default event CheckedChanged, and handle it as you want.
private void nowyCheckBox1_CheckedChanged(object sender, EventArgs e)
{
label2.Text = nowyCheckBox1.Checked
? "Jestem włączony"
: "Jestem wyłączony";
}
If you'd like to read more.
Handle and raise events
C# - Events
I have customized the GroupBox to make it collapsable/expandable.
Code is working fine. But I want to initialize this GroupBox in collapsed form instead of expendable. I tried to set values (m_collapsed = true/false and m_bResizingFromCollapse = true/false)and override OnPaint, but nothing is working.
namespace ABC
{
/// <summary>
/// GroupBox control that provides functionality to
/// allow it to be collapsed.
/// </summary>
[ToolboxBitmap(typeof(CollapsibleGroupBox))]
public partial class CollapsibleGroupBox : GroupBox
{
#region Fields
private Rectangle m_toggleRect = new Rectangle(8, 2, 11, 11);
private Boolean m_collapsed = false;
private Boolean m_bResizingFromCollapse = false;
private const int m_collapsedHeight = 20;
private Size m_FullSize = Size.Empty;
#endregion
#region Events & Delegates
/// <summary>Fired when the Collapse Toggle button is pressed</summary>
public delegate void CollapseBoxClickedEventHandler(object sender);
public event CollapseBoxClickedEventHandler CollapseBoxClickedEvent;
#endregion
#region Constructor
public CollapsibleGroupBox()
{
InitializeComponent();
}
#endregion
#region Public Properties
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int FullHeight
{
get { return m_FullSize.Height; }
}
[DefaultValue(false), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsCollapsed
{
get { return m_collapsed; }
set
{
if (value != m_collapsed)
{
m_collapsed = value;
if (!value)
// Expand
this.Size = m_FullSize;
else
{
// Collapse
m_bResizingFromCollapse = true;
this.Height = m_collapsedHeight;
m_bResizingFromCollapse = false;
}
foreach (Control c in Controls)
c.Visible = !value;
Invalidate();
}
}
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int CollapsedHeight
{
get { return m_collapsedHeight; }
}
#endregion
#region Overrides
protected override void OnMouseUp(MouseEventArgs e)
{
if (m_toggleRect.Contains(e.Location))
ToggleCollapsed();
else
base.OnMouseUp(e);
}
protected override void OnPaint(PaintEventArgs e)
{
HandleResize();
DrawGroupBox(e.Graphics);
DrawToggleButton(e.Graphics);
}
#endregion
#region Implimentation
void DrawGroupBox(Graphics g)
{
// Get windows to draw the GroupBox
Rectangle bounds = new Rectangle(ClientRectangle.X, ClientRectangle.Y + 6, ClientRectangle.Width, ClientRectangle.Height - 6);
GroupBoxRenderer.DrawGroupBox(g, bounds, Enabled ? GroupBoxState.Normal : GroupBoxState.Disabled);
// Text Formating positioning & Size
StringFormat sf = new StringFormat();
int i_textPos = (bounds.X + 8) + m_toggleRect.Width + 2;
int i_textSize = (int)g.MeasureString(Text, this.Font).Width;
i_textSize = i_textSize < 1 ? 1 : i_textSize;
int i_endPos = i_textPos + i_textSize + 1;
// Draw a line to cover the GroupBox border where the text will sit
g.DrawLine(SystemPens.Control, i_textPos, bounds.Y, i_endPos, bounds.Y);
// Draw the GroupBox text
using (SolidBrush drawBrush = new SolidBrush(Color.FromArgb(0, 70, 213)))
g.DrawString(Text, this.Font, drawBrush, i_textPos, 0);
}
void DrawToggleButton(Graphics g)
{
if (IsCollapsed)
{
Image plusImage = Image.FromFile(Path.Combine(GameManager.sAssetsDir, "plus.bmp"));
g.DrawImage(plusImage, m_toggleRect);
}
else
{
Image minusImage = Image.FromFile(Path.Combine(GameManager.sAssetsDir, "minus.bmp"));
g.DrawImage(minusImage, m_toggleRect);
}
}
void ToggleCollapsed()
{
IsCollapsed = !IsCollapsed;
if (CollapseBoxClickedEvent != null)
CollapseBoxClickedEvent(this);
}
void HandleResize()
{
if (!m_bResizingFromCollapse && !m_collapsed)
m_FullSize = this.Size;
}
#endregion
}
}
I'm adding undo/redo functionality to my graphical editor, but I'm having some issues with undo/redo for move and resize.
The first thing is when I move my Control (Ellipse for example) and I click the undo button the Ellipse goes to another position than were it should go. When I press redo after the undo the Ellipse goes to the correct position.
Here is a link which will make it more clear what I mean: http://gyazo.com/d299fe5e52f08742e7fa2132f6ff9839
The second things which has a lot of agreements with the first thing is when I resize the Control (Ellipse for example) and I click undo the Ellipse has another size than it should have. After clicking redo again it has the right size again.
Here is a link which will make it more clear what I mean: http://gyazo.com/59ecdcba751ff3b8ffd053dd19cd9945
Maybe the problem is in the MoveCommand and ResizeCommand classes, my code is based on this code http://www.codeproject.com/Articles/33384/Multilevel-Undo-and-Redo-Implementation-in-Cshar. In the linked page they use WPF and Thickness is used for moving/resizing. In WinForms there's no Thickness but there's Margin/Padding which I also tried but with no luck. How to make the resizing / moving undo/redo work?
MoveCommand.cs
class MoveCommand : ICommand
{
Control _control;
int _changeOfTop;
int _changeOfLeft;
public MoveCommand(int top, int left, Control control)
{
_control = control;
_changeOfTop = top;
_changeOfLeft = left;
}
public void Execute()
{
_control.Top = _control.Top + _changeOfTop;
_control.Left = _control.Left + _changeOfLeft;
}
public void UnExecute()
{
_control.Top = _control.Top - _changeOfTop;
_control.Left = _control.Left - _changeOfLeft;
}
}
ResizeCommand.cs
class ResizeCommand : ICommand
{
private int _changeOfWidth;
private int _changeOfHeight;
private Control _control;
public ResizeCommand(int width, int height, Control control)
{
_changeOfWidth = width;
_changeOfHeight = height;
_control = control;
}
public void Execute()
{
_control.Height = _control.Height + _changeOfHeight;
_control.Width = _control.Width + _changeOfWidth;
}
public void UnExecute()
{
_control.Height = _control.Height - _changeOfHeight;
_control.Width = _control.Width - _changeOfWidth;
}
}
Ellipse.cs
class Ellipse : Control
{
private Point mDown { get; set; }
private Point conLoc { get; set; }
private bool userResizing = false;
private Form1 form =null;
public Ellipse(Form1 form1)
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.Transparent;
this.DoubleBuffered = true;
this.ResizeRedraw = true;
this.form = form1;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Draw a black ellipse in the rectangle represented by the control.
e.Graphics.FillEllipse(Brushes.Black, 0, 0, Width, Height);
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
mDown = e.Location;
conLoc = this.Location;
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (this.Location != conLoc)
{
Console.WriteLine("You moved me");
this.form.InsertInUnDoRedoForMove(conLoc.Y, conLoc.X, this);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
// Call MyBase.OnMouseMove to activate the delegate.
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left)
{
Location = new Point(e.X + Left - mDown.X, e.Y + Top - mDown.Y);
}
}
/* Allow resizing at the bottom right corner */
protected override void WndProc(ref Message m)
{
const int wmNcHitTest = 0x84;
const int htBottomLeft = 16;
const int htBottomRight = 17;
const int WM_EXITSIZEMOVE = 0x232;
const int WM_NCLBUTTONDWN = 0xA1;
if (m.Msg == WM_NCLBUTTONDWN)
{
if (!userResizing)
{
userResizing = true;
Console.WriteLine("Start Resizing");
this.form.InsertInUnDoRedoForResize(this.Width, this.Height, this);
}
}
else if (m.Msg == WM_EXITSIZEMOVE)
{
if (userResizing)
{
userResizing = false;
Console.WriteLine("Finish Resizing");
}
}
else if (m.Msg == wmNcHitTest)
{
int x = (int)(m.LParam.ToInt64() & 0xFFFF);
int y = (int)((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
Point pt = PointToClient(new Point(x, y));
Size clientSize = ClientSize;
if (pt.X >= clientSize.Width - 16 &&
pt.Y >= clientSize.Height - 16 &&
clientSize.Height >= 16)
{
m.Result = (IntPtr)(IsMirrored ? htBottomLeft : htBottomRight);
return;
}
}
base.WndProc(ref m);
}
}
Form1.cs
public partial class Form1 : Form
{
private bool draw;
private int x, y, xe, ye;
private Stack<ICommand> _Undocommands = new Stack<ICommand>();
private Stack<ICommand> _Redocommands = new Stack<ICommand>();
public Form1()
{
InitializeComponent();
menuComboBoxShape.ComboBox.DataSource = Enum.GetValues(typeof(Item));
}
public enum Item
{
Pencil,
Rectangle,
Ellipse,
}
private void panel_MouseDown(object sender, MouseEventArgs e)
{
draw = true;
x = e.X;
y = e.Y;
}
private void panel_MouseUp(object sender, MouseEventArgs e)
{
draw = false;
xe = e.X;
ye = e.Y;
Item item;
Enum.TryParse<Item>(menuComboBoxShape.ComboBox.SelectedValue.ToString(), out item);
switch (item)
{
case Item.Pencil:
using (Graphics g = panel.CreateGraphics())
using (var pen = new Pen(System.Drawing.Color.Black)) //Create the pen used to draw the line (using statement makes sure the pen is disposed)
{
g.DrawLine(pen, new Point(x, y), new Point(xe, ye));
}
break;
case Item.Rectangle:
var box = new Box(this);
panel.Controls.Add(box);
box.Location = new Point(x, y);
box.Width = (xe - x);
box.Height = (ye - y);
//InsertCommand boxcmd = new InsertCommand(box, panel);
InsertInUnDoRedoForInsert(box);
break;
case Item.Ellipse:
var el = new Ellipse(this);
panel.Controls.Add(el);
el.Location = new Point(x, y);
el.Width = (xe - x);
el.Height = (ye - y);
//InsertCommand elcmd = new InsertCommand(el,panel);
InsertInUnDoRedoForInsert(el);
break;
default:
break;
}
}
private void undoButton_Click(object sender, EventArgs e)
{
Undo(1);
}
private void redoButton_Click(object sender, EventArgs e)
{
Redo(1);
}
private void clearAllButton_Click(object sender, EventArgs e)
{
commandList.Clear();
current = 0;
panel.Controls.Clear();
}
public void Redo(int levels)
{
for (int i = 1; i <= levels; i++)
{
if (_Redocommands.Count != 0)
{
ICommand command = _Redocommands.Pop();
command.Execute();
_Undocommands.Push(command);
}
Invalidate();
}
}
public void Undo(int levels)
{
for (int i = 1; i <= levels; i++)
{
if (_Undocommands.Count != 0)
{
ICommand command = _Undocommands.Pop();
command.UnExecute();
_Redocommands.Push(command);
}
Invalidate();
}
}
#region UndoHelperFunctions
public void InsertInUnDoRedoForInsert(Control control)
{
ICommand cmd = new InsertCommand(control, panel,shapeTreeView);
_Undocommands.Push(cmd); _Redocommands.Clear();
}
public void InsertInUnDoRedoForResize
(int width, int height, Control control)
{
ICommand cmd = new ResizeCommand(width, height, control);
_Undocommands.Push(cmd); _Redocommands.Clear();
}
public void InsertInUnDoRedoForMove
(int top, int left, Control control)
{
ICommand cmd = new MoveCommand(top,left,control);
_Undocommands.Push(cmd); _Redocommands.Clear();
}
#endregion
}
Can anyone point me to a good implementation of a basic Windows Forms TextBox that will initially show watermark text that disappears when the cursor enters it? I think I can create my own with some creative use of Enter and Leave events, but I'm sure there's a perfectly usable implementation sitting around somewhere. I saw the WPF implementation and if necessary I could nest it, but a native WinForms TextBox derivative would be better.
I have this so far; haven't tried it yet but does anyone see any glaring problems?
public class WatermarkTextBox:TextBox
{
public string WatermarkText { get; set; }
public Color WatermarkColor { get; set; }
private Color TextColor { get; set; }
private bool isInTransition;
public WatermarkTextBox()
{
WatermarkColor = SystemColors.GrayText;
}
private bool HasText { get { return Text.IsNotNullOrBlankOr(WatermarkText); }}
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);
if (HasText) return;
isInTransition = true;
ForeColor = TextColor;
Text = String.Empty;
isInTransition = false;
}
protected override void OnForeColorChanged(EventArgs e)
{
base.OnForeColorChanged(e);
if (!isInTransition) //the change came from outside
TextColor = ForeColor;
}
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
if (HasText) return;
isInTransition = true;
ForeColor = WatermarkColor;
Text = WatermarkText.EmptyIfNull();
isInTransition = false;
}
}
EDIT: The above would have eventually worked with some finessing, but the CueProvider worked much better. Here's my final implementation:
public class WatermarkTextBox:TextBox
{
private string watermarkText;
public string WatermarkText
{
get { return watermarkText; }
set
{
watermarkText = value;
if (watermarkText.IsNullOrBlank())
CueProvider.ClearCue(this);
else
CueProvider.SetCue(this, watermarkText);
}
}
}
I could have integrated the CueProvider functionality completely, but this works beautifully.
The official term is "cue banner". Here's another way to do it, just inheriting TextBox gets the job done too. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox and set the Cue property.
You get a live preview of the Cue value in the designer, localized to the form's Language property. Lots of bang for very little buck, an excellent demonstration of the good parts of Winforms.
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class CueTextBox : TextBox {
[Localizable(true)]
public string Cue {
get { return mCue; }
set { mCue = value; updateCue(); }
}
private void updateCue() {
if (this.IsHandleCreated && mCue != null) {
SendMessage(this.Handle, 0x1501, (IntPtr)1, mCue);
}
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
updateCue();
}
private string mCue;
// PInvoke
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, string lp);
}
I've updated the answer given by #Hans Passant above to introduce constants, make it consistent with pinvoke.net definitions and to let the code pass FxCop validation.
class CueTextBox : TextBox
{
private static class NativeMethods
{
private const uint ECM_FIRST = 0x1500;
internal const uint EM_SETCUEBANNER = ECM_FIRST + 1;
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, string lParam);
}
private string _cue;
public string Cue
{
get
{
return _cue;
}
set
{
_cue = value;
UpdateCue();
}
}
private void UpdateCue()
{
if (IsHandleCreated && _cue != null)
{
NativeMethods.SendMessage(Handle, NativeMethods.EM_SETCUEBANNER, (IntPtr)1, _cue);
}
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
UpdateCue();
}
}
Edit: update the PInvoke call to set CharSet attribute, to err on the safe side. For more info see the SendMessage page at pinvoke.net.
.NET 5.0+, .NET Core 3.0+
You can use PlaceholderText property. It supports both multi-line and single-line text-box.
textBox1.PlaceholderText = "Something";
If you look into the .NET CORE implementation of TextBox you see it's been implemented by handling paint message.
.NET Framework
Here is an implementation of a TextBox which supports showing hint (or watermark or cue):
It also shows the hint when MultiLine is true.
It's based on handling WM_PAINT message and drawing the hint. So you can simply customize the hint and add some properties like hint color, or you can draw it right to left or control when to show the hint.
using System.Drawing;
using System.Windows.Forms;
public class ExTextBox : TextBox
{
string hint;
public string Hint
{
get { return hint; }
set { hint = value; this.Invalidate(); }
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0xf)
{
if (!this.Focused && string.IsNullOrEmpty(this.Text)
&& !string.IsNullOrEmpty(this.Hint))
{
using (var g = this.CreateGraphics())
{
TextRenderer.DrawText(g, this.Hint, this.Font,
this.ClientRectangle, SystemColors.GrayText , this.BackColor,
TextFormatFlags.Top | TextFormatFlags.Left);
}
}
}
}
}
If you use EM_SETCUEBANNER, then there will be 2 issues. The text always will be shown in a system default color. Also the text will not be shown when the TextBox is MultiLine.
Using the painting solution, you can show the text with any color that you want. You also can show the watermark when the control is multi-line:
Download
You can clone or download the working example:
Download Zip
Github repository
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);
And the message constants:
private const uint EM_SETCUEBANNER = 0x1501;
private const uint CB_SETCUEBANNER = 0x1703; // minimum supported client Windows Vista, minimum supported server Windows Server 2008
And imho the best way to implement it is as an extension method.
So for the TextBox control the syntax would be:
MyTextBox.CueBanner(false, "Password");
From the code:
public static void CueBanner(this TextBox textbox, bool showcuewhenfocus, string cuetext)
{
uint BOOL = 0;
if (showcuewhenfocus == true) { BOOL = 1; }
SendMessage(textbox.Handle, EM_SETCUEBANNER, (IntPtr)BOOL, cuetext); ;
}
Using WinForms on .NET Core:
This has been greatly simplified in .NET Core. You can directly add placeholder text by modifying the new PlaceholderText Property of the TextBox.
public virtual string PlaceholderText { get; set; }
Note that you would probably still have to edit the ForeColor if you want to get a colored Placeholder Text. The PlaceholderText field is visible when the Text field is null or empty.
You can add a watermark to a Textbox (multiline or not) that works pretty well by drawing it in different controls Paint event. For Example:
Private Sub Panel1_Paint(sender As Object, e As PaintEventArgs) Handles Panel1.Paint
If TextBox1.Text = "" Then
TextBox1.CreateGraphics.DrawString("Enter Text Here", Me.Font, New SolidBrush(Color.LightGray), 0, 0)
End If
End Sub
-OO-
I wrote a reusable custom control class for my project.
maybe it can help someone that need to implement multiple placeholder textboxes in his project.
here is the C# and vb.net versions:
C#:
namespace reusebleplaceholdertextbox
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// implementation
CustomPlaceHolderTextbox myCustomTxt = new CustomPlaceHolderTextbox(
"Please Write Text Here...", Color.Gray, new Font("ARIAL", 11, FontStyle.Italic)
, Color.Black, new Font("ARIAL", 11, FontStyle.Regular)
);
myCustomTxt.Multiline = true;
myCustomTxt.Size = new Size(200, 50);
myCustomTxt.Location = new Point(10, 10);
this.Controls.Add(myCustomTxt);
}
}
class CustomPlaceHolderTextbox : System.Windows.Forms.TextBox
{
public string PlaceholderText { get; private set; }
public Color PlaceholderForeColor { get; private set; }
public Font PlaceholderFont { get; private set; }
public Color TextForeColor { get; private set; }
public Font TextFont { get; private set; }
public CustomPlaceHolderTextbox(string placeholdertext, Color placeholderforecolor,
Font placeholderfont, Color textforecolor, Font textfont)
{
this.PlaceholderText = placeholdertext;
this.PlaceholderFont = placeholderfont;
this.PlaceholderForeColor = placeholderforecolor;
this.PlaceholderFont = placeholderfont;
this.TextForeColor = textforecolor;
this.TextFont = textfont;
if (!string.IsNullOrEmpty(this.PlaceholderText))
{
SetPlaceHolder(true);
this.Update();
}
}
private void SetPlaceHolder(bool addEvents)
{
if (addEvents)
{
this.LostFocus += txt_lostfocus;
this.Click += txt_click;
}
this.Text = PlaceholderText;
this.ForeColor = PlaceholderForeColor;
this.Font = PlaceholderFont;
}
private void txt_click(object sender, EventArgs e)
{
// IsNotFirstClickOnThis:
// if there is no other control in the form
// we will have a problem after the first load
// because we dont other focusable control to move the focus to
// and we dont want to remove the place holder
// only on first time the place holder will be removed by click event
RemovePlaceHolder();
this.GotFocus += txt_focus;
// no need for this event listener now
this.Click -= txt_click;
}
private void RemovePlaceHolder()
{
this.Text = "";
this.ForeColor = TextForeColor;
this.Font = TextFont;
}
private void txt_lostfocus(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(this.Text))
{
// set placeholder again
SetPlaceHolder(false);
}
}
private void txt_focus(object sender, EventArgs e)
{
if (this.Text == PlaceholderText)
{
// IsNotFirstClickOnThis:
// if there is no other control in the form
// we will have a problem after the first load
// because we dont other focusable control to move the focus to
// and we dont want to remove the place holder
RemovePlaceHolder();
}
}
}
}
VB.NET:
Namespace CustomControls
Public Class PlaceHolderTextBox
Inherits System.Windows.Forms.TextBox
Public Property PlaceholderText As String
Public Property PlaceholderForeColor As Color
Public Property PlaceholderFont As Font
Public Property TextForeColor As Color
Public Property TextFont As Font
Public Sub New(ByVal placeholdertext As String, ByVal placeholderforecolor As Color, ByVal placeholderfont As Font, ByVal txtboxbackcolor As Color, ByVal textforecolor As Color, ByVal textfont As Font)
Me.PlaceholderText = placeholdertext
Me.PlaceholderFont = placeholderfont
Me.PlaceholderForeColor = placeholderforecolor
Me.PlaceholderFont = placeholderfont
Me.TextForeColor = textforecolor
Me.TextFont = textfont
Me.BackColor = txtboxbackcolor
If Not String.IsNullOrEmpty(Me.PlaceholderText) Then
SetPlaceHolder(True)
Me.Update()
End If
End Sub
Private Sub SetPlaceHolder(ByVal addEvents As Boolean)
If addEvents Then
AddHandler Me.LostFocus, AddressOf txt_lostfocus
AddHandler Me.Click, AddressOf txt_click
End If
Me.Text = PlaceholderText
Me.ForeColor = PlaceholderForeColor
Me.Font = PlaceholderFont
End Sub
Private Sub txt_click(ByVal sender As Object, ByVal e As EventArgs)
RemovePlaceHolder()
AddHandler Me.GotFocus, AddressOf txt_focus
RemoveHandler Me.Click, AddressOf txt_click
End Sub
Private Sub RemovePlaceHolder()
Me.Text = ""
Me.ForeColor = TextForeColor
Me.Font = TextFont
End Sub
Private Sub txt_lostfocus(ByVal sender As Object, ByVal e As EventArgs)
If String.IsNullOrEmpty(Me.Text) Then
SetPlaceHolder(False)
End If
End Sub
Private Sub txt_focus(ByVal sender As Object, ByVal e As EventArgs)
If Me.Text = PlaceholderText Then
RemovePlaceHolder()
End If
End Sub
End Class
End Namespace
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace PlaceHolderTextBoxCSharp
{
public class CTextBox : TextBox
{
private Panel contenedor;
protected string texto = "PlaceHolderText";
protected Color colorTextoDefault = Color.Gray;
public Color colorTexto = Color.Gray;
protected Color colorTextoObligatorio = Color.Red;
private Font fuente;
private SolidBrush establecerColorTexto;
private bool obligatoriedad = false;
private bool colorConFoco = false;
private int vuelta = 0;
public CTextBox()
{
Inicializar();
}
private void Inicializar()
{
fuente = Font;
CharacterCasing = CharacterCasing.Upper;
contenedor = null;
MuestraPlaceHolder();
Leave += new EventHandler(PierdeFoco);
TextChanged += new EventHandler(CambiaTexto);
}
private void EliminaPlaceHolder()
{
if (contenedor != null)
{
Controls.Remove(contenedor);
contenedor = null;
}
}
private void MuestraPlaceHolder()
{
if (contenedor == null && TextLength <= 0)
{
contenedor = new Panel();
contenedor.Paint += new PaintEventHandler(contenedorPaint);
contenedor.Invalidate();
contenedor.Click += new EventHandler(contenedorClick);
Controls.Add(contenedor);
}
}
private void contenedorClick(object sender, EventArgs e)
{
Focus();
}
private void contenedorPaint(object sender, PaintEventArgs e)
{
contenedor.Location = new Point(2, 0);
contenedor.Height = Height;
contenedor.Width = Width;
contenedor.Anchor = AnchorStyles.Left | AnchorStyles.Right;
establecerColorTexto = new SolidBrush(colorTexto);
Graphics g = e.Graphics;
g.DrawString(texto, fuente, establecerColorTexto, new PointF(-1f, 1f));
}
private void PierdeFoco(object sender, EventArgs e)
{
if (TextLength > 0)
{
EliminaPlaceHolder();
}
else
{
if (obligatoriedad == true)
{
colorTexto = colorTextoObligatorio;
}
else
{
colorTexto = colorTextoDefault;
}
Invalidate();
}
}
private void CambiaTexto(object sender, EventArgs e)
{
if (TextLength > 0)
{
EliminaPlaceHolder();
}
else
{
MuestraPlaceHolder();
vuelta += 1;
if (vuelta >= 1 && obligatoriedad == true)
{
colorTexto = colorTextoObligatorio;
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
MuestraPlaceHolder();
}
protected override void OnInvalidated(InvalidateEventArgs e)
{
base.OnInvalidated(e);
if (contenedor != null)
{
contenedor.Invalidate();
}
}
[Category("Atributos PlaceHolder")]
[Description("Establece el texto a mostrar.")]
public string PlaceHolderText
{
get
{
return texto;
}
set
{
texto = value;
Invalidate();
}
}
[Category("Atributos PlaceHolder")]
[Description("Establece el estilo de fuente del PlaceHolder.")]
public Font PlaceHolderFont
{
get
{
return fuente;
}
set
{
fuente = value;
Invalidate();
}
}
[Category("Atributos PlaceHolder")]
[Description("Indica si el campo es obligatorio.")]
public bool PlaceHolderFieldRequired
{
get
{
return obligatoriedad;
}
set
{
obligatoriedad = value;
Invalidate();
}
}
}
}
With .Net Core 3 a property was introduced into TextBox: PlaceHolderText
If one is going to need this in a FrameWork application the required code parts can be taken from the official open source code and placed in a TextBox descendant (see license).
This supports multiline TextBox and also RTL text.
public class PlaceHolderTextBox : TextBox
{
private const int WM_KILLFOCUS = 0x0008;
private const int WM_PAINT = 0x000F;
private string _placeholderText;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (this.ShouldRenderPlaceHolderText(m))
{
using Graphics g = this.CreateGraphics();
this.DrawPlaceholderText(g);
}
}
#region PlaceHolder
/// <summary>
/// Gets or sets the text that is displayed when the control has no text and does not have the focus.
/// </summary>
/// <value>The text that is displayed when the control has no text and does not have the focus.</value>
[Localizable(true), DefaultValue("")]
public virtual string PlaceholderText
{
get => _placeholderText;
set
{
if (value == null)
{
value = string.Empty;
}
if (_placeholderText != value)
{
_placeholderText = value;
if (this.IsHandleCreated)
{
this.Invalidate();
}
}
}
}
//-------------------------------------------------------------------------------------------------
/// <summary>
/// Draws the <see cref="PlaceholderText"/> in the client area of the <see cref="TextBox"/> using the default font and color.
/// </summary>
private void DrawPlaceholderText(Graphics graphics)
{
TextFormatFlags flags = TextFormatFlags.NoPadding | TextFormatFlags.Top |
TextFormatFlags.EndEllipsis;
Rectangle rectangle = this.ClientRectangle;
if (this.RightToLeft == RightToLeft.Yes)
{
flags |= TextFormatFlags.RightToLeft;
switch (this.TextAlign)
{
case HorizontalAlignment.Center:
flags |= TextFormatFlags.HorizontalCenter;
rectangle.Offset(0, 1);
break;
case HorizontalAlignment.Left:
flags |= TextFormatFlags.Right;
rectangle.Offset(1, 1);
break;
case HorizontalAlignment.Right:
flags |= TextFormatFlags.Left;
rectangle.Offset(0, 1);
break;
}
}
else
{
flags &= ~TextFormatFlags.RightToLeft;
switch (this.TextAlign)
{
case HorizontalAlignment.Center:
flags |= TextFormatFlags.HorizontalCenter;
rectangle.Offset(0, 1);
break;
case HorizontalAlignment.Left:
flags |= TextFormatFlags.Left;
rectangle.Offset(1, 1);
break;
case HorizontalAlignment.Right:
flags |= TextFormatFlags.Right;
rectangle.Offset(0, 1);
break;
}
}
TextRenderer.DrawText(graphics, this.PlaceholderText, this.Font, rectangle, SystemColors.GrayText, this.BackColor, flags);
}
private bool ShouldRenderPlaceHolderText(in Message m) =>
!string.IsNullOrEmpty(this.PlaceholderText) &&
(m.Msg == WM_PAINT || m.Msg == WM_KILLFOCUS) &&
!this.GetStyle(ControlStyles.UserPaint) &&
!this.Focused && this.TextLength == 0;
#endregion
}
Update 30/12/2022
With .NET6 (or maybe lower version have this option, I'm not sure)
Just right-click on textbox > properties > PlaceHolderText > and set it as anything you want
click here to see that property in picture
Private Sub randomSubName() Handles txtWatermark.Click
txtWatermark.text = ""
End Sub
Make the default text of the textbox whatever you want the watermark to be, I assume in this example you name the textbox txtWatermark
Hey, I'm new. So sorry if I terribly screwed up the post... I also have no idea if this works...