dotnet :: winforms :: рисуем однопиксельную рамку вне клиентской области контроля

Nov 02, 2005 14:56


Очень простой вопрос, на который очень сложно найти правильный ответ.
Т.е. в google находятся несколько методов, на они все не до конца верные.
Я разбирался несколько часов.

Сначала дам верный ответ, затем поговорю подробнее.

Q: А зачем это вообще нужно ?
A: Например, вы захотели изменить цвет рамки Label с BorderMode FixedSingle.
Или написать свой контейнер с рамкой так, чтобы граница никогда не перекрывалась внутренними контролями.

Q: Хорошо, и как нарисовать однопиксельную рамку вне клиентской области контроля ?
A: Сначала вам понадобится переопределить WndProc в своём контроле. Пример:

private void WmNCPaint(ref Message m) { NonClientPaint.DrawBorder( ref m, this, this.BorderColor ); this.Invalidate(); } protected override void WndProc(ref Message m) { if (m.Msg == NativeMethods.WM_NCPAINT) this.WmNCPaint(ref m); else base.WndProc (ref m); }
А вот код из моего класса NonClientPaint:

public static void DrawBorder(ref Message m, Control control, Color color) { NonClientPaint.DrawBorder( ref m, new Rectangle(0, 0, control.Width - 1, control.Height - 1), color ); } public static void DrawBorder(ref Message m, Rectangle bounds, Color color) { IntPtr hRgn = m.WParam; // The update region is clipped to the window frame. // When wParam is 1, the entire window frame needs to be updated. IntPtr hdc = UnsafeNativeMethods.GetDCEx( m.HWnd, hRgn, NativeMethods.DCX_WINDOW | NativeMethods.DCX_INTERSECTRGN | 0x10000 // 0x10000 it is a magic value from // http://www.catch22.net/tuts/tips.asp#UndocumentedGetDCEx ); if (hdc != IntPtr.Zero) { try { using (Graphics graphics = Graphics.FromHdc(hdc)) { using (Pen pen = new Pen(color)) { graphics.DrawRectangle(pen, bounds); } // create a clipping region for remaining parts to be drawn excluding // the border we did just drew bounds.Inflate(-1, -1); } IntPtr hRgn2 = SafeNativeMethods.CreateRectRgn( bounds.Left, bounds.Top, bounds.Right, bounds.Bottom ); if (hRgn2 == IntPtrOne) m.WParam = hRgn2; else { // combine with existing clipping region. SafeNativeMethods.CombineRgn(hRgn, hRgn, hRgn2, NativeMethods.RGN_AND); SafeNativeMethods.DeleteObject(hRgn2); } } finally { m.Result = IntPtrOne; UnsafeNativeMethods.ReleaseDC(m.HWnd, hdc); } } }
Q: А если у моего контроля нет места для рамки ?
A: Чтобы зарезервировать место для рисования рамки, перопределите CreateParams.

protected override CreateParams CreateParams { get { CreateParams @params = base.CreateParams; @params.Style |= NativeMethods.WS_BORDER; return @params; } }
Q: Когда я изменяю размер контроля, рамка не перерисовывается !
A: Для этого нужно переопределить ещё и OnResize:

protected override void OnResize(EventArgs e) { NonClientPaint.Invalidate(this); base.OnResize (e); } ... // NonClientPaint public static void Invalidate(Control control) { SafeNativeMethods.RedrawWindow( control.Handle, IntPtr.Zero, IntPtr.Zero, NativeMethods.RDW_FRAME | NativeMethods.RDW_INVALIDATE ); }
Q: SafeNativeMethods ?
A: Обычные функции WinApi (P/Invoke), организованные по приниципу, описанному здесь.

Q: Чем плохо решение, предложенное в MSDN ?
A: При этом возникают серьёзные проблемы с перерисовкой.
Части рамки остаются на других окнах и контролях (расположенных сверху данного).
Эти проблемы очень заметны, если рисовать так рамку для перетаскиваемого окна.

Q: Что такое 0x10000 ( как флаг GetDCEx) ?
A: Не знаю.
Я взял этот параметр из UndocumentedGetDCEx, и без него действительно ничего не работает.

Q: Почему bounds именно такие (0, 0, Width-1, Height-1) ?
A: Width - 1 и Height - 1 соответствуют правой и нижней границам вне клиентской области.
ClientRectangle тут очевидным образом не подходит.

Q: Так ли необходима вся эта часть с hRgn2 ?
A: Насколько я понимаю, нет.
Если только контроль не рисует вне клиентской области что-то ещё (заголовок окна, например).

Q: Invalidate: Почему не сделать PostMessage(WM_NCPAINT) ?
A: Простой ответ: это не работает.
Такой метод иногда предлагают в сети, но это сообщение никогда не доходит до контроля.
Люди со Spy++ могут это проверить, люди с исходниками Windows могут это объяснить.

Q: Рамка неправильно перерисовывается под Windows 2003 Server !
A: Сам только что заметил. Как только разберусь - напишу сюда.

Q: Могу ли я разместить эти Q&A у себя на сайте ?
A: Конечно, только не забудьте упомянуть меня (Андрей Щёкин) и этот журнал.





howto, .net, winapi, gdi, windows_forms

Previous post
Up