Контроль рендеринга на стекле: решение найдено, требует двойной буферизации/улучшения - программирование
Подтвердить что ты не робот

Контроль рендеринга на стекле: решение найдено, требует двойной буферизации/улучшения

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;
        }
    }
    
4b9b3361

Ответ 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.