Я (наконец-то!) Нашел способ рендеринга элементов управления Windows.Forms на стекле, который, похоже, не имеет ни существенного недостатка, ни большого времени реализации.Это вдохновлено этой статьей от Coded, которая в основном объясняет, как естественным образом переопределить рисование элементов управления, чтобы рисовать над ними.
Я использовал этот подход, чтобы отобразить элемент управления в растровое изображение и нарисовать егообратно с GDI + и соответствующим альфа-каналом над областью рисования NativeWindow.Реализация проста, но может быть усовершенствована для удобства использования, но не в этом вопрос.Результаты, однако, вполне удовлетворительные:
Тем не менее, есть две области, которые должны быть исправлены, чтобы это было действительно полезным.
- Двойная буферизация , потому что мерцание между этим наложенным изображением и реальным контролем часто и ужасно (проверьте себя с помощью кода).Установка базового элемента управления для двойной буферизации с
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.
РЕДАКТИРОВАТЬ: Возможные пути для двойной буферизации / защиты от мерцания:
- Удаление строки
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;
}
}