How do I suspend painting for a control and its children?

前端 未结 10 1944
小鲜肉
小鲜肉 2020-11-22 01:34

I have a control which I have to make large modifications to. I\'d like to completely prevent it from redrawing while I do that - SuspendLayout and ResumeLayout aren\'t eno

相关标签:
10条回答
  • 2020-11-22 01:43

    To help with not forgetting to reenable drawing:

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

    usage:

    SuspendDrawing(myControl, () =>
    {
        somemethod();
    });
    
    0 讨论(0)
  • 2020-11-22 01:44

    At my previous job we struggled with getting our rich UI app to paint instantly and smoothly. We were using standard .Net controls, custom controls and devexpress controls.

    After a lot of googling and reflector usage I came across the WM_SETREDRAW win32 message. This really stops controls drawing whilst you update them and can be applied, IIRC to the parent/containing panel.

    This is a very very simple class demonstrating how to use this message:

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

    There are fuller discussions on this - google for C# and WM_SETREDRAW, e.g.

    C# Jitter

    Suspending Layouts

    And to whom it may concern, this is similar example in 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
    
    0 讨论(0)
  • 2020-11-22 01:46

    This is even simpler, and perhaps hacky - as I can see a lot of GDI muscle on this thread, and is obviously only a good fit for certain scenarios. YMMV

    In my scenario, I use what I'll refer to as a "Parent" UserControl - and during the Load event, I simply remove the control-to-be-manipulated from the Parent's .Controls collection, and the Parent's OnPaint takes care of completely painting the child control in whatever special way.. fully taking the child's paint capabilities offline.

    Now, I hand off my child paint routine to an extension method based off this concept from Mike Gold for printing windows forms.

    Here I'm needing a sub-set of labels to render perpendicular to the layout:

    Then, I exempt the child control from being painted, with this code in the 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
    

    Then, in the same ParentUserControl, we paint the control-to-be-manipulated from the ground up:

    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
    

    Once you host the ParentUserControl somewhere, e.g. a Windows Form - I'm finding that my Visual Studio 2015 renders the form correctly at Design Time as well as runtime:

    Now, since my particular manipulation rotates the child control 90 degrees, I'm sure all the hot spots and interactivity has been destroyed in that region - but, the problem I was solving was all for a package label that needed to preview and print, which worked out fine for me.

    If there are ways to reintroduce the hot spots and control-ness to my purposely orphaned control - I'd love to learn about that someday (not for this scenario, of course, but.. just to learn). Of course, WPF supports such craziness OOTB.. but.. hey.. WinForms is so much fun still, amiright?

    0 讨论(0)
  • 2020-11-22 01:47

    Here is a combination of ceztko's and ng5000's to bring a VB extensions version that doesn't use 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
    
    0 讨论(0)
  • 2020-11-22 01:52

    Based on ng5000's answer, I like using this extension:

            #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
    

    Use:

    using (this.BeginSuspendlock())
    {
        //update GUI
    }
    
    0 讨论(0)
  • 2020-11-22 01:56

    I usually use a little modified version of ngLink's answer.

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

    This allows suspend/resume calls to be nested. You must make sure to match each SuspendDrawing with a ResumeDrawing. Hence, it wouldn't probably be a good idea to make them public.

    0 讨论(0)
提交回复
热议问题