(C# Winforms) Drawing a circle on a TextBox - c#

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.

Related

How to pass parameters in C# GDI+ Windows Desktop

I'm interested in learning about C# GDI+ and have googled many tutorials. I'm attempting to create a simple windows form that has two textbox controls and a button. I simply want to put a length dimension in one textbox and a height dimension in the other, click the button and have the app draw the rectangle on the window using those two entered dimensions. To date, all I have been able to do is locate tutorials where the rectangle parameters are hardcoded into the Form_Load and Form_Paint. How would I take the user input from the textboxes and pass them to make the app refresh and draw the rectangles on the button click?
Please let me know if more info is needed.
Thanks in advance for your knowledge!
public partial class Form1 : Form
{
Bitmap drwBitmap;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Graphics graphicObj;
drwBitmap = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
graphicObj = Graphics.FromImage(drwBitmap);
graphicObj.Clear(Color.White);
Pen myPen = new Pen(System.Drawing.Color.Red, 3);
Rectangle rectObj;
rectObj = new Rectangle(10, 10, 100, 200);
graphicObj.DrawEllipse(myPen, rectObj);
graphicObj.Dispose();
}
private void Form1_Paint(object sender, PaintEventArgs e, Graphics graphicsObj)
{
Graphics graphicsObj1 = graphicsObj;
graphicsObj1.DrawImage(drwBitmap, 0, 0, drwBitmap.Width, drwBitmap.Height);
graphicsObj.Dispose();
}
The current approach is quite strange as it draws directly to the form. It's difficult to modify as a bitmap is created early on and then must somehow be changed. Furthermore, there is no need for a bitmap.
A better approach is to create a custom control:
public class EllipseControl : Control
{
private float m_ellipseWidth = 200;
private float m_ellipseHeight = 120;
public float EllipseWidth
{
get { return m_ellipseWidth; }
set
{
m_ellipseWidth = value;
Invalidate();
}
}
public float EllipseHeight
{
get { return m_ellipseHeight; }
set
{
m_ellipseHeight = value;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
using Pen pen = new Pen(System.Drawing.Color.Red, 3);
graphics.DrawEllipse(pen, 0, 0, EllipseWidth, EllipseHeight);
}
}
This control can then be placed on the form. It has two properties: EllipseWidth and EllipseHeight.
In the event handler for the button click, you can take the values from the text fields and set them on the ellipse control.

draw filled circle in WinForms C#

I have a WinForms application that interacts with a connection. If the connection is fine I want to show a green ("everything is fine") filled circle, if not I want to show a red filled circle.
I found no circle element in the toolbox so I think I have to draw it on my own.
I created a picture box called picBoxClientState and started with this code
public partial class FrmMain : Form
{
public void CheckSignedInState()
{
// some other code
DrawClientStateIcon(client.IsSignedIn);
}
private void DrawClientStateIcon(bool isSignedIn)
{
Point rectangleLocation = picBoxClientState.Location;
Size rectangleSize = picBoxClientState.Size;
Rectangle rectangle = new Rectangle(rectangleLocation, rectangleSize);
Color iconColor = isSignedIn ? Color.Green : Color.Red;
SolidBrush iconBrush = new SolidBrush(iconColor);
Graphics graphics = picBoxClientState.CreateGraphics();
graphics.FillEllipse(iconBrush, rectangle);
}
}
How can I draw on this picturebox whenever I call CheckSignedInState() ?
Maybe there is a better way instead of drawing? (I don't want to toggle two images because there might be more states to draw)
A simple example using a Label control to draw an ellipse.
You can use any control that has a Paint event to draw shapes.
It could also be a Panel, a PictureBox, a Button...
A bool variable (clientIsSignedIn) declared at Class scope is used to keep track of the current status, as reported by your client.IsSignedIn value.
When the status changes, update clientIsSignedIn and Invalidate() the Control that provides the visual aid.
bool clientIsSignedIn = false;
public void CheckSignedInState()
{
// some other code
clientIsSignedIn = client.IsSignedIn;
lblVisualStatus.Invalidate();
}
private void lblVisualStatus_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillEllipse((clientIsSignedIn) ? Brushes.Green : Brushes.Red, ((Control)sender).ClientRectangle);
}
I don't think picture box is necessary. You can try something like this:
private void button1_Click(object sender, EventArgs e)
{
System.Drawing.SolidBrush myBrush = new
System.Drawing.SolidBrush(System.Drawing.Color.Red);
System.Drawing.Graphics formGraphics;
formGraphics = this.CreateGraphics();
formGraphics.FillEllipse(myBrush, new Rectangle(200, 200, 30, 30));
myBrush.Dispose();
formGraphics.Dispose();
}
If you need to draw on picturebox be sure to call picturebox.Invalidate();
your code works, it did seem to draw a bit off center though but also the fact that you weren't disposing of the object could potentially cause issues too. Try using the code below as your DrawClientStateIcon method
Edit: full code example below. I added a button to toggle the state of the client and this works for me.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public class Client
{
public bool IsSignedIn { get; set; }
}
Client client = new Client()
{
IsSignedIn = false
};
public Form1()
{
InitializeComponent();
}
public void CheckSignedInState()
{
// some other code
DrawClientStateIcon(client.IsSignedIn);
}
private void DrawClientStateIcon(bool isSignedIn)
{
Point rectangleLocation = picBoxClientState.Location;
Size rectangleSize = picBoxClientState.Size;
Rectangle rectangle = new Rectangle(rectangleLocation, new Size(rectangleSize.Width / 2, rectangleSize.Height / 2));
Color iconColor = isSignedIn ? Color.Green : Color.Red;
using (SolidBrush iconBrush = new SolidBrush(iconColor))
{
using (Graphics graphics = picBoxClientState.CreateGraphics())
{
graphics.FillEllipse(iconBrush, rectangle);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
client.IsSignedIn = !client.IsSignedIn;
CheckSignedInState();
}
}
}
You need to call it in Form Shown event.
private void FrmGraphics_Shown(object sender, EventArgs e)
{
DrawClientStateIcon(true);
}
private void DrawClientStateIcon(bool isSignedIn)
{
Point rectangleLocation = picBoxClientState.Location;
Size rectangleSize = picBoxClientState.Size;
Rectangle rectangle = new Rectangle(rectangleLocation, rectangleSize);
Color iconColor = isSignedIn ? Color.Green : Color.Red;
SolidBrush iconBrush = new SolidBrush(iconColor);
Graphics graphics = base.CreateGraphics();
graphics.FillEllipse(iconBrush, rectangle);
}

Why it's not drawing a rectangle with the mouse on the new transparent form?

It's getting to the F_Paint event but not to the F_MouseDown event.
What I want to be able to draw rectangle on the F form.
Maybe since the F form is transparent it can't draw on it ? But it's never get to the F_MouseDown event I use a break point inside the F_MouseDown event.
Not sure why it's not getting to the MouseDown event.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Tester
{
public partial class Form1 : Form
{
Ffmpeg ffmpeg = new Ffmpeg();
private bool _canDraw;
private int _startX, _startY;
private Rectangle _rect;
public Form1()
{
InitializeComponent();
BackColor = Color.Blue;
TransparencyKey = BackColor;
Opacity = 1;
var f = new HelperForm { Opacity = 0, ShowInTaskbar = false, FormBorderStyle = FormBorderStyle.None };
f.MouseDown += F_MouseDown;
f.MouseMove += F_MouseMove;
f.MouseUp += F_MouseUp;
f.Paint += F_Paint;
f.Show();
Visible = false;
Owner = f;
Visible = true;
Move += (o, a) => f.Bounds = Bounds;
Resize += (o, a) => f.Bounds = Bounds;
f.Bounds = Bounds;
ffmpeg.Start(#"d:\ffmpegx86\test.mp4", 24);
}
private void F_Paint(object sender, PaintEventArgs e)
{
//Create a new 'pen' to draw our rectangle with, give it the color red and a width of 2
using (Pen pen = new Pen(Color.Red, 2))
{
//Draw the rectangle on our form with the pen
e.Graphics.DrawRectangle(pen, _rect);
}
}
private void F_MouseUp(object sender, MouseEventArgs e)
{
//The system is no longer allowed to draw rectangles
_canDraw = false;
}
private void F_MouseMove(object sender, MouseEventArgs e)
{
//If we are not allowed to draw, simply return and disregard the rest of the code
if (!_canDraw) return;
//The x-value of our rectangle should be the minimum between the start x-value and the current x-position
int x = Math.Min(_startX, e.X);
//The y-value of our rectangle should also be the minimum between the start y-value and current y-value
int y = Math.Min(_startY, e.Y);
//The width of our rectangle should be the maximum between the start x-position and current x-position minus
//the minimum of start x-position and current x-position
int width = Math.Max(_startX, e.X) - Math.Min(_startX, e.X);
//For the hight value, it's basically the same thing as above, but now with the y-values:
int height = Math.Max(_startY, e.Y) - Math.Min(_startY, e.Y);
_rect = new Rectangle(x, y, width, height);
//Refresh the form and draw the rectangle
Refresh();
}
private void F_MouseDown(object sender, MouseEventArgs e)
{
//The system is now allowed to draw rectangles
_canDraw = true;
//Initialize and keep track of the start position
_startX = e.X;
_startY = e.Y;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
ffmpeg.Close();
}
}
class HelperForm : Form
{
protected override CreateParams CreateParams
{
get
{
const int WS_EX_TOOLWINDOW = 0x80;
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_TOOLWINDOW;
return cp;
}
}
}
}
There're several reasons preventing the rectangle from being drawn and firing mouse events. First, you're setting the Opacity to 0; this means whatever you try to draw could never be visible. Instead, you should set the TransparencyKey to the color in BackColor:
f.TransparencyKey = f.BackColor;
Then, you're trying to draw a rectangle using the object _rect which was never initialized; therefore the rectangle you're trying to draw will be drawn with the size of 0 width and 0 height which means it won't be drawn, so, during your initialization, you should give a default value for _rect e.g:
_rect = new Rectangle(Point.Empty, this.Size);
The drawn rectangle should be visible by now; however, the events won't fire because you're reversing form ownership, so, instead of
Owner = f;
Use:
f.Owner = this;

Why when resizing the user control graph it's not really resized?

I have a user control chart in my Form1 designer and this is the code to resize it:
private void graphChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.Size = new Size(600, 600);
}
When I move the mouse to the control area it's not resizing it make it bigger but deleting some other controls.
This is an image before I move the mouse over the control:
And this is an image when I moved the mouse over the control:
This is the code of the user control where the chart is:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Web;
using System.Windows.Forms.DataVisualization.Charting;
namespace GatherLinks
{
public partial class GraphChart : UserControl
{
public GraphChart()
{
InitializeComponent();
}
private double f(int i)
{
var f1 = 59894 - (8128 * i) + (262 * i * i) - (1.6 * i * i * i);
return f1;
}
private void GraphChart_Load(object sender, EventArgs e)
{
chart1.Series.Clear();
var series1 = new System.Windows.Forms.DataVisualization.Charting.Series
{
Name = "Series1",
Color = System.Drawing.Color.Green,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType = SeriesChartType.Line
};
this.chart1.Series.Add(series1);
for (int i = 0; i < 100; i++)
{
series1.Points.AddXY(i, f(i));
}
chart1.Invalidate();
}
}
}
EDIT:
I did this in the user control class code:
public void ChangeChartSize(int width, int height)
{
chart1.Size = new Size(width, height);
chart1.Invalidate();
}
I had to add chart1.Invalidate(); to make it to take effect but then it sized the chart it self inside the user control. The user control was not changed.
So in the Form1 mouse enter I also changing the graphChart1 the control size:
private void graphChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.ChangeChartSize(600, 600);
graphChart1.Size = new Size(600, 600);
}
The problem is that now it's taking a lot of time almost 20 seconds or so until it take effect when I'm moving the mouse over the control. If I will remove the second line:
graphChart1.Size = new Size(600, 600);
it will work fast but then it will change the chart only inside the control but it won't change the control size.
Tried also with invalidate:
private void graphChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.ChangeChartSize(600, 600);
graphChart1.Size = new Size(600, 600);
graphChart1.Invalidate();
}
But still very slow. Maybe I need to change the control it self size also in the user control class code and not in Form1 ?
The problem is that you are resizing the GraphicChart (your user control) but not the Chart itself. You could add the method in your GraphChart class in order to do that. This is the method that will change the chart size:
public void ChangeChartSize(int width, int height)
{
chart1.Size = new Size(width, height);
}
And in your mouse enter event handler you could call something like this:
void graphicChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.ChangeChartSize(600, 600);
}
With graphChart1.Size = you are resizing your container but not the chart within it.
The easiest work-around is probably to make chart1 public in the control and do graphChart1.chart1.Size = instead.
In the user control class code I did:
public void ChangeChartSize(int width, int height)
{
this.Size = new Size(width, height);
chart1.Size = new Size(width, height);
chart1.Invalidate();
}
In Form1 I did:
private void graphChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.ChangeChartSize(600, 600);
}
Working smooth.

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