Tooltip Text going off screen (WinForms) - c#

I've drawn on a Tool Tip control - The code is below.
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
ToolTip tt = (sender as ToolTip);
string toolTipText = tt.GetToolTip(e.AssociatedControl);
e.ToolTipSize = TextRenderer.MeasureText(toolTipText, new Font("Arial", 16.0f));
}
private void toolTip1_Draw(object sender, DrawToolTipEventArgs e) => DrawToolTip(sender, e);
[System.Runtime.InteropServices.DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr h, int x, int y, int width, int height, bool redraw);
private void DrawToolTip(object sender, DrawToolTipEventArgs e)
{
ToolTip tt = (sender as ToolTip);
string toolTipText = tt.GetToolTip(e.AssociatedControl);
Font f = new Font("Arial", 16.0f);
e.DrawBackground();
e.DrawBorder();
toolTipText = e.ToolTipText;
e.Graphics.DrawString(e.ToolTipText, f, Brushes.Black, new PointF(2, 2));
}
And then to set the ToolTip text:
toolTip1.SetToolTip(btnLogin, "Some text.....");
Additionally, here is an image of what is happening.
It looks alright, but it has come to my attention that if the text is a certain length, the tooltip will go off-screen. Is there anyway to prevent that? I would rather not have to add Environment.NewLine(); or \n etc, since I would need to do that for MANY strings.

If I understand your question correctly, you are trying to combine the two solutions posted here and here to mainly resize the ToolTip window with the screen width, and move it to Point(2, 2), screen coordinates.
If that is what you need, you need to modify the source codes a bit to set the right e.ToolTipSize in the Popup event, and as the gentlemen commented above, draw the string in a rectangle, the e.Bounds property in the Draw event.
using System;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public partial class Form1 : Form
{
TextFormatFlags flags = TextFormatFlags.VerticalCenter
| TextFormatFlags.Left
| TextFormatFlags.LeftAndRightPadding
| TextFormatFlags.NoClipping
| TextFormatFlags.WordBreak;
public Form1()
{
InitializeComponent();
}
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
var tt = sender as ToolTip;
var toolTipText = tt.GetToolTip(e.AssociatedControl);
var screen = Screen.FromControl(e.AssociatedControl).WorkingArea;
using (var g = e.AssociatedControl.CreateGraphics())
using (var font = new Font("Arial", 16))
{
var sz = TextRenderer.MeasureText(
g, toolTipText, font, screen.Size, flags);
e.ToolTipSize = new Size(screen.Width - 2, sz.Height + 10);
}
}
private void toolTip1_Draw(object sender, DrawToolTipEventArgs e)
{
var t = sender as ToolTip;
var h = (IntPtr)t.GetType().GetProperty("Handle",
BindingFlags.NonPublic | BindingFlags.Instance).GetValue(t);
MoveWindow(h, 2, 2, e.Bounds.Width - 2, e.Bounds.Height, false);
e.DrawBackground();
e.DrawBorder();
using (var font = new Font("Arial", 16))
TextRenderer.DrawText(e.Graphics, e.ToolTipText, font,
e.Bounds, Color.Black, flags);
}
[DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr h, int x, int y,
int width, int height, bool redraw);
}

Related

WinForms TreeView glitch when expanding

