9

I have the following code:

public class OurTextBox : TextBox
{
    public OurTextBox()
        : base()
    {
        this.SetStyle(ControlStyles.UserPaint, true);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
         base.OnPaint(e);
         Pen penBorder = new Pen(Color.Gray, 1);
         Rectangle rectBorder = new Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1);
         e.Graphics.DrawRectangle(penBorder, rectBorder);
   }
}

This is working perfectly, but it doesn't show the text until it gets focus.

Can anybody help me? What is wrong?

jordanz
  • 367
  • 4
  • 12
Moisés Martínez
  • 153
  • 1
  • 1
  • 8
  • You should not use `OnPaint` for drawing border for `TextBox`. Instead you should handle `WM_NCPAINT` message in `WndProc` and draw the border on non-client area, like [this](http://stackoverflow.com/questions/17466067/change-border-color-in-textbox-c-sharp/39420512#39420512). – Reza Aghaei Sep 09 '16 at 23:35

4 Answers4

36

To change border color of TextBox you can override WndProc method and handle WM_NCPAINT message. Then get the window device context of the control using GetWindowDC because we want to draw to non-client area of control. Then to draw, it's enough to create a Graphics object from that context, then draw border for control.

To redraw the control when the BorderColor property changes, you can use RedrawWindow method.

Code

Here is a TextBox which has a BorderColor property. The control uses BorderColor if the property values is different than Color.Transparent and BorderStyle is its default value Fixed3d.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyTextBox : TextBox {
    const int WM_NCPAINT = 0x85;
    const uint RDW_INVALIDATE = 0x1;
    const uint RDW_IUPDATENOW = 0x100;
    const uint RDW_FRAME = 0x400;
    [DllImport("user32.dll")]
    static extern IntPtr GetWindowDC(IntPtr hWnd);
    [DllImport("user32.dll")]
    static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
    [DllImport("user32.dll")]
    static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprc, IntPtr hrgn, uint flags);
    Color borderColor = Color.Blue;
    public Color BorderColor {
        get { return borderColor; }
        set { borderColor = value;
            RedrawWindow(Handle, IntPtr.Zero, IntPtr.Zero,
                RDW_FRAME | RDW_IUPDATENOW | RDW_INVALIDATE);
        }
    }
    protected override void WndProc(ref Message m) {
        base.WndProc(ref m);
        if (m.Msg == WM_NCPAINT && BorderColor != Color.Transparent &&
            BorderStyle == System.Windows.Forms.BorderStyle.Fixed3D) {
            var hdc = GetWindowDC(this.Handle);
            using (var g = Graphics.FromHdcInternal(hdc))
            using (var p = new Pen(BorderColor))
                g.DrawRectangle(p, new Rectangle(0, 0, Width - 1, Height - 1));
            ReleaseDC(this.Handle, hdc);
        }
    }
    protected override void OnSizeChanged(EventArgs e) {
        base.OnSizeChanged(e);
        RedrawWindow(Handle, IntPtr.Zero, IntPtr.Zero,
               RDW_FRAME | RDW_IUPDATENOW | RDW_INVALIDATE);
    }
}

Result

Here is the result using different colors and different states. All states of border-style is supported as you can see in below image and you can use any color for border:

enter image description here

Download

