Come sospendo la pittura per un controllo e i suoi figli?

Ho un controllo a cui devo apportare grandi modifiche. Mi piacerebbe impedirgli completamente di ridisegnare mentre faccio così: SuspendLayout e ResumeLayout non sono sufficienti. Come sospendo la pittura per un controllo e i suoi figli?

Nel mio precedente lavoro abbiamo faticato a far sì che la nostra ricca interfaccia utente dipingesse all’istante e senza intoppi. Stavamo usando controlli .Net standard, controlli personalizzati e controlli Devexpress.

Dopo un sacco di utilizzo di googling e reflector mi sono imbattuto nel messaggio Win32 di WM_SETREDRAW. Questo interrompe davvero il disegno mentre si aggiornano e possono essere applicati, IIRC al pannello principale / contenente.

Questa è una class molto semplice che dimostra come usare questo messaggio:

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

Ci sono discussioni più complete su questo – google per C # e WM_SETREDRAW, ad es

C # Jitter

Sospendere i layout

E a chi può interessare, questo è un esempio simile in VB:

 Public Module Extensions  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  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  Public Sub SuspendDrawing(ByVal Target As Control) SendMessage(Target.Handle, WM_SETREDRAW, False, 0) End Sub  Public Sub ResumeDrawing(ByVal Target As Control) ResumeDrawing(Target, True) End Sub End Module 

Quanto segue è la stessa soluzione di ng5000 ma non usa 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(); } } 

Di solito uso una versione leggermente modificata della risposta di 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(); } } } 

Ciò consente di sospendere / riprendere le chiamate da nidificare. È necessario assicurarsi di abbinare ogni SuspendDrawing con un ResumeDrawing . Quindi, probabilmente non sarebbe una buona idea renderle pubbliche.

Per aiutare con non dimenticare di ritriggersre il disegno:

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

utilizzo:

 SuspendDrawing(myControl, () => { somemethod(); }); 

Una bella soluzione senza usare interop:

Come sempre, abilita semplicemente DoubleBuffered = true sul tuo CustomControl. Quindi, se si dispone di contenitori come FlowLayoutPanel o TableLayoutPanel, derivare una class da ciascuno di questi tipi e nei costruttori, abilitare il doppio buffering. Ora, è sufficiente utilizzare i contenitori derivati ​​anziché i contenitori Windows.Forms.

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

Ecco una combinazione di ceztko e ng5000 per portare una versione di estensioni VB che non usa pinvoke

 Imports System.Runtime.CompilerServices Module ControlExtensions Dim WM_SETREDRAW As Integer = 11 '''  ''' A stronger "SuspendLayout" completely holds the controls painting until ResumePaint is called '''  '''  '''   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 '''  ''' Resume from SuspendPaint method '''  '''  '''   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 

So che questa è una vecchia domanda, ho già risposto, ma ecco la mia opinione su questo; Ho refactored la sospensione degli aggiornamenti in un IDisposable – in questo modo posso albind le dichiarazioni che voglio eseguire in una dichiarazione 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(); } } 

Sulla base della risposta di ng5000, mi piace usare questa estensione:

  #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 

Uso:

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

Questo è ancora più semplice, e forse hacky – come posso vedere un sacco di muscoli GDI su questo thread , ed è ovviamente solo una buona misura per alcuni scenari. YMMV

Nel mio scenario, utilizzo quello che .Controls UserControl “Parent” – e durante l’evento Load , rimuovo semplicemente il controllo da manipolare dalla collezione .Controls di Parent e l’ OnPaint di OnPaint si occupa di dipingendo completamente il controllo del bambino in qualsiasi modo speciale. Prendendo completamente le funzionalità del colore del bambino offline.

Ora distribuisco la mia routine di pittura per bambini a un metodo di estensione basato su questo concetto di Mike Gold per la stampa di moduli Windows .

Qui ho bisogno di un sottoinsieme di etichette da rendere perpendicolare al layout:

diagramma semplice del tuo IDE di Visual Studio

Quindi ParentUserControl.Load il controllo figlio da essere dipinto, con questo codice nel gestore di eventi 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 

Quindi, nello stesso ParentUserControl, dipingiamo il controllo da manipolare da zero:

 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 

Una volta che hai ospitato il ParentUserControl da qualche parte, ad esempio un Windows Form, sto riscontrando che il mio Visual Studio 2015 esegue correttamente il rendering del modulo in Design Time e durante il runtime: ParentUserControl ospitato in un Windows Form o forse un altro controllo utente

Ora, dato che la mia particolare manipolazione ruota il controllo del bambino di 90 gradi, sono sicuro che tutti i punti caldi e l’interattività sono stati distrutti in quella regione – ma il problema che stavo risolvendo era tutto per un’etichetta di pacchetto che doveva essere visualizzata e stampata, che ha funzionato bene per me.

Se ci sono modi per reintrodurre i punti caldi e il controllo sul mio controllo volutamente orfano – mi piacerebbe conoscerlo un giorno (non per questo scenario, ovviamente, ma .. solo per imparare). Naturalmente, WPF supporta tale follia OOTB .. ma .. hey .. WinForms è così divertente ancora, amiright?

O semplicemente utilizzare Control.SuspendLayout() e Control.ResumeLayout() .