I've extended WinForms TreeView in C# in order to draw my own text (I'm drawing a shadowed font with black under white) and everything looks okay apart from when I expand a node with a large number of children (by large I'm only talking 20+). I've tried to capture what happens but it's so quick I've been unsuccessful so I'll try to explain it.
The TreeView has around 15 1st level nodes in it and when I open one of the 20+ children nodes, I get a glitch where it displays the node under the one I've clicked multiple times (looks like the same amount of times as the children nodes that subsequently appear) for an instant and then displays all the child nodes. It's very frustrating, mostly because I can't find anything online similar to this.
I've set the TreeView to use double buffering but although it covers lots of flicking, it doesn't cover this glitch.
Here's my TreeView class:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Liq.CommonControls.Tree
{
public partial class LiqTreeView : TreeView
{
private Brush backBrush, highlightBrush, blackBrush, textBrush;
private Font drawFont;
private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44;
private const int TVM_GETEXTENDEDSTYLE = 0x1100 + 45;
private const int TVS_EX_DOUBLEBUFFER = 0x0004;
private const int WM_ERASEBKGND = 0x0014;
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
public LiqTreeView()
{
InitializeComponent();
this.DrawNode += LiqTreeView_DrawNode;
BackColor = Color.FromArgb(255, 105, 105, 105);
backBrush = new SolidBrush(this.BackColor);
highlightBrush = new SolidBrush(Color.LightGray);
blackBrush = new SolidBrush(Color.Black);
textBrush = new SolidBrush(Color.WhiteSmoke);
drawFont = new Font("Arial", 14, FontStyle.Bold, GraphicsUnit.Pixel);
}
protected override void OnHandleCreated(EventArgs e)
{
SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)TVS_EX_DOUBLEBUFFER, (IntPtr)TVS_EX_DOUBLEBUFFER);
base.OnHandleCreated(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_ERASEBKGND)
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
private void LiqTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (e.Bounds.Height < 1 || e.Bounds.Width < 1) return;
e.DrawDefault = false;
if (!e.Node.IsVisible)
return;
if (e.Node.IsSelected)
e.Graphics.FillRectangle(highlightBrush, e.Bounds);
Rectangle r = e.Bounds;
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Near;
sf.LineAlignment = StringAlignment.Near;
sf.Trimming = StringTrimming.Character;
r.Y -= 2;
r.X += e.Node.Level * 10;
TextRenderer.DrawText(e.Graphics, e.Node.Text, drawFont, (Rectangle)r, Color.Black, TextFormatFlags.Left|TextFormatFlags.EndEllipsis);
r.Y -= 1;
r.X += 1;
TextRenderer.DrawText(e.Graphics, e.Node.Text, drawFont, (Rectangle)r, Color.WhiteSmoke, TextFormatFlags.Left | TextFormatFlags.EndEllipsis);
}
}
}

(C# Winforms) Drawing a circle on a TextBox

Sorry for my poor English.
I have a user control which includes two text boxes. I wanna draw a circle over that.
I tried to use a transparent panel like below. (This code is from Drawing circles on top of a form)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void DrawCircle(int x, int y, int transparency, Graphics graphics)
{
if (transparency < 0)
transparency = 0;
else if (transparency > 255)
transparency = 255;
Pen pen = new Pen(Color.Red, 5)
graphics.DrawEllipse(pen, new Rectangle(x, y, 90, 90));
pen.Dispose();
graphics.Dispose();
}
private void TransparentPanel1_Paint(object sender, PaintEventArgs e)
{
DrawCircle(10, 10, 255, e.Graphics);
}
private void Form1_Load(object sender, EventArgs e)
{
transparentPanel1.Enabled = false;
transparentPanel1.Paint += TransparentPanel1_Paint;
transparentPanel1.BringToFront();
}
}
public class TransparentPanel : Panel
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e);
}
}
However, it doesn't work.
When I use normal panel rather than transparent panel, the background color covers the entire textbox so I can't see the text. I don't want that.
I don't need editing text when the circle appeared, so this textbox can be replaced with label. (But I still need editing text when the circle doesn't exist.)
How can i draw a circle on a textbox? (Circle can be replaced with 'Circle Image file'. But the background of circle still need to be transparent.)
You can do this by setting the Region property as a ring.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace WinForm
{
public partial class Form1 : Form
{
TextBox textBox1;
TextBox textBox2;
Panel circle;
public Form1()
{
//InitializeComponent();
textBox1 = new TextBox { Parent = this, Width = 100, Left = 20, Top = 20, Height = 80, AutoSize = false };
textBox2 = new TextBox { Parent = this, Width = 100, Left = 20, Top = textBox1.Bottom };
ShowCircle();
}
void ShowCircle()
{
circle = new Panel
{
Parent = this,
BackColor = Color.Red,
Top = textBox1.Top,
Left = textBox1.Left,
Width = textBox1.Width,
Height = textBox1.Height + textBox2.Height
};
using (var path = new GraphicsPath())
{
var rect = new Rectangle(0, 0, circle.Width, circle.Height);
path.AddEllipse(rect);
rect.Inflate(-5, -5);
path.AddEllipse(rect);
circle.Region = new Region(path);
circle.BringToFront();
}
}
}
}
I have had a similiar problem and the best solution I came up with was to make a screenshot from the parent panel and show the image in a another panel. This panel was made visible while the other one containing all controls was made invisible. I used this to show a loading screen without using a modal form.
I found the code in an old VB.NET application and hope that the translated code works:
class NativeMethods
{
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
internal static Image PrintControl(Control ctrl)
{
using (Graphics controlGraphics = ctrl.CreateGraphics())
{
Bitmap bmp = new Bitmap(ctrl.Size.Width, ctrl.Size.Height, controlGraphics);
using (Graphics bmpGraphics = Graphics.FromImage(bmp))
{
IntPtr dc = bmpGraphics.GetHdc();
PrintWindow(ctrl.Handle, dc, 0);
bmpGraphics.ReleaseHdc(dc);
return bmp;
}
}
}
}
Given you have Panel1 which contains the TextBox controls and Panel2 as a overlay (that contains the screenshot with the red circle) you could use a code like this:
private void ShowRedCircle()
{
Image bmp = NativeMethods.PrintControl(this.panel1);
using (Graphics bmpGraphics = Graphics.FromImage(bmp))
using (Pen sPen = new Pen(Color.Red))
{
bmpGraphics.DrawEllipse(sPen, new Rectangle(10, 10, 90, 90));
this.panel2.BackgroundImage = bmp;
}
this.panel2.Visible = true;
this.panel1.Visible = false;
}
When you want to remove the circle just change the visibility of the panels again. You should consider to dispose the BackgroundImage of panel2 in this case.

