Как приостановить рисование для элемента управления и его дочерних элементов? - PullRequest
176 голосов
/ 28 января 2009

У меня есть элемент управления, в который я должен внести большие изменения. Я хотел бы полностью предотвратить перерисовку, пока я делаю это - SuspendLayout и ResumeLayout недостаточно. Как приостановить рисование для элемента управления и его дочерних элементов?

Ответы [ 10 ]

289 голосов
/ 28 января 2009

На моей предыдущей работе мы боролись за то, чтобы наше приложение с богатым пользовательским интерфейсом рисовало быстро и плавно. Мы использовали стандартные элементы управления .Net, пользовательские элементы управления и элементы управления devexpress.

После долгих поисков и поисков я наткнулся на сообщение win32 WM_SETREDRAW. Это действительно останавливает рисование элементов управления, пока вы обновляете их, и может применяться IIRC к родительской / содержащей панели.

Это очень очень простой класс, демонстрирующий, как использовать это сообщение:

class DrawingControl
{
    [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();
    }
}

Есть более подробные обсуждения по этому вопросу - Google для C # и WM_SETREDRAW, например,

C # Джиттер

Подвесные макеты

И к кому это может относиться, это аналогичный пример в VB:

Public Module Extensions
    <DllImport("user32.dll")>
    Private Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Boolean, ByVal lParam As IntPtr) As Integer
    End Function

    Private Const WM_SETREDRAW As Integer = 11

    ' Extension methods for Control
    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control, ByVal Redraw As Boolean)
        SendMessage(Target.Handle, WM_SETREDRAW, True, 0)
        If Redraw Then
            Target.Refresh()
        End If
    End Sub

    <Extension()>
    Public Sub SuspendDrawing(ByVal Target As Control)
        SendMessage(Target.Handle, WM_SETREDRAW, False, 0)
    End Sub

    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control)
        ResumeDrawing(Target, True)
    End Sub
End Module
51 голосов
/ 10 июня 2011

Ниже приведено то же решение ng5000, но не используется P / Invoke.

public static class SuspendUpdate
{
    private const int WM_SETREDRAW = 0x000B;

    public static void Suspend(Control control)
    {
        Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgSuspendUpdate);
    }

    public static void Resume(Control control)
    {
        // Create a C "true" boolean as an IntPtr
        IntPtr wparam = new IntPtr(1);
        Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgResumeUpdate);

        control.Invalidate();
    }
}
14 голосов
/ 24 ноября 2009

Я обычно использую немного измененную версию ответа ngLink .

public class MyControl : Control
{
    private int suspendCounter = 0;

    private void SuspendDrawing()
    {
        if(suspendCounter == 0) 
            SendMessage(this.Handle, WM_SETREDRAW, false, 0);
        suspendCounter++;
    }

    private void ResumeDrawing()
    {
        suspendCounter--; 
        if(suspendCounter == 0) 
        {
            SendMessage(this.Handle, WM_SETREDRAW, true, 0);
            this.Refresh();
        }
    }
}

Это позволяет приостановить / возобновить вызовы, которые будут вложены. Вы должны убедиться, что каждый SuspendDrawing соответствует ResumeDrawing. Следовательно, было бы неплохо сделать их публичными.

12 голосов
/ 10 марта 2015

Чтобы не забыть включить рисунок:

public static void SuspendDrawing(Control control, Action action)
{
    SendMessage(control.Handle, WM_SETREDRAW, false, 0);
    action();
    SendMessage(control.Handle, WM_SETREDRAW, true, 0);
    control.Refresh();
}

использование:

SuspendDrawing(myControl, () =>
{
    somemethod();
});
8 голосов
/ 11 марта 2010

Хорошее решение без использования взаимодействия:

Как всегда, просто включите DoubleBuffered = true в CustomControl. Затем, если у вас есть какие-либо контейнеры, такие как FlowLayoutPanel или TableLayoutPanel, выведите класс из каждого из этих типов и в конструкторах включите двойную буферизацию. Теперь просто используйте производные контейнеры вместо контейнеров Windows.Forms.

class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
    public TableLayoutPanel()
    {
        DoubleBuffered = true;
    }
}

class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
    public FlowLayoutPanel()
    {
        DoubleBuffered = true;
    }
}
4 голосов
/ 28 октября 2016

Исходя из ответа ng5000, мне нравится использовать это расширение:

        #region Suspend
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
        private const int WM_SETREDRAW = 11;
        public static IDisposable BeginSuspendlock(this Control ctrl)
        {
            return new suspender(ctrl);
        }
        private class suspender : IDisposable
        {
            private Control _ctrl;
            public suspender(Control ctrl)
            {
                this._ctrl = ctrl;
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, false, 0);
            }
            public void Dispose()
            {
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, true, 0);
                this._ctrl.Refresh();
            }
        }
        #endregion

Использование:

using (this.BeginSuspendlock())
{
    //update GUI
}
3 голосов
/ 12 марта 2012

