如何在.Net WinForms控件上绘制自定义边框

我一直在尝试为现有的.Net WinForms控件绘制自定义边框。 我试过这个是通过创建一个类来控制我想要改变它的边框颜色,然后在绘画过程中尝试几件事。 我尝试过以下方法:

1.捕获WM_NCPAINT 。 这有点起作用。 下面的代码的问题是,当控件resize时,边框将在右侧和底侧被切断。 不好。

 protected override void WndProc(ref Message m) { if (m.Msg == NativeMethods.WM_NCPAINT) { WmNcPaint(ref m); return; } base.WndProc(ref m); } private void WmNcPaint(ref Message m) { if (BorderStyle == BorderStyle.None) { return; } IntPtr hDC = NativeMethods.GetWindowDC(m.HWnd); if (hDC != IntPtr.Zero) { using (Graphics g = Graphics.FromHdc(hDC)) { ControlPaint.DrawBorder(g, new Rectangle(0, 0, this.Width, this.Height), _BorderColor, ButtonBorderStyle.Solid); } m.Result = (IntPtr)1; NativeMethods.ReleaseDC(m.HWnd, hDC); } } 

2.覆盖void OnPaint 。 这适用于某些控件,但不是全部。 这还要求您将BorderStyle设置为BorderStyle.None ,并且您必须手动清除paint上的背景,否则在resize时会得到这个 。

 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); ControlPaint.DrawBorder(e.Graphics, new Rectangle(0, 0, this.Width, this.Height), _BorderColor, ButtonBorderStyle.Solid); } 

3.重写void OnResizevoid OnPaint (如方法2中所示)。 通过这种方式,它可以很好地resize,但是当Panel启用了AutoScroll时则不会,在这种情况下,向下滚动时它将如下所示 。 如果我尝试使用WM_NCPAINT绘制边框,则Refresh()无效。

 protected override void OnResize(EventArgs eventargs) { base.OnResize(eventargs); Refresh(); } 

建议非常欢迎。 我想知道最好的方法是什么,对于多种类型的控件(我必须为多个默认的WinForms控件执行此操作)。

编辑:所以我想出了导致我最初问题的原因。 经过很长一段时间的修补,试验和查看.Net框架源代码,这里有一个明确的方法(考虑到你有一个控件inheritance自你想要绘制自定义边框的控件):

 [DllImport("user32.dll")] public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RedrawWindowFlags flags); [Flags()] public enum RedrawWindowFlags : uint { Invalidate = 0X1, InternalPaint = 0X2, Erase = 0X4, Validate = 0X8, NoInternalPaint = 0X10, NoErase = 0X20, NoChildren = 0X40, AllChildren = 0X80, UpdateNow = 0X100, EraseNow = 0X200, Frame = 0X400, NoFrame = 0X800 } // Make sure that WS_BORDER is a style, otherwise borders aren't painted at all protected override CreateParams CreateParams { get { if (DesignMode) { return base.CreateParams; } CreateParams cp = base.CreateParams; cp.ExStyle &= (~0x00000200); // WS_EX_CLIENTEDGE cp.Style |= 0x00800000; // WS_BORDER return cp; } } // During OnResize, call RedrawWindow with Frame|UpdateNow|Invalidate so that the frame is always redrawn accordingly protected override void OnResize(EventArgs e) { base.OnResize(e); if (DesignMode) { RecreateHandle(); } RedrawWindow(this.Handle, IntPtr.Zero, IntPtr.Zero, RedrawWindowFlags.Frame | RedrawWindowFlags.UpdateNow | RedrawWindowFlags.Invalidate); } // Catch WM_NCPAINT for painting protected override void WndProc(ref Message m) { if (m.Msg == NativeMethods.WM_NCPAINT) { WmNcPaint(ref m); return; } base.WndProc(ref m); } // Paint the custom frame here private void WmNcPaint(ref Message m) { if (BorderStyle == BorderStyle.None) { return; } IntPtr hDC = NativeMethods.GetWindowDC(m.HWnd); using (Graphics g = Graphics.FromHdc(hDC)) { g.DrawRectangle(new Pen(_BorderColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); } NativeMethods.ReleaseDC(m.HWnd, hDC); } 

简而言之,保持OnPaint WM_NCPAINT ,确保设置了WS_BORDER ,然后捕获WM_NCPAINT并通过hDC绘制边框,并确保在OnResize调用RedrawWindow

这甚至可以扩展以绘制自定义滚动条,因为这是在WM_NCPAINT期间可以绘制的窗口框架的一部分。

我从中删除了我的旧答案。

编辑2:对于ComboBox ,您必须在WndProc()捕获WM_PAINT ,因为由于某种原因,用于绘制ComboBox的.Net源不使用OnPaint() ,而是使用WM_PAINT 。 所以这样的事情:

 protected override void WndProc(ref Message m) { base.WndProc(ref m); if (m.Msg == NativeMethods.WM_PAINT) { OnWmPaint(); } } private void OnWmPaint() { using (Graphics g = CreateGraphics()) { if (!_HasBorders) { g.DrawRectangle(new Pen(BackColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); return; } if (!Enabled) { g.DrawRectangle(new Pen(_BorderColorDisabled), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); return; } if (ContainsFocus) { g.DrawRectangle(new Pen(_BorderColorActive), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); return; } g.DrawRectangle(new Pen(_BorderColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); } } 

实际上,您可以使用WPF互操作性控件来创建所需的任何边框。

  1. 创建表单
  2. 在表单上放置ElementHost控件(来自WPF互操作性)
  3. 使用自定义边框创建WPF用户控件(或使用现有面板)
  4. 将WindowsFormsHost控件放在WPF用户控件中(此控件稍后将用于托管您的控件)
  5. 使用上一步中的WPF User Control设置ElementHost Child属性

    我同意我的解决方案包含许多嵌套控件,但从我的角度来看,它显着减少了与OnPaint相关的问题 嵌套控件WPF + WinForm