You can clone or download the working example:

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • I know that this question was asked 3 years ago, but I'm struggling with same problem right now. I've used Your method, but I get flickering border problem: http://imgur.com/RmkvXT0 maybe You had same problem? Thanks for hints! – Misiu Apr 13 '17 at 10:17
  • 1
    @Misiu I can confirm the issue and I'll try to solve it. – Reza Aghaei Apr 13 '17 at 14:51
  • 1
    @Misiu You can override `OnSizeChanged` and call `RedrawWindow` the same way which called in `BorderColor `. – Reza Aghaei Apr 13 '17 at 15:28
  • 1
    I've tried handling `WM_SIZE` inside `WndProc` hoping it will work, but it didn't (now sure what's the difference, it should be the same thing). Override suggested by You works perfect. Thank You! – Misiu Apr 13 '17 at 19:04
  • 1
    This flickers horribly under Windows 7 because of controls getting highlighted in Aero when you enter them with the mouse cursor. – user3700562 Jun 21 '18 at 15:33
  • 2
    @user3700562 I don't have access to a Win7 at the moment. As soon as I find time to setup a Win7 VM, I'll take a look at that. But on Win 8.1 and 10 it's working properly. – Reza Aghaei Jun 21 '18 at 15:36
  • @RezaAghaei Ok, this is what happens when the mouse pointer hovers over an input control in windows 7: https://imgur.com/9ZwKVqF I thought the problem is that base.WndProc shouldn't be called since we have already handled WM_NCPAINT but doing that causes weird artifacts. – user3700562 Jun 21 '18 at 18:30
  • Nice @RezaAghaei. But there's a bug in your code. The FixedSingle box, second from bottom, according to your code, is supposed to have a blue border. It seems that changing the border color doesn't work when using FixedSingle borders. Which is a shame, because if I want to change the BackColor of the textbox then it leaves a 1px white border inside the normal border, unless I use FixedSingle Border. – stuzor Dec 17 '18 at 06:21
  • 1
    @stuzor `Fixed3D` is the style which shows the color. `FixedSingle` will draw using default `FixedSingle` style. – Reza Aghaei Dec 17 '18 at 06:39
  • OK so then there's absolutely no way to change the colour for a Fixed Single border then? Thanks @RezaAghaei – stuzor Dec 17 '18 at 11:27
  • 1
    @stuzor The `Fixed3D` in above class will act as `FixedSingle`. It draws a solid single line colored border. – Reza Aghaei Dec 17 '18 at 11:47
  • I'm saying that when i use Fixed3D, the Border color changes as you suggest, but I ALSO get a thin white inner border if I change the BackColor. Perhaps you only use it with White as the BackColor? My only solution to that is to use FixedSingle, but then I can't change the BorderColor anymore. Is there any way to override the OnPaint method to fix that white border in Fixed3D mode? Thiis image shows what I mean: https://www.dropbox.com/s/jtavfbb2rqyy30e/textbox-border-problem.png?dl=0 – stuzor Dec 18 '18 at 02:15
  • 1
    @stuzor I haven't seen that. I'll investigate more when I find time and will inform you about the result. Thanks for the comment. – Reza Aghaei Dec 18 '18 at 03:58
  • I set the color to the original (not blue), but all the textboxes in a dialog paints with the border in blue, as if they had the focus. – DanielB Oct 04 '20 at 07:56
  • @DanielB Maybe I couldn't get the question well, but make sure you have set the border style to Fixed3D and the set BorderColor to the desired color, then it will work as expected. If you want to change just on focus, then take a look at my [other answer](https://stackoverflow.com/a/38405319/3110834). – Reza Aghaei Oct 04 '20 at 09:31
  • @RezaAghaei The designer had silently set the BorderColor property to blue to all my textboxes in the dialog. It works great now, thanks! – DanielB Oct 04 '20 at 19:41
  • 2
    @stuzor The reason you get a thin white line is because the non-client area of a textbox with a Fixed3D border is a rectangle that is two pixels thick. To handle the back color issue, simply change the core of the above code to this: var hdc = GetWindowDC(this.Handle); using (var g = Graphics.FromHdcInternal(hdc)) { using (var p = new Pen(BorderColor)) g.DrawRectangle(p, new Rectangle(0, 0, Width - 1, Height - 1)); using (var b = new Pen(BackColor)) g.DrawRectangle(b, new Rectangle(1, 1, Width - 3, Height - 3)); } ReleaseDC(this.Handle, hdc); – David Fletcher Dec 29 '21 at 05:19
  • Check my answer at stackoverflow.com/a/74106039/5514131 which offers a custom TextBox control that tries to fix the flicker issues, the code uses the same concept mentioned by Reza Aghaei – Ahmed Osama Oct 18 '22 at 05:46
6

You have to draw text manually as well.

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    Pen penBorder = new Pen(Color.Gray, 1);
    Rectangle rectBorder = new Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1);
    e.Graphics.DrawRectangle(penBorder, rectBorder);

    Rectangle textRec = new Rectangle(e.ClipRectangle.X + 1, e.ClipRectangle.Y + 1, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1);
    TextRenderer.DrawText(e.Graphics, Text, this.Font, textRec, this.ForeColor, this.BackColor, TextFormatFlags.Default);
}

