9

我一直在尝试为现有的 .Net WinForms 控件绘制自定义边框。我通过创建一个类来尝试此操作,该类从我想更改其边框颜色的控件中,然后在绘画过程中尝试几件事。我尝试了以下方法:

1. 抓住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。这适用于某些控件,但不是全部。这也需要您设置BorderStyleBorderStyle.None,并且您必须手动清除油漆上的背景,否则在调整大小时会出现此问题

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 OnResizeand void OnPaint(如方法 2)。这样,它可以很好地调整大小,但不是在启用面板时,在这种情况下,向下滚动时AutoScroll它将看起来像这样。如果我尝试使用WM_NCPAINT绘制边框,Refresh()没有任何效果。

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

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

4

2 回答 2

2

编辑:所以我弄清楚是什么导致了我最初的问题。经过很长时间的修补、试验和研究 .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 捕获并绘制边框,并确保RedrawWindowOnResize.

这甚至可以扩展以绘制自定义滚动条,因为这是您可以在WM_NCPAINT.

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

编辑2:对于ComboBox,你必须赶上WM_PAINTWndProc()因为出于某种原因,用于绘画的.Net源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));
  }
}
于 2014-10-02T09:57:14.563 回答
-2

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

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

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

于 2014-10-01T07:01:45.250 回答