Jak zawiesić malowanie kontrolki i jej dzieci?

Mam kontrolkę, do której muszę dokonać dużych modyfikacji. Chciałbym całkowicie zapobiec przerysowaniu, podczas gdy to robię - SuspendLayout i ResumeLayout to za mało. Jak zawiesić malowanie kontrolki i jej dzieci?

Author: Simon, 2009-01-28

10 answers

W mojej poprzedniej pracy mieliśmy problemy z uzyskaniem naszej bogatej aplikacji do malowania natychmiast i płynnie. Korzystaliśmy ze standardowych kontrolek. Net, niestandardowych oraz devexpress.

Po wielu googlowaniu i użyciu reflektora natknąłem się na komunikat WM_SETREDRAW win32. To naprawdę przestaje kontrolować rysowanie podczas ich aktualizacji i może być stosowane, IIRC do panelu nadrzędnego/zawierającego.

Jest to bardzo prosta klasa demonstrująca jak używać tego wiadomość:

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

Trwają dyskusje na ten temat-google dla C# i WM_SETREDRAW, np.

C# Jitter

Zawieszanie Layoutów

I do kogo to może dotyczyć, jest to podobny przykład w 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
 278
Author: ng5000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-22 23:30:29

Poniżej znajduje się to samo rozwiązanie ng5000, ale nie używa 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();
    }
}
 50
Author: ceztko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-10 04:36:33

Zwykle używam trochę zmodyfikowanej wersji odpowiedzi nglinka .

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

Pozwala to na zagnieżdżanie wywołań zawieszania/wznawiania. Musisz upewnić się, że każda SuspendDrawing jest zgodna z ResumeDrawing. Dlatego raczej nie byłoby dobrym pomysłem, aby je upublicznić.

 15
Author: Ozgur Ozcitak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:32

Aby pomóc nie zapomnieć o ponownym rysunku:

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

Użycie:

SuspendDrawing(myControl, () =>
{
    somemethod();
});
 11
Author: Jonathan H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-10 11:40:42

Ładne rozwiązanie bez użycia interop:

Jak zawsze, po prostu włącz DoubleBuffered = true na CustomControl. Następnie, jeśli masz jakieś kontenery, takie jak FlowLayoutPanel lub TableLayoutPanel, wywołaj klasę z każdego z tych typów, a w konstruktorach włącz podwójne buforowanie. Teraz po prostu używaj pochodnych kontenerów zamiast okien.Tworzy Pojemniki.

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

class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
    public FlowLayoutPanel()
    {
        DoubleBuffered = true;
    }
}
 8
Author: Eugenio De Hoyos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-10 22:53:44

Oto połączenie ceztko i ng5000, aby przynieść wersję rozszerzeń VB, która nie używa 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
 4
Author: goughy000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-12 15:47:57

Wiem, że to stare pytanie, na które już odpowiedziałem, ale oto moje zdanie na ten temat; zmieniłem zawieszenie aktualizacji na IDisposable - w ten sposób mogę dołączyć oświadczenia, które chcę uruchomić w using oświadczenie.

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();
    }
}
 3
Author: Scott Baker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-05 23:56:50

Bazując na odpowiedzi ng5000, lubię używać tego rozszerzenia:

        #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

Użycie:

using (this.BeginSuspendlock())
{
    //update GUI
}
 3
Author: Koray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-28 16:07:53

To jest jeszcze prostsze, i być może hacky - jak widzę sporo mięśni GDI na tym wątku , i jest oczywiście tylko dobre dopasowanie do pewnych scenariuszy. YMMV

W moim scenariuszu używam tego, co będę nazywał "rodzicem" UserControl - i podczas zdarzenia Load po prostu usuwam kontrolę, która ma być manipulowana z kolekcji rodzica .Controls, A rodzic OnPaint zajmuje się całkowitym malowaniem kontroli dziecka w jakikolwiek szczególny sposób.. pełne branie dziecka możliwości malowania offline.

Teraz przekazuję programowi malowania dziecka metodę rozszerzenia opartą na tej koncepcji Mike ' a Golda do drukowania formularzy windows .

Tutaj potrzebuję podzestaw etykiet do renderowania prostopadle do układu:

prosty schemat programu Visual Studio IDE

Następnie zwalniam kontrolę nad dzieckiem z malowania, z tym kodem w ParentUserControl.Load event handler:

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

Następnie, w tym samym Rodzicu, malujemy sterowanie-być-manipulowane od podstaw:

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

Gdy gdzieś hostujesz ParentUserControl, np. formularz Windows - stwierdzam, że moje Visual Studio 2015 renderuje formularz poprawnie w czasie projektowania, jak i podczas pracy: ParentUserControl hostowany w formie Windows lub być może innej kontroli użytkownika

Teraz, ponieważ moja szczególna manipulacja obraca kontrolę nad dzieckiem o 90 stopni, jestem pewien, że wszystkie gorące punkty i interaktywność zostały zniszczone w tym regionie - ale problem, który rozwiązywałem, polegał na etykiecie opakowania, która potrzebne do podglądu i wydruku, co wyszło mi dobrze.

Jeśli istnieją sposoby, aby przywrócić gorące punkty i kontrolę do mojej celowo osieroconej kontroli-chciałbym się o tym kiedyś dowiedzieć (Nie w tym scenariuszu, oczywiście, ale.. tylko się uczyć). Oczywiście WPF wspiera takie szaleństwo OOTB.. ale.. Hej.. WinForms jest nadal bardzo zabawne, amiright?

 2
Author: bkwdesign,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-08 17:25:36

Lub po prostu użyć Control.SuspendLayout() i Control.ResumeLayout().

 -4
Author: JustJoost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-26 19:24:47