Очень простой вопрос, на который очень сложно найти правильный ответ.
Т.е. в 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: Конечно, только не забудьте упомянуть меня (Андрей Щёкин) и этот журнал.