我如何画上的.Net WinForms控件自定义边框

我如何画上的.Net WinForms控件自定义边框

问题描述:

我一直想画自定义边框为现有的.NET WinForms控件。我已经通过创建从控制我想改变的边框颜色,然后画在尝试几件事情的类尝试这一点。我试过以下内容:

I've been trying to paint custom borders for existing .Net WinForms controls. I've attempted this by creating a class which from the control I want to change the border color of, and then try several things during painting. I've tried the following:

1。抓住 WM_NCPAINT 。这工作,一些。用下面的代码的问题是,当控制调整大小,边界将在右侧和底侧切断。不好。

1. Catch WM_NCPAINT. This works, somewhat. The problem with the code below is that when the control resizes, the border will be cut off on the right and bottom side. Not good.

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。覆盖无效的OnPaint 。这适用于某些控制,但不是全部。这也需要您设置边框 BorderStyle.None ,你必须手动清除油漆背景,否则你当你调整本

2. Override void OnPaint. This works for some controls, but not all. This also requires that you set BorderStyle to BorderStyle.None, and you have to manually clear the background on paint, otherwise you get this when you 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。覆盖无效onResize受到无效的OnPaint (如在方法2)。这样,油漆很好地调整大小,而不是在小组自动滚屏启用,在这种情况下,它会的向下滚动时,这个样子。如果我尝试使用 WM_NCPAINT 来绘制边框,刷新()没有效果。

3. Overriding void OnResize and void OnPaint (like in method 2). This way, it paints well with resizing, but not when the Panel has AutoScroll enabled, in which case it will look like this when scrolling down. If I try to use WM_NCPAINT to paint the border, Refresh() has no effect.

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



建议都欢迎。我想知道的办法去了解这一点,对于多种类型的控件(我不得不为多个默认WinForms控件做到这一点)。

Suggestions are more than welcome. I'd like to know what the best way to go about this is, for multiple types of controls (I'll have to do this for multiple default WinForms controls).

编辑:所以我想通了什么导致我最初的问题。经过一个很长的修修补补,试验,并期待在.NET Framework中的源代码的时候,这里有一个明确的办法做到这一点(考虑到你有从要绘制自定义边框控制继承的控件):

So I figured out what was causing my initial problems. After a very long time of tinkering, experimenting, and looking into the .Net framework source code, here's a definitive way to do it (considering you have a control that inherits from the control you want to draw a custom border on):

[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受到

So in a nutshell, leave OnPaint as is, make sure WS_BORDER is set, then catch WM_NCPAINT and draw the border via the hDC, and make sure that RedrawWindow is called in OnResize.

这可能甚至是为了绘制一个自定义的扩展滚动条,因为这是窗框,你可以在 WM_NCPAINT 借鉴的一部分。

This could maybe even be extended in order to draw a custom scrollbar, because that's part of the window frame that you can draw on during WM_NCPAINT.

我删除我的旧的答案从这个

I removed my old answer from this.

编辑2:对于组合框,你必须赶上 WM_PAINT 的WndProc(),因为某些原因,净源画组合框不使用的OnPaint(),但 WM_PAINT 。因此,像这样:

EDIT 2: For ComboBox, you have to catch WM_PAINT in WndProc(), because for some reason the .Net source for painting the ComboBox doesn't use OnPaint(), but WM_PAINT. So something like this:

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