简体   繁体   中英

How do I paint custom borders on .Net WinForms controls

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

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

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

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 .

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.

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

Actually you may use WPF interoperability controls to create any border you want .

  1. Create Form
  2. Place ElementHost control (from WPF Interoperability) on the form
  3. Create a WPF User Control(or use existing panel) with custom border
  4. Place WindowsFormsHost control inside WPF User Control (this control will be used later to host your control )
  5. Set the ElementHost Child property with WPF User Control from previous step

    I agree that my solution contains lot of nested controls , but from my point of view it significantly reduces amount of problems related to OnPaint 嵌套控件WPF + WinForm

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM