Как рисовать пользовательские границы on.Net элементы управления WinForms

я пытался нарисовать пользовательские границы для существующих элементов управления .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. Это работает для некоторых элементов управления, но не для всех. Это также требует, чтобы вы установили BorderStyle to BorderStyle.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 OnResize и void OnPaint (как в методе 2). Таким образом, он хорошо рисует с изменением размера, но не тогда, когда панель имеет AutoScroll включено, и в этом случае это будет выглядеть так при прокрутке вниз. Если я попытаюсь использовать WM_NCPAINT в нарисуйте границу,Refresh() не имеет никакого эффекта.

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

предложения, более чем приветствуется. Я хотел бы знать, что лучшие способ сделать это, для нескольких типов элементов управления (мне придется сделать это для нескольких элементов управления WinForms по умолчанию).

2 ответов


EDIT: поэтому я понял, что вызвало мои первоначальные проблемы. После очень долгого времени возиться, экспериментировать и глядя в исходный код .Net framework, вот окончательный способ сделать это (учитывая, что у вас есть элемент управления, который наследуется от элемента управления, который вы хотите нарисовать пользовательскую границу):

[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 установлен, затем catch WM_NCPAINT и нарисуйте границу через hDC и убедитесь, что RedrawWindow называется OnResize.

это может быть даже расширено, чтобы нарисовать пользовательскую полосу прокрутки, потому что это часть оконной рамы, которую вы можете нарисовать во время WM_NCPAINT.

Я удалил свой старый ответ от этого.

EDIT 2: на ComboBox, вы должны поймать WM_PAINT на WndProc(), потому что по какой-то причине источник .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));
  }
}

на самом деле вы можете использовать элементы управления совместимости WPF для создания любой границы.

  1. Создать Форму
  2. поместите элемент управления ElementHost (из взаимодействия WPF) в форму
  3. создайте пользовательский элемент управления WPF (или используйте существующую панель) с пользовательской границей
  4. поместите элемент управления WindowsFormsHost в пользовательский элемент управления WPF (этот элемент управления будет использоваться позже для размещения элемента управления )
  5. задайте дочернее свойство ElementHost с помощью WPF Пользовательский контроль с предыдущего шага

    Я согласен, что мое решение содержит много вложенных элементов управления, но, с моей точки зрения, это значительно уменьшает количество проблем, связанных с OnPaint nested controls WPF+WinForm