Как приостановить рисование для элемента управления и его детей?

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

Ответ 1

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

После многого использования googling и рефлектора я наткнулся на сообщение 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 для С# и WM_SETREDRAW, например

С# Jitter

Приостановка раскладки

И к кому это может относиться, это аналогичный пример в 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

Ответ 2

Следующее одно и то же решение 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();
    }
}

Ответ 3

Обычно я использую небольшую измененную версию 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. Следовательно, было бы неплохо сделать их общедоступными.

Ответ 4

Чтобы не забывать повторно использовать чертеж:

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

Ответ 5

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

Как всегда, просто включите 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;
    }
}

Ответ 6

Вот комбинация 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

Ответ 7

Я знаю, что это старый вопрос, уже ответили, но вот мой подход к этому; Я реорганизовал приостановку обновлений в 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();
    }
}

Ответ 8

На основе ответа 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
}

Ответ 9

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

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

Теперь я передаю подпрограмму детской краски методу расширения, основанному на этой концепции от Mike Gold для печати форм Windows.

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

простая диаграмма вашей 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 размещен в Windows Form или, возможно, другом пользовательском элементе управления

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

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

Ответ 10

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