Контроль рендеринга на стекле: решение найдено, требует двойной буферизации/улучшения
I (наконец!) нашел способ визуализации элементов управления Windows.Forms на стекле, который, похоже, не имеет какого-либо серьезного недостатка или большого времени реализации. Он вдохновил эту статью от Coded, в котором в основном объясняется, как изначально изменить картину элементов управления, чтобы нарисовать их.
Я использовал этот подход, чтобы отобразить элемент управления в растровом изображении и нарисовать его с помощью GDI + и соответствующего альфа-канала над областью рисования NativeWindow. Реализация проста, но может быть усовершенствована для удобства использования, но это не вопрос этого вопроса. Результаты, однако, вполне удовлетворяют:
![Real textbox on glass]()
Однако есть две области, которые необходимо исправить, чтобы это было действительно полезным.
- Двойная буферизация, потому что мерцание между этим оверлейным изображением и реальным контролем является частым и ужасным (проверьте себя на код). Установка базового элемента управления с двойной буферизацией с помощью
SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true)
не работает, но я подозреваю, что мы можем заставить его работать с небольшим пробным и ошибкой.
-
Некоторые элементы управления не работают. Я смог выполнить следующую работу:
- TextBox
- MaskedComboBox
- ComboBox (DropDownStyle == DropDownList)
- ListBox
- CheckedListBox
- ListView
- TreeView
- DateTimePicker
- MonthCalendar
Но я не могу заставить их работать, хотя я не понимаю, почему нет. Моя образованная догадка заключается в том, что фактический дескриптор NativeWindow я ссылаюсь на весь элемент управления, в то время как мне нужно ссылаться на его "входную" (текстовую) часть, возможно, на ребенка. Любая помощь экспертов WinAPI в том, как получить этот дескриптор окна ввода, приветствуется.
- ComboBox (DropDownStyle!= DropDownList)
- NumericUpDown
- RichTextBox
Но фиксация двойной буферизации будет основной фокус для удобства использования.
Здесь пример использования:
new GlassControlRenderer(textBox1);
Здесь код:
public class GlassControlRenderer : NativeWindow
{
private Control Control;
private Bitmap Bitmap;
private Graphics ControlGraphics;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0xF: // WM_PAINT
case 0x85: // WM_NCPAINT
case 0x100: // WM_KEYDOWN
case 0x200: // WM_MOUSEMOVE
case 0x201: // WM_LBUTTONDOWN
this.Control.Invalidate();
base.WndProc(ref m);
this.CustomPaint();
break;
default:
base.WndProc(ref m);
break;
}
}
public GlassControlRenderer(Control control)
{
this.Control = control;
this.Bitmap = new Bitmap(this.Control.Width, this.Control.Height);
this.ControlGraphics = Graphics.FromHwnd(this.Control.Handle);
this.AssignHandle(this.Control.Handle);
}
public void CustomPaint()
{
this.Control.DrawToBitmap(this.Bitmap, new Rectangle(0, 0, this.Control.Width, this.Control.Height));
this.ControlGraphics.DrawImageUnscaled(this.Bitmap, -1, -1); // -1, -1 for content controls (e.g. TextBox, ListBox)
}
}
Я был бы очень рад это исправить, и раз и навсегда имеет реальный способ рендеринга на стекле для всех элементов управления .NET без WPF.
EDIT: Возможные пути для двойной буферизации/анти-мерцания:
- Удаление строки
this.Control.Invalidate()
удаляет мерцание, но прерывает ввод текста в текстовое поле.
-
Я пробовал подход WM_SETREDRAW и метод SuspendLayout без везения:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
public static void SuspendDrawing(Control parent)
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
public static void ResumeDrawing(Control parent)
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0xF: // WM_PAINT
case 0x85: // WM_NCPAINT
case 0x100: // WM_KEYDOWN
case 0x200: // WM_MOUSEMOVE
case 0x201: // WM_LBUTTONDOWN
//this.Control.Parent.SuspendLayout();
//GlassControlRenderer.SuspendDrawing(this.Control);
//this.Control.Invalidate();
base.WndProc(ref m);
this.CustomPaint();
//GlassControlRenderer.ResumeDrawing(this.Control);
//this.Control.Parent.ResumeLayout();
break;
default:
base.WndProc(ref m);
break;
}
}
Ответы
Ответ 1
Вот версия с гораздо меньшим мерцанием, но еще не идеальная.
public class GlassControlRenderer : NativeWindow
{
private Control Control;
private Bitmap Bitmap;
private Graphics ControlGraphics;
private object Lock = new object();
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x14: // WM_ERASEBKGND
this.CustomPaint();
break;
case 0x0F: // WM_PAINT
case 0x85: // WM_NCPAINT
case 0x100: // WM_KEYDOWN
case 0x101: // WM_KEYUP
case 0x102: // WM_CHAR
case 0x200: // WM_MOUSEMOVE
case 0x2A1: // WM_MOUSEHOVER
case 0x201: // WM_LBUTTONDOWN
case 0x202: // WM_LBUTTONUP
case 0x285: // WM_IME_SELECT
case 0x300: // WM_CUT
case 0x301: // WM_COPY
case 0x302: // WM_PASTE
case 0x303: // WM_CLEAR
case 0x304: // WM_UNDO
base.WndProc(ref m);
this.CustomPaint();
break;
default:
base.WndProc(ref m);
break;
}
}
private Point Offset { get; set; }
public GlassControlRenderer(Control control, int xOffset, int yOffset)
{
this.Offset = new Point(xOffset, yOffset);
this.Control = control;
this.Bitmap = new Bitmap(this.Control.Width, this.Control.Height);
this.ControlGraphics = Graphics.FromHwnd(this.Control.Handle);
this.AssignHandle(this.Control.Handle);
}
public void CustomPaint()
{
this.Control.DrawToBitmap(this.Bitmap, new Rectangle(0, 0, this.Control.Width, this.Control.Height));
this.ControlGraphics.DrawImageUnscaled(this.Bitmap, this.Offset); // -1, -1 for content controls (e.g. TextBox, ListBox)
}
}
Ответ 2
У меня была проблема с мерцанием раньше (много элементов управления в форме, пользовательские элементы управления). Пробовал почти все. Это то, что сработало для меня:
Вы пытались поместить это в свой класс формы?
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
cp.ExStyle |= 0x00080000; // WS_EX_LAYERED
return cp;
}
}
И в вашем конструкторе вам нужно включить двойную буферизацию, иначе это не сработает:
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Он работает только тогда, когда aero включен, если он не может сделать мерцание еще хуже.
и вы также можете добавить это
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
return cp;
}
}
в ваш класс UserControls.