Alternatively you can try to use e.Graphics.DrawString() method if TextRenderer is not giving you desired results (I always have better results with this approach thou).

gzaxx
  • 17,312
  • 2
  • 36
  • 54
  • 4
    You should not override `OnPaint` because you need drawing the string, blinking caret, selection highlight and so on. Instead you should handle `WM_NCPAINT` message in `WndProc` and draw the border on non-client area, like [this](http://stackoverflow.com/questions/17466067/change-border-color-in-textbox-c-sharp/39420512#39420512). – Reza Aghaei Sep 14 '16 at 10:14
1

There are several ways to do this and none are ideal. This is just the nature of WinForms. However, you have some options. I will summarise:

One way you can achieve what you want is by embedding a TextBox in a Panel as follows.

public class BorderedTextBox : Panel 
{
    private TextBox textBox;
    private bool focusedAlways = false;
    private Color normalBorderColor = Color.Gray;
    private Color focusedBorderColor = Color.Red;

    public BorderTextBox() 
    {
        this.DoubleBuffered = true;
        this.Padding = new Padding(2);

        this.TextBox = new TextBox();
        this.TextBox.AutoSize = false;
        this.TextBox.BorderStyle = BorderStyle.None;
        this.TextBox.Dock = DockStyle.Fill;
        this.TextBox.Enter += new EventHandler(this.TextBox_Refresh);
        this.TextBox.Leave += new EventHandler(this.TextBox_Refresh);
        this.TextBox.Resize += new EventHandler(this.TextBox_Refresh);
        this.Controls.Add(this.TextBox);
    }

    private void TextBox_Refresh(object sender, EventArgs e) 
    {
        this.Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e) 
    {
        e.Graphics.Clear(SystemColors.Window);
        using (Pen borderPen = new Pen(this.TextBox.Focused || FocusedAlways ? 
            focusedBorderColor : normalBorderColor)) 
        {
            e.Graphics.DrawRectangle(borderPen, 
                new Rectangle(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1));
        }
        base.OnPaint(e);
    }

    public TextBox TextBox
    {
        get { return textbox; }
        set { textbox = value; }
    }

    public bool FocusedAlaways
    {
        get { return focusedAlways; }
        set { focusedAlways = value; }
    }
}

You can also do this without overriding any controls, but the above method is better. The above will draw a border when the control gets focus. if you want the border on permanently, set the FocusedAlways property to True.

I hope this helps.

MoonKnight
  • 23,214
  • 40
  • 145
  • 277
  • 1
    Wow, an amazing amount of work for a simple border. That is why one should always use wpf, i guess :) – Nikita B Jul 04 '13 at 09:21
  • 1
    Just moved to WPF myself. I am enjoying it but it is tough coming from WinForms background - wish I had started with it. MVVM is a tough thing to get you head round, but the entire thing is better for sure! I now have a WinForm project with loads of these sort of solutions I have picked up over the years. – MoonKnight Jul 04 '13 at 09:26
  • 1
    Thank.I'm agree with Nick... It's amazing so much work to change a border – Moisés Martínez Jul 04 '13 at 09:44
  • I think, as Nik has pointed out. This was the reason for the inception of WPF. I for one am pretty sick of having to do this sort of stuff on a daily basis! – MoonKnight Jul 04 '13 at 09:47
  • To draw the border permanently, Uings a [derived](http://stackoverflow.com/questions/17466067/change-border-color-in-textbox-c-sharp/39420512#39420512) `TextBox` control is more friendly and more elegant than a `Panel` containing a TextBox. Also if you want to highlight borders just on focus, you can simply use such [solution](http://stackoverflow.com/a/38405319/3110834) – Reza Aghaei Sep 14 '16 at 10:19
1

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);

}
Moory Pc
  • 860
  • 15
  • 16