2014-09-29 48 views
7

我一直在尝试为现有的.Net WinForms控件绘制自定义边框。我试图通过创建一个类来控制我想改变的边界颜色,然后在绘画时尝试几件事情。我试过以下内容:如何在.Net上绘制自定义边框WinForms控件

1. Catch WM_NCPAINT。这有点。下面的代码的问题是,当控件调整大小时,边界将在右侧和底部切断。不好。

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,并且您必须手动清除油漆背景,否则在调整大小时将清除背景you get this

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)。通过这种方式,它可以很好地进行调整大小,但不能在面板启用AutoScroll时进行调整,在这种情况下,它将在滚动时调整为look like this。如果我尝试使用WM_NCPAINT绘制边框,Refresh()不起作用。

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

建议不止欢迎。我想知道什么最好这样做,对于多种类型的控件(我将不得不这样做多个默认的WinForms控件)。

+0

我相信你之前听说过,但我诚实的建议是只使用WPF而不是WinFroms。除此之外,您对我这样做的祝福,以及我写的一个精心编写的问题+1。 – BradleyDotNET 2014-09-29 17:45:28

+0

谢谢!是的,我之前听说过很多次:)我仍然需要找时间学习WPF,但是这个项目对于WinForms来说太深了,无法将它转换成WPF。也许在将来。 – Codecat 2014-09-29 17:48:10

+0

保护覆盖无效onResize受到(EventArgs的EventArgs的) { base.OnResize(EventArgs的); 刷新(); } – houssam 2014-09-29 20:35:47

回答

1

编辑问题金额:所以我想通了什么导致我最初的问题。经过很长时间的修补,试验和研究.Net框架源代码之后,这里有一个明确的方法来做到这一点(考虑到你有一个控件继承自你想要绘制自定义边框的控件):

[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原样,确保WS_BORDER设置,再搭上WM_NCPAINT并通过对HDC绘制边框,并确保RedrawWindow被称为OnResize

为了绘制自定义滚动条,这可能甚至可能会延长,因为这是窗口框架的一部分,您可以在WM_NCPAINT期间使用该窗口框架。

我删除我的旧的答案来源于此。

编辑2:对于ComboBox,你必须赶上WM_PAINTWndProc(),因为某些原因,净源画ComboBox不使用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)); 
    } 
} 
-2

其实您可以使用WPF互操作性控件来创建任何你想要的边框。

  1. 创建表单
  2. 形式
  3. 创建WPF用户控件的Place ElementHost控件(来自WPF互操作性)(或使用现有的面板)与自定义边框
  4. WPF用户控制内部
  5. 地点WindowsFormsHost控件(这种控制稍后将用于承载您的控制)
  6. 设置ElementHost的子属性与上一步骤WPF用户控件

    我同意在溶液中含有大量的嵌套控件的,但是从我的角度来看,这显著减少了相关的OnPaint nested controls WPF+WinForm

+2

这看起来太耗费资源,加上它太复杂了,就像您已经说过的那样。 – Codecat 2014-10-01 12:13:35

+0

资源密集?这取决于你想要绘制自定义边框的控件数量。 – tarasn 2014-10-01 13:16:49

+0

每一个有边框的控件。你的答案是一个可能的解决方案,但它并不理想。 – Codecat 2014-10-01 13:51:03

相关问题