文本框自定义onPaint

5
我正在尝试为我的文本框创建自定义onPaint,它正在工作...它正在工作。但是当我尝试输入内容时,会在文本框上方渲染出另一个文本框
这是我的构造函数:
public TextBox()
{
  Font = new Font("Segoe UI", 11F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
  BackColor = Color.White;
  BorderColor = Color.Gray;
  BorderStyle = BorderStyle.None;
  SetStyle(ControlStyles.UserPaint, true);
}

同时在 onPaint 中:

protected override void OnPaint(PaintEventArgs e)
{
  Graphics g = e.Graphics;
  g.FillRectangle(backgroundBrush, 0, 0, this.Width, this.Height);

  SizeF fontSize = g.MeasureString(Text, Font);
  g.DrawString(Text, Font, new SolidBrush(ForeColor), new PointF(5, 5), cFormat);

  g.DrawRectangle(borderPen, borderPen.Width / 2, borderPen.Width / 2,
                  this.Width - borderPen.Width, this.Height - borderPen.Width);
}

1
TextBox控件不使用Paint事件,因此您看到的是控件的版本和您自己的版本。 - LarsTech
那么,我需要制作自己的控件吗?还是有其他方法? - gerard
如果你只是想要创建一个边框,可以将TextBox放在一个带有2像素填充的Panel中,并将TextBox设置为Dock.Fill和MultiLine=true。否则,不清楚你为什么要绘制TextBox。 - LarsTech
@LarsTech 我们想要一个带有自定义边框颜色的文本框。 - King King
@gerard SetStyle(ControlStyles.UserPaint, true); 会让你自己绘制所有的东西,这太复杂了。如果你想要一个带有自定义边框的 TextBox,我认为还有另一种方法可以实现,而不是用这种方式进行绘制。 - King King
3
@HighCore,又是你,我想知道为什么你喜欢涉足“Winforms”问题,而你又认为它已经死了。难道是为了说服人们更快地让“Winforms”消失吗?我认为你没必要这样做。很多人肯定会认识到对他们来说,“WPF”更好。我敢打赌,“WPF”将成为我下一个商业项目的选择。不过,我仍会学习/研究“Winforms”以练习其他技能,比如GDI+,Win32...而且简单地说,这也是为了好玩。 - King King
1个回答

9

如果您只想要一个带有自定义边框(宽度和颜色)的自定义TextBox,我这里有两个解决方案:

  1. Using ControlPaint, this will allow you to draw border with some style and color but can't use Brush to draw more variously (like a HatchBrush can do):

    public class CustomTextBox : TextBox
    {
        [DllImport("user32")]
        private static extern IntPtr GetWindowDC(IntPtr hwnd);
        struct RECT
        {
          public int left, top, right, bottom;
        }
        struct NCCALSIZE_PARAMS
        {
          public RECT newWindow;
          public RECT oldWindow;
          public RECT clientWindow;
          IntPtr windowPos;
        }            
        float clientPadding = 2;  
        float actualBorderWidth = 4;
        Color borderColor = Color.Red;      
        protected override void WndProc(ref Message m)
        {
          //We have to change the clientsize to make room for borders
          //if not, the border is limited in how thick it is.
          if (m.Msg == 0x83) //WM_NCCALCSIZE   
          {
            if (m.WParam == IntPtr.Zero)
            {
                RECT rect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
                rect.left += clientPadding;
                rect.right -= clientPadding;
                rect.top += clientPadding;
                rect.bottom -= clientPadding;
                Marshal.StructureToPtr(rect, m.LParam, false);
            }
            else
            {
                NCCALSIZE_PARAMS rects = (NCCALSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALSIZE_PARAMS));
                rects.newWindow.left += clientPadding;
                rects.newWindow.right -= clientPadding;
                rects.newWindow.top += clientPadding;
                rects.newWindow.bottom -= clientPadding;
                Marshal.StructureToPtr(rects, m.LParam, false);
            }
          }
          if (m.Msg == 0x85) //WM_NCPAINT    
          {         
             IntPtr wDC = GetWindowDC(Handle);
             using(Graphics g = Graphics.FromHdc(wDC)){                                                      
               ControlPaint.DrawBorder(g, new Rectangle(0,0,Size.Width, Size.Height), borderColor, actualBorderWidth, ButtonBorderStyle.Solid,
             borderColor, actualBorderWidth, ButtonBorderStyle.Solid, borderColor, actualBorderWidth, ButtonBorderStyle.Solid,
             borderColor, actualBorderWidth, ButtonBorderStyle.Solid); 
             }   
             return;          
          }
          base.WndProc(ref m);
        }
    }
    

    Here is the textbox snapshot:

  2. using FillRegion method of a Graphics to paint the border with various kinds of Brush, here I use HatchBrush:

    public class CustomTextBox : TextBox
    {
      [DllImport("user32")]
      private static extern IntPtr GetWindowDC(IntPtr hwnd);
      struct RECT
      {
        public int left, top, right, bottom;
      }
      struct NCCALSIZE_PARAMS
      {
        public RECT newWindow;
        public RECT oldWindow;
        public RECT clientWindow;
        IntPtr windowPos;
      }         
      int clientPadding = 2;   
      int actualBorderWidth = 4;     
      protected override void WndProc(ref Message m)
      {
          //We have to change the clientsize to make room for borders
          //if not, the border is limited in how thick it is.
          if (m.Msg == 0x83) //WM_NCCALCSIZE   
          {
            if (m.WParam == IntPtr.Zero)
            {
                RECT rect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
                rect.left += clientPadding;
                rect.right -= clientPadding;
                rect.top += clientPadding;
                rect.bottom -= clientPadding;
                Marshal.StructureToPtr(rect, m.LParam, false);
            }
            else
            {
                NCCALSIZE_PARAMS rects = (NCCALSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALSIZE_PARAMS));
                rects.newWindow.left += clientPadding;
                rects.newWindow.right -= clientPadding;
                rects.newWindow.top += clientPadding;
                rects.newWindow.bottom -= clientPadding;
                Marshal.StructureToPtr(rects, m.LParam, false);
            }
          }
          if (m.Msg == 0x85) //WM_NCPAINT
          {                 
            IntPtr wDC = GetWindowDC(Handle);
            using(Graphics g = Graphics.FromHdc(wDC)){                                                
              Rectangle rect = new Rectangle(0,0,Width,Height);
              Rectangle inner = new Rectangle(0, 0, Width, Height);
              inner.Offset(actualBorderWidth + 2, actualBorderWidth + 2);
              inner.Width -= 2 * actualBorderWidth + 4;
              inner.Height -= 2 * actualBorderWidth + 4;
              Region r = new Region(rect);
              r.Xor(inner);
              using (System.Drawing.Drawing2D.HatchBrush brush = new System.Drawing.Drawing2D.HatchBrush(System.Drawing.Drawing2D.HatchStyle.SmallCheckerBoard, Color.Green, Color.Red))
              {                    
                g.FillRegion(brush, r);
              }
            }
            return;
          }
          base.WndProc(ref m);
      }
    }
    

    Here is the textbox snapshot:
    with scrollbars


