it-swarm.com.de

Wie setze ich das Malen für ein Steuerelement und seine Kinder aus?

Ich habe ein Steuerelement, an dem ich große Änderungen vornehmen muss. Ich möchte vollständig verhindern, dass es währenddessen neu gezeichnet wird - SuspendLayout und ResumeLayout reichen nicht aus. Wie setze ich das Malen für ein Steuerelement und seine Kinder aus?

178
Simon

Bei meinem vorherigen Job hatten wir Mühe, unsere reichhaltige UI-App sofort und reibungslos zum Malen zu bringen. Wir verwendeten Standard-.NET-Steuerelemente, benutzerdefinierte Steuerelemente und Devexpress-Steuerelemente.

Nach vielem Googeln und Reflektieren bin ich auf die Meldung WM_SETREDRAW win32 gestoßen. Dies stoppt das Zeichnen von Steuerelementen wirklich, während Sie sie aktualisieren, und kann IIRC auf das übergeordnete/enthaltende Bedienfeld angewendet werden.

Dies ist eine sehr sehr einfache Klasse, die die Verwendung dieser Nachricht demonstriert:

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

Hierzu gibt es ausführlichere Diskussionen - Google für C # und WM_SETREDRAW, z.

C # -Zittern

Layouts anhalten

Und wen es betrifft, das ist ein ähnliches Beispiel 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
294
ng5000

Das Folgende ist die gleiche Lösung von ng5000, verwendet jedoch nicht 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();
    }
}
51
ceztko

Normalerweise verwende ich eine kleine modifizierte Version von ngLinks 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();
        }
    }
}

Dies ermöglicht das Verschachteln von Anhalten/Fortsetzen von Anrufen. Sie müssen sicherstellen, dass jedes SuspendDrawing mit einem ResumeDrawing übereinstimmt. Daher wäre es wahrscheinlich keine gute Idee, sie öffentlich zu machen.

15
Ozgur Ozcitak

So vergessen Sie nicht, das Zeichnen wieder zu aktivieren:

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

verwendung:

SuspendDrawing(myControl, () =>
{
    somemethod();
});
13
Jonathan H

Eine schöne Lösung ohne Verwendung von Interop:

Aktivieren Sie wie immer einfach DoubleBuffered = true in Ihrem CustomControl. Wenn Sie dann Container wie FlowLayoutPanel oder TableLayoutPanel haben, leiten Sie eine Klasse von jedem dieser Typen ab und aktivieren Sie in den Konstruktoren die doppelte Pufferung. Verwenden Sie jetzt einfach Ihre abgeleiteten Container anstelle der Windows.Forms-Container.

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

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

Basierend auf der Antwort von ng5000 verwende ich gerne diese Erweiterung:

        #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

Verwenden:

using (this.BeginSuspendlock())
{
    //update GUI
}
4
Koray

Hier ist eine Kombination von ceztko's und ng5000's, um eine VB Erweiterungsversion zu bringen, die kein Pinvoke verwendet

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
3
goughy000

Ich weiß, dass dies eine alte Frage ist, die bereits beantwortet wurde, aber hier ist meine Meinung dazu; Ich habe die Unterbrechung von Aktualisierungen in ein IDisposable überarbeitet. Auf diese Weise kann ich die Anweisungen, die ich ausführen möchte, in eine using -Anweisung einschließen.

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
Scott Baker

Das ist noch einfacher, und vielleicht hacky - wie ich viel sehen kann GDI Muskel auf diesem Thread und ist passt offensichtlich nur gut zu bestimmten Szenarien. YMMV

In meinem Szenario verwende ich das, was ich als "übergeordnetes" Benutzersteuerelement bezeichne - und während des Ereignisses Load entferne ich einfach das zu manipulierende Steuerelement aus dem übergeordneten .Controls Auflistung und das OnPaint der Eltern sorgen dafür, dass das Steuerelement des Kindes auf jede spezielle Weise vollständig gezeichnet wird. Die Malfunktionen des Kindes werden vollständig offline geschaltet.

Jetzt übergebe ich meine untergeordnete Paint-Routine an eine Erweiterungsmethode, die auf diesem Konzept von Mike Gold zum Drucken von Windows-Formularen basiert.

Hier benötige ich eine Untergruppe von Beschriftungen, um senkrecht zum Layout zu rendern:

simple diagram of your Visual Studio IDE

Dann befreie ich das untergeordnete Steuerelement vom Zeichnen mit diesem Code in der ParentUserControl.Load Eventhandler:

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

Dann malen wir in demselben ParentUserControl das zu manipulierende Steuerelement von Grund auf:

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

Sobald Sie das ParentUserControl irgendwo hosten, z. Windows Form - Ich stelle fest, dass mein Visual Studio 2015 das Formular zur Entwurfszeit sowie zur Laufzeit korrekt wiedergibt: ParentUserControl hosted in a Windows Form or perhaps other user control

Nun, da meine spezielle Manipulation die Kindersteuerung um 90 Grad dreht, bin ich sicher, dass alle Hotspots und Interaktivität in dieser Region zerstört wurden - aber das Problem, das ich gelöst habe, betraf ein Paketetikett, das in der Vorschau angezeigt und gedruckt werden musste. was für mich gut geklappt hat.

Wenn es Möglichkeiten gibt, die Hotspots und die Beherrschung wieder in meine absichtlich verwaiste Beherrschung einzuführen, würde ich gerne eines Tages etwas darüber lernen (natürlich nicht für dieses Szenario, aber ... nur um zu lernen). Natürlich unterstützt WPF eine solche Verrücktheit OOTB ... aber ... hey ... WinForms macht immer noch so viel Spaß, oder?

2
bkwdesign