How to increase ToolTip Rectangle size

I am currently implementing a tooltip which has at least two sentences worth inside of it, so I need to somehow create a large rectangle which would hold it.
My issue is the height of the rectangle.
Snip:
As you can see the green rectangle does not have the required size.
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Discounting.Module
{
public partial class Benefits : UserControl
{
public Benefits()
{
InitializeComponent();
}
private void ToolTip1_Draw(object sender, DrawToolTipEventArgs e)
{
var newEventArgs = new DrawToolTipEventArgs(
e.Graphics,
e.AssociatedWindow,
e.AssociatedControl,
e.Bounds, e.ToolTipText,
this.BackColor,
this.ForeColor,
Font);
DrawToolTip(e);
}
private void DrawToolTip(DrawToolTipEventArgs e)
{
using (var sf = new StringFormat())
{
sf.LineAlignment = StringAlignment.Center;
sf.Alignment = StringAlignment.Center;
using (var graphics = e.Graphics)
{
var linearGradientBrush = new LinearGradientBrush(new Rectangle(e.Bounds.X, e.Bounds.Y,
8000, 1000), Color.GreenYellow, Color.MintCream, 45f);
graphics.FillRectangle(linearGradientBrush, linearGradientBrush.Rectangle);
graphics.DrawString(e.ToolTipText, new Font("Aerial",12.0f, FontStyle.Bold), Brushes.Silver,
new PointF(linearGradientBrush.Rectangle.X + 6, linearGradientBrush.Rectangle.Y + 6)); // shadow layer
graphics.DrawString(e.ToolTipText, new Font("Aerial",12.0f, FontStyle.Bold), Brushes.Black,
new PointF(linearGradientBrush.Rectangle.X + 5, linearGradientBrush.Rectangle.Y + 5)); // top layer
linearGradientBrush.Dispose();
}
}
}
private void ToolTip2_Draw(object sender, DrawToolTipEventArgs e)
{
DrawToolTip(e);
}
private void ToolTip3_Draw(object sender, DrawToolTipEventArgs e)
{
DrawToolTip(e);
}
private void ToolTip4_Draw(object sender, DrawToolTipEventArgs e)
{
DrawToolTip(e);
}
}
}
If you require further details I would be happy to provide them.
Well, since there might be some quirks when mixing TextRenderer and the Graphics object, here's an example:
The ToolTip.PopUp event provides means to set the Size of the ToolTip rectangle. You just need to measure the Text and set its PopupEventArgs.ToolTipSize property to the measured Size.
This allows to use multi-line strings as well, using Environment.NewLine to separate the lines.
The PopupEventArgs object doesn't provide a Graphics object that can be use to measure the Text. We can use TextRenderer.MeasureText instead.
TextRenderer.MeasureText is very precise: it will give back the exact measure of the Text. Since you are using Graphics.DrawString to draw the Text, we better be generous and add some more space to the measured Width, to avoid text wrapping and also because the Text looks better if the container rectangle is not too tight.
In the Popup event, after measuring the Text, I'm adding 5 pixels to both the Width and Height (Size.Add([Measured Size], new Size(5, 5))). Modify as required
Note:
Here, the Font family and Size are hard-coded. Of course you may want to use a more dynamic Font object, possibly linked to a property of your UserControl. The Font can be changed at any time: the PopUp event will use it to measure the test bounds.
TextFormatFlags toolTipFlags = TextFormatFlags.VerticalCenter |
TextFormatFlags.LeftAndRightPadding | TextFormatFlags.HorizontalCenter | TextFormatFlags.NoClipping;
Font toolTipFont = new Font("Arial", 12.0f, FontStyle.Bold);
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
string toolTipText = (sender as ToolTip).GetToolTip(e.AssociatedControl);
using (var g = e.AssociatedControl.CreateGraphics()) {
var textSize = Size.Add(TextRenderer.MeasureText(
g, toolTipText, toolTipFont, Size.Empty, flags), new Size(10, 5));
e.ToolTipSize = textSize;
}
}
private void toolTip1_Draw(object sender, DrawToolTipEventArgs e) => DrawToolTip(e);
private void DrawToolTip(DrawToolTipEventArgs e)
{
using (var linearGradientBrush = new LinearGradientBrush(e.Bounds, Color.GreenYellow, Color.MintCream, 45f)) {
e.Graphics.FillRectangle(linearGradientBrush, e.Bounds);
}
var shadowBounds = new Rectangle(new Point(e.Bounds.X + 1, e.Bounds.Y + 1), e.Bounds.Size);
TextRenderer.DrawText(e.Graphics, e.ToolTipText, toolTipFont, shadowBounds, Color.LightGray, toolTipFlags);
TextRenderer.DrawText(e.Graphics, e.ToolTipText, toolTipFont, e.Bounds, Color.Black, toolTipFlags);
}

