Визуализация элементов управления на стекле: решение найдено, требуется двойная буферизация / совершенствование - PullRequest
46 голосов
/ 15 августа 2011

Я (наконец-то!) Нашел способ рендеринга элементов управления Windows.Forms на стекле, который, похоже, не имеет ни существенного недостатка, ни большого времени реализации.Это вдохновлено этой статьей от Coded, которая в основном объясняет, как естественным образом переопределить рисование элементов управления, чтобы рисовать над ними.

Я использовал этот подход, чтобы отобразить элемент управления в растровое изображение и нарисовать егообратно с GDI + и соответствующим альфа-каналом над областью рисования NativeWindow.Реализация проста, но может быть усовершенствована для удобства использования, но не в этом вопрос.Результаты, однако, вполне удовлетворительные:

Real textbox on glass

Тем не менее, есть две области, которые должны быть исправлены, чтобы это было действительно полезным.

  1. Двойная буферизация , потому что мерцание между этим наложенным изображением и реальным контролем часто и ужасно (проверьте себя с помощью кода).Установка базового элемента управления для двойной буферизации с SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true) не работает, но я подозреваю, что мы можем заставить его работать с небольшой пробой и ошибкой.
  2. Некоторые элементы управления не работают.Я смог сделать следующую работу:

    • 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;
        }
    }
    

Ответы [ 2 ]

6 голосов
/ 30 сентября 2011

Вот версия с гораздо меньшим мерцанием, но все же не идеальная.

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)
    }
}
1 голос
/ 30 сентября 2011

У меня раньше была проблема с мерцанием (много элементов управления в форме, пользовательские элементы управления). Перепробовал практически все. Вот что у меня сработало:

Вы пытались поместить это в класс?

    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);

Работает только при включенном аэро, если нет, то мерцание может быть еще хуже.

и вы также можете добавить это

  protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;

                cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
            return cp;
        }
    } 

в ваш класс UserControls.

...