2014-09-29 4 views
7

Я пытаюсь рисовать пользовательские границы для существующих элементов .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 в 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 OnResize и void 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

+0

Спасибо! И да, я слышал это раньше, много раз :) Мне все еще нужно найти время, чтобы изучить WPF, но этот проект слишком глубоко в WinForms, чтобы преобразовать его в WPF. Возможно в будущем. – Codecat

+0

protected override void OnResize (EventArgs eventargs) { base.OnResize (eventargs); Обновить(); } – houssam

ответ

1

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 установлен, то поймать 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)); 
    } 
} 
-2

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

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

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

+2

Это выглядит слишком ресурсоемким, а также слишком сложным, как вы уже сказали. – Codecat

+0

Ресурс интенсивный? Это зависит от того, сколько элементов управления вы хотите использовать пользовательскую границу. – tarasn

+0

Каждый элемент управления, который имеет границу. Ваш ответ - это возможное решение, но оно не идеально. – Codecat

 Смежные вопросы

  • Нет связанных вопросов^_^