TextBox drawn in WM_PAINT flickers on mouse enter/leave

I have a custom TextBox in which I draw some place holder text when it's empty.
It works pretty well, but it flickers when the mouse enters and leaves the TextBox. It seems related to the border becoming blue when the mouse hovers the control (I'm on Windows 8.1).
Any idea how I could fix this ?
I've tried various SetStyles flags without success.
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center
};
private Font mPlaceHolderFont;
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0F)
{
if (string.IsNullOrEmpty(Text) && !Focused)
{
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var rect = new RectangleF(2, 2, Width - 4, Height - 4);
g.FillRectangle(Brushes.White, rect);
g.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
}
}
}
}
I had other problems with overriding OnPaint. Here is the best solution I came up with :
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near
};
private Font mPlaceHolderFont;
private Brush mForegroundBrush;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var bounds = new Rectangle(-2, -2, Width, Height);
var rect = new RectangleF(1, 0, Width - 2, Height - 2);
e.Graphics.FillRectangle(Brushes.White, rect);
if (string.IsNullOrEmpty(Text) && !Focused)
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
if (mForegroundBrush == null)
mForegroundBrush = new SolidBrush(ForeColor);
e.Graphics.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
else
{
var flags = TextFormatFlags.Default | TextFormatFlags.TextBoxControl;
if (!Multiline)
flags |= TextFormatFlags.SingleLine | TextFormatFlags.NoPadding;
TextBoxRenderer.DrawTextBox(e.Graphics, bounds, Text, Font, flags, TextBoxState.Selected);
}
}
}
Is there a special reason for using WM_PAINT instead of OnPaint? In WM_PAINT you obtain a drawing context from the handle, which is always a direct access to the control. In OnPaint you already have a Graphics in the event args, which can be either a buffer or a direct context, depending on the styles.
You mentioned that you have tried a few styles with no success. Firstly I would say try these and move your paint logic into OnPaint:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
If it does not work (a focused control may behave strangely in Windows) and you must stick to WM_PAINT, then create a buffer manually. Your original code draws a white rectangle first, then some text, which causes flickering. You can avoid this by using a buffer:
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
// creating a buffered context
using (BufferedGraphicsContext context = new BufferedGraphicsContext())
{
// creating a buffer for the original Graphics
using (BufferedGraphics bg = context.Allocate(e.Graphics, ClientRectangle))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var gBuf = bg.Graphics;
var rect = ClientRectangle;
rect.Inflate(-1, -1);
gBuf.FillRectangle(Brushes.White, rect);
gBuf.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
// copying the buffer onto the original Graphics
bg.Render(e.Graphics);
}
}
}

