Comment puis-je suspendre la peinture pour un contrôle et ses enfants?
j'ai un contrôle sur lequel je dois faire de grandes modifications. J'aimerais l'empêcher complètement de se redessiner pendant que je le fais - SuspendLayout et ResumeLayout ne sont pas assez. Comment puis-je suspendre la peinture pour un contrôle et ses enfants?
10 réponses
à mon travail précédent, nous avons eu du mal à obtenir notre application D'interface utilisateur riche pour peindre instantanément et en douceur. Nous utilisions les commandes standard .Net, les commandes personnalisées et les commandes devexpress.
après beaucoup d'utilisation de googling et de réflecteur, je suis tombé sur le message win32 WM_SETRAW. Cela arrête vraiment le dessin des commandes pendant que vous les mettez à jour et peut être appliqué, IIRC au panneau parent/containing.
il s'agit d'une classe très très simple démontrant comment pour utiliser ce 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();
}
}
Il ya des discussions plus complètes sur ce-google pour C# et WM_SETREW, par exemple
et à qui cela peut concerner, c'est un exemple similaire dans 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
ce qui suit est la même solution de ng5000 mais n'utilise pas 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();
}
}
j'utilise habituellement une version modifiée de la réponse de 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();
}
}
}
cela permet d'imbriquer les appels de suspension/reprise. Vous devez vous assurer de faire correspondre chaque SuspendDrawing
avec un ResumeDrawing
. Par conséquent, il ne serait probablement pas une bonne idée de les rendre publiques.
pour aider à ne pas oublier de reenable dessin:
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();
}
utilisation:
SuspendDrawing(myControl, () =>
{
somemethod();
});
une solution agréable sans utiliser interop:
comme toujours, il vous suffit d'activer DoubleBuffered=true sur votre CustomControl. Ensuite, si vous avez des conteneurs comme FlowLayoutPanel ou TableLayoutPanel, dérivez une classe de chacun de ces types et dans les constructeurs, activez double buffering. Maintenant, utilisez simplement vos conteneurs dérivés au lieu des fenêtres.Formulaires Conteneurs.
class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
public TableLayoutPanel()
{
DoubleBuffered = true;
}
}
class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
public FlowLayoutPanel()
{
DoubleBuffered = true;
}
}
voici une combinaison de ceztko et ng5000 pour apporter une version VB extensions qui n'utilise pas 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
je sais que c'est une vieille question, déjà répondu, Mais voici mon point de vue sur ce; j'ai refactorisé la suspension des mises à jour dans un IDisposable - de cette façon, je peux joindre les déclarations que je veux exécuter dans une déclaration 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();
}
}
basé sur la réponse de ng5000, j'aime utiliser cette 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
utiliser:
using (this.BeginSuspendlock())
{
//update GUI
}
C'est encore plus simple, et peut - être hacky-comme je peux voir beaucoup de muscle GDI sur ce fil , et est évidemment seulement un bon ajustement pour certains scénarios. YMMV
dans mon scénario, j'utilise ce que je vais appeler un contrôle D'utilisateur "Parent" - et pendant l'événement Load
, je supprime tout simplement le contrôle-à-être-manipulé de la collection .Controls
du Parent, et le OnPaint
du Parent s'occupe de tout peindre le contrôle de l'enfant d'une manière spéciale.. enlever complètement les capacités de peinture de l'enfant.
maintenant, je passe ma routine de peinture d'enfant à une méthode d'extension basée sur ce concept de Mike Gold pour l'impression des formes de fenêtres .
J'ai besoin D'un sous-ensemble d'étiquettes pour rendre perpendiculaire au layout:
alors, je dispense le contrôle de l'enfant d'être peint, avec ce code dans le 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
puis, dans le même ParentUserControl, nous peignons le contrôle-à-être-manipulé à partir de la base:
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
une fois que vous hébergez le ParentUserControl quelque part, par exemple un formulaire Windows - je constate que mon Visual Studio 2015 rend le formulaire correctement à la conception Temps ainsi que de l'exécution:
maintenant, puisque ma manipulation particulière fait tourner le contrôle de l'enfant à 90 degrés, je suis sûr que tous les points chauds et l'interactivité ont été détruits dans cette région - mais, le problème que je résolvais était tout pour une étiquette de paquet qui avait besoin de prévisualiser et imprimer, qui a fonctionné très bien pour moi.
S'il y a des moyens de réintroduire les points chauds et le contrôle à mon intention contrôle orphelin - j'aimerais apprendre à ce sujet un jour (pas pour ce scénario, bien sûr, mais.. tout juste de l'apprendre). Bien sûr, la WPF soutient une telle folie OOTB.. mais.. Hey.. WinForms est toujours si amusant, n'est-ce pas?
ou utilisez simplement Control.SuspendLayout()
et Control.ResumeLayout()
.