Вот комбинация ceztko и ng5000 для создания версии расширений VB, которая не использует pinvoke

Imports System.Runtime.CompilerServices

Module ControlExtensions

Dim WM_SETREDRAW As Integer = 11

''' <summary>
''' A stronger "SuspendLayout" completely holds the controls painting until ResumePaint is called
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub SuspendPaint(ByVal ctrl As Windows.Forms.Control)

    Dim msgSuspendUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, System.IntPtr.Zero, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgSuspendUpdate)

End Sub

''' <summary>
''' Resume from SuspendPaint method
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub ResumePaint(ByVal ctrl As Windows.Forms.Control)

    Dim wparam As New System.IntPtr(1)
    Dim msgResumeUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, wparam, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgResumeUpdate)

    ctrl.Invalidate()

End Sub

End Module
2 голосов
/ 08 января 2016

Это еще проще, и, возможно, хакерство - поскольку я вижу много GDI-мышц в этой теме , и , очевидно, подходит только для определенных сценариев. YMMV

В моем сценарии я использую то, что я буду называть «Родительским» UserControl - и во время события Load я просто удаляю элемент управления, которым нужно манипулировать, из коллекции .Controls Родителя, и OnPaint Родителя позаботится о том, чтобы полностью нарисовать дочерний элемент управления каким-либо особым образом ... полностью отключить возможности рисования ребенка в автономном режиме.

Теперь я передаю свою дочернюю процедуру рисования методу расширения на основе этой концепции от Майка Голда для печати оконных форм .

Здесь мне нужно подмножество меток для визуализации перпендикулярно к макету:

simple diagram of your Visual Studio IDE

Затем я освобождаю дочерний элемент управления от рисования с помощью этого кода в обработчике ParentUserControl.Load:

Private Sub ParentUserControl_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    SetStyle(ControlStyles.UserPaint, True)
    SetStyle(ControlStyles.AllPaintingInWmPaint, True)

    'exempt this control from standard painting: 
    Me.Controls.Remove(Me.HostedControlToBeRotated) 
End Sub

Затем в том же ParentUserControl мы рисуем элемент управления, которым нужно манипулировать с нуля:

Protected Overrides Sub OnPaint(e As PaintEventArgs)
    'here, we will custom paint the HostedControlToBeRotated instance...

    'twist rendering mode 90 counter clockwise, and shift rendering over to right-most end 
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    e.Graphics.TranslateTransform(Me.Width - Me.HostedControlToBeRotated.Height, Me.Height)
    e.Graphics.RotateTransform(-90)
    MyCompany.Forms.CustomGDI.DrawControlAndChildren(Me.HostedControlToBeRotated, e.Graphics)

    e.Graphics.ResetTransform()
    e.Graphics.Dispose()

    GC.Collect()
End Sub

После того, как вы где-нибудь разместите ParentUserControl, например, форма Windows - я обнаружил, что моя Visual Studio 2015 правильно отображает форму во время разработки, а также во время выполнения: ParentUserControl hosted in a Windows Form or perhaps other user control

Теперь, так как мои конкретные манипуляции поворачивают дочерний элемент управления на 90 градусов, я уверен, что все горячие точки и интерактивность были уничтожены в этом регионе - но проблема, которую я решал, была связана с меткой пакета, которую нужно было предварительно просмотреть. и распечатать, что отлично сработало для меня.

Если есть способы восстановить горячие точки и контроль для моего намеренно осиротевшего контроля - я бы с радостью узнал об этом когда-нибудь (конечно, не для этого сценария, а ... просто для изучения). Конечно, WPF поддерживает такое безумие, OOTB ... но ... эй ... WinForms все еще очень забавны, правда?

2 голосов
/ 06 января 2015

Я знаю, что это старый вопрос, уже отвеченный, но вот мой взгляд на это; Я реорганизовал приостановку обновлений в IDisposable - таким образом я могу заключить операторы, которые я хочу выполнить, в оператор using.

class SuspendDrawingUpdate : IDisposable
{
    private const int WM_SETREDRAW = 0x000B;
    private readonly Control _control;
    private readonly NativeWindow _window;

    public SuspendDrawingUpdate(Control control)
    {
        _control = control;

        var msgSuspendUpdate = Message.Create(_control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);

        _window = NativeWindow.FromHandle(_control.Handle);
        _window.DefWndProc(ref msgSuspendUpdate);
    }

    public void Dispose()
    {
        var wparam = new IntPtr(1);  // Create a C "true" boolean as an IntPtr
        var msgResumeUpdate = Message.Create(_control.Handle, WM_SETREDRAW, wparam, IntPtr.Zero);

        _window.DefWndProc(ref msgResumeUpdate);

        _control.Invalidate();
    }
}
0 голосов
/ 14 января 2012

Или просто используйте Control.SuspendLayout() и Control.ResumeLayout().

...