Change the borderColor of the TextBox

How can I change the BorderColor of the Textbox when a user Clicks on it or focuses on it?
You can handle WM_NCPAINT message of TextBox and draw a border on the non-client area of control if the control has focus. You can use any color to draw border:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ExTextBox : TextBox
{
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
private const int WM_NCPAINT = 0x85;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NCPAINT && this.Focused)
{
var dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
g.DrawRectangle(Pens.Red, 0, 0, Width - 1, Height - 1);
}
}
}
}
Result
The painting of borders while the control is focused is completely flicker-free:
BorderColor property for TextBox
In the current post I just change the border color on focus. You can also add a BorderColor property to the control. Then you can change border-color based on your requirement at design-time or run-time. I've posted a more completed version of TextBox which has BorderColor property:
in the following post:
BorderColor property for TextBox
try this
bool focus = false;
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (focus)
{
textBox1.BorderStyle = BorderStyle.None;
Pen p = new Pen(Color.Red);
Graphics g = e.Graphics;
int variance = 3;
g.DrawRectangle(p, new Rectangle(textBox1.Location.X - variance, textBox1.Location.Y - variance, textBox1.Width + variance, textBox1.Height +variance ));
}
else
{
textBox1.BorderStyle = BorderStyle.FixedSingle;
}
}
private void textBox1_Enter(object sender, EventArgs e)
{
focus = true;
this.Refresh();
}
private void textBox1_Leave(object sender, EventArgs e)
{
focus = false;
this.Refresh();
}
This is an ultimate solution to set the border color of a TextBox:
public class BorderedTextBox : UserControl
{
TextBox textBox;
public BorderedTextBox()
{
textBox = new TextBox()
{
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(-1, -1),
Anchor = AnchorStyles.Top | AnchorStyles.Bottom |
AnchorStyles.Left | AnchorStyles.Right
};
Control container = new ContainerControl()
{
Dock = DockStyle.Fill,
Padding = new Padding(-1)
};
container.Controls.Add(textBox);
this.Controls.Add(container);
DefaultBorderColor = SystemColors.ControlDark;
FocusedBorderColor = Color.Red;
BackColor = DefaultBorderColor;
Padding = new Padding(1);
Size = textBox.Size;
}
public Color DefaultBorderColor { get; set; }
public Color FocusedBorderColor { get; set; }
public override string Text
{
get { return textBox.Text; }
set { textBox.Text = value; }
}
protected override void OnEnter(EventArgs e)
{
BackColor = FocusedBorderColor;
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e)
{
BackColor = DefaultBorderColor;
base.OnLeave(e);
}
protected override void SetBoundsCore(int x, int y,
int width, int height, BoundsSpecified specified)
{
base.SetBoundsCore(x, y, width, textBox.PreferredHeight, specified);
}
}
WinForms was never good at this and it's a bit of a pain.
One way you can try is by embedding a TextBox in a Panel and then manage the drawing based on focus from there:
public class BorderTextBox : Panel {
private Color _NormalBorderColor = Color.Gray;
private Color _FocusBorderColor = Color.Blue;
public TextBox EditBox;
public BorderTextBox() {
this.DoubleBuffered = true;
this.Padding = new Padding(2);
EditBox = new TextBox();
EditBox.AutoSize = false;
EditBox.BorderStyle = BorderStyle.None;
EditBox.Dock = DockStyle.Fill;
EditBox.Enter += new EventHandler(EditBox_Refresh);
EditBox.Leave += new EventHandler(EditBox_Refresh);
EditBox.Resize += new EventHandler(EditBox_Refresh);
this.Controls.Add(EditBox);
}
private void EditBox_Refresh(object sender, EventArgs e) {
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(SystemColors.Window);
using (Pen borderPen = new Pen(this.EditBox.Focused ? _FocusBorderColor : _NormalBorderColor)) {
e.Graphics.DrawRectangle(borderPen, new Rectangle(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1));
}
base.OnPaint(e);
}
}
Using OnPaint to draw a custom border on your controls is fine. But know how to use OnPaint to keep efficiency up, and render time to a minimum. Read this if you are experiencing a laggy GUI while using custom paint routines: What is the right way to use OnPaint in .Net applications?
Because the accepted answer of PraVn may seem simple, but is actually inefficient. Using a custom control, like the ones posted in the answers above is way better.
Maybe the performance is not an issue in your application, because it is small, but for larger applications with a lot of custom OnPaint routines it is a wrong approach to use the way PraVn showed.
set Text box Border style to None
then write this code to container form "paint" event
private void Form1_Paint(object sender, PaintEventArgs e)
{
System.Drawing.Rectangle rect = new Rectangle(TextBox1.Location.X,
TextBox1.Location.Y, TextBox1.ClientSize.Width, TextBox1.ClientSize.Height);
rect.Inflate(1, 1); // border thickness
System.Windows.Forms.ControlPaint.DrawBorder(e.Graphics, rect,
Color.DeepSkyBlue, ButtonBorderStyle.Solid);
}
With PictureBox1
.Visible = False
.Width = TextBox1.Width + 4
.Height = TextBox1.Height + 4
.Left = TextBox1.Left - 2
.Top = TextBox1.Top - 2
.SendToBack()
.Visible = True
End With
Here is my complete Flat TextBox control that supports themes including custom border colors in normal and focused states.
The control uses the same concept mentioned by Reza Aghaei https://stackoverflow.com/a/38405319/5514131 ,however the FlatTextBox control is more customizable and flicker-free.
The control handles the WM_NCPAINT window message in a better way to help eliminate flicker.
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = WindowMessage.WM_NCPAINT AndAlso _drawBorder AndAlso Not DesignMode Then 'Draw the control border
Dim w As Integer
Dim h As Integer
Dim clip As Rectangle
Dim hdc As IntPtr
Dim clientRect As RECT = Nothing
GetClientRect(Handle, clientRect)
Dim windowRect As RECT = Nothing
GetWindowRect(Handle, windowRect)
w = windowRect.Right - windowRect.Left
h = windowRect.Bottom - windowRect.Top
clip = New Rectangle(CInt((w - clientRect.Right) / 2), CInt((h - clientRect.Bottom) / 2), clientRect.Right, clientRect.Bottom)
hdc = GetWindowDC(Handle)
Using g As Graphics = Graphics.FromHdc(hdc)
g.SetClip(clip, CombineMode.Exclude)
Using sb = New SolidBrush(BackColor)
g.FillRectangle(sb, 0, 0, w, h)
End Using
Using p = New Pen(If(Focused, _borderActiveColor, _borderNormalColor), BORDER_WIDTH)
g.DrawRectangle(p, 0, 0, w - 1, h - 1)
End Using
End Using
ReleaseDC(Handle, hdc)
Return
End If
MyBase.WndProc(m)
End Sub
I have removed the default BorderStyle property and replaced it with a simple boolean DrawBorder property that controls whether to draw a border around the control or not.
Use the BorderNormalColor property to specify the border color when the TextBox has no focus, and the BorderActiveColor property to specify the border color when the control receives focus.
The FlatTextBox comes with two themes VS2019 Dark and VS2019 Light, use the Theme property to switch between them.
Complete FlatTextBox control code written in VB.NET
https://gist.github.com/ahmedosama007/37fe2004183a51a4ea0b4a6dcb554176

Categories