@gerard 哪段代码不起作用?第一段还是第二段?在第二段代码中,actualBorderWidth 应该声明为 int,我已经更新了,抱歉。但在第一段代码中,它应该是 float - King King
1
@gerard 这个代码泄漏很严重。你需要使用 ReleaseDC,而不是 .G.ReleaseHdc,并在处理完图形对象后调用 ReleaseDC(Handle, wDC);。 图形对象应该包含在 using 块中,using (Graphics g = Graphics.FromHdc(wDC)) {...}。我认为(我可能错了),返回语句可能会导致无法调用 base.WndProc(ref m); 方法:在 Vista 或 Win7 上,多行框中的滚动条可能无法绘制。 - LarsTech
滚动条位于非客户区。 滚动条不总是正确重绘 - 我刚在Win2008中测试了您的控件以验证。 在您的解决方案中,您应该首先调用base.WndProc(ref m);以便控件可以正确绘制自身,但这会导致令人讨厌的闪烁问题。 - LarsTech
1
不要忽略ReleaseDC的建议。你的当前代码正在泄漏句柄。 - LarsTech
@KingKing 我正在使用 MultiLine=True,但是我的文本被边框覆盖了,我该如何更改 TextBox 的高度,或者只是给文本区域添加填充? - Aesthetic
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接