191 votes

Comment suspendre le dessin pour un contrôle et ses enfants?

J'ai un contrôle sur lequel je dois apporter de grandes modifications. J'aimerais complètement l'empêcher de se redessiner pendant que je le fais - SuspendLayout et ResumeLayout ne suffisent pas. Comment suspendre le dessin pour un contrôle et ses enfants?

3 votes

Quelqu'un peut-il s'il vous plaît m'expliquer ce qu'est un dessin ou une peinture ici dans ce contexte ? (Je suis nouveau sur .net) au moins fournir un lien.

1 votes

C'est vraiment une honte (ou risible) que .Net existe depuis plus de 15 ans et que ce soit toujours un problème. Si Microsoft passait autant de temps à résoudre de vrais problèmes comme le scintillement de l'écran que, disons, Get Windows X les logiciels malveillants, alors cela aurait été résolu il y a longtemps.

0 votes

@jww Ils l'ont corrigé; ça s'appelle WPF.

324voto

ng5000 Points 4556

À mon ancien travail, nous avons eu du mal à faire en sorte que notre application riche en UI se peigne instantanément et en douceur. Nous utilisions des contrôles standard .Net, des contrôles personnalisés et des contrôles devexpress.

Après beaucoup de recherches sur Google et l'utilisation de Reflector, j'ai découvert le message WM_SETREDRAW de win32. Cela arrête vraiment le dessin des contrôles pendant que vous les mettez à jour et peut être appliqué, si je me souviens bien, au panneau parent/conteneur.

Il s'agit d'une classe très simple montrant comment 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 y a des discussions plus détaillées à ce sujet - cherchez sur Google "C# and WM_SETREDRAW", par exemple.

C# Jitter

Suspension des mises en page

Et pour ceux que cela intéresse, voici un exemple similaire en 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

    ' Méthodes d'extension pour Control

    Public Sub ResumeDrawing(ByVal Target As Control, ByVal Redraw As Boolean)
        SendMessage(Target.Handle, WM_SETREDRAW, True, IntPtr.Zero)
        If Redraw Then
            Target.Refresh()
        End If
    End Sub

    Public Sub SuspendDrawing(ByVal Target As Control)
        SendMessage(Target.Handle, WM_SETREDRAW, False, IntPtr.Zero)
    End Sub

    Public Sub ResumeDrawing(ByVal Target As Control)
        ResumeDrawing(Target, True)
    End Sub
End Module

48 votes

Quelle excellente réponse et quelle aide formidable! J'ai créé des méthodes d'extension SuspendDrawing et ResumeDrawing pour la classe Control, donc je peux les appeler pour n'importe quel contrôle dans n'importe quel contexte.

2 votes

Avait un contrôle de type arborescence qui ne rafraîchissait correctement un nœud qu'en restructurant ses enfants si vous le repliiez puis l'agrandissiez, ce qui entraînait un clignotement désagréable. Cela a parfaitement fonctionné pour contourner le problème. Merci! (De manière amusante, le contrôle avait déjà importé SendMessage, et défini WM_SETREDRAW, mais ne l'utilisait effectivement pour rien. Maintenant si.)

7 votes

Ceci n'est pas particulièrement utile. C'est exactement ce que la classe de base Control pour tous les contrôles WinForms fait déjà pour les méthodes BeginUpdate et EndUpdate. Envoyer le message vous-même n'est pas mieux que d'utiliser ces méthodes pour faire le gros du travail à votre place, et ne peut certainement pas produire des résultats différents.

53voto

ceztko Points 1729

La solution suivante est la même que ng5000 mais ne 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)
    {
        // Créer un boolean "true" en C comme un 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();
    }
}

0 votes

J'ai essayé, mais à l'étape intermédiaire entre Suspendre et Reprendre, cela se révèle simplement invalide. Cela peut sembler étrange, mais est-il possible de conserver son état avant la suspension dans l'état ​​transitoire?

2 votes

Suspendre un contrôle signifie qu'aucun dessin ne sera effectué du tout dans la zone de contrôle et vous pouvez obtenir des restes dessinés provenant d'autres fenêtres/contrôles de cette surface. Vous pouvez essayer d'utiliser un contrôle avec DoubleBuffer défini sur vrai si vous souhaitez que l'état précédent soit dessiné jusqu'à ce que le contrôle soit repris (si j'ai bien compris ce que vous vouliez dire), mais je ne peux pas garantir que cela fonctionnera. Quoi qu'il en soit, je pense que vous manquez le point d'utiliser cette technique : elle vise à éviter que l'utilisateur voie un dessin progressif et lent des objets (il vaut mieux les voir tous apparaître ensemble). Pour d'autres besoins, utilisez d'autres techniques.

6 votes

Belle réponse, mais ce serait beaucoup mieux de montrer où se trouvent Message et NativeWindow; chercher de la documentation pour une classe nommée Message n'est pas vraiment divertissant.

19voto

Ozgur Ozcitak Points 4111

J'utilise généralement une version légèrement 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'emboîter 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 publics.

4 votes

Cela aide à garder les appels équilibrés : SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }. Une autre option est de mettre en œuvre ceci dans une classe IDisposable et d'encadrer la partie de dessin dans une instruction using. Le handle serait passé au constructeur, qui suspendrait le dessin.

0 votes

Ancienne question, je sais, mais l'envoi de "false" ne semble pas fonctionner dans les versions récentes de c#/VS. J'ai dû changer false en 0 et true en 1.

0 votes

@MauryMarkowitz est-ce que votre DllImport déclare wParam comme bool?

8voto

Eugenio De Hoyos Points 872

Une belle solution sans utiliser l'interop :

Comme toujours, il 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 le double buffering. Maintenant, utilisez simplement vos Conteneurs dérivés au lieu des Conteneurs Windows.Forms.

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

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

2 votes

C'est certainement une technique utile - que j'utilise assez souvent pour les ListViews - mais elle ne prévient pas réellement les redessinements d'avoir lieu; ils ont toujours lieu hors de l'écran.

4 votes

Vous avez raison, cela résout le problème de scintillement, pas le problème de redessin hors écran spécifiquement. Lorsque je cherchais une solution au scintillement, je suis tombé sur plusieurs fils de discussion connexes comme celui-ci, et quand je l'ai trouvé, je n'ai peut-être pas posté dans le fil le plus pertinent. Cependant, lorsque la plupart des gens veulent suspendre la peinture, ils font probablement référence à la peinture à l'écran, qui est souvent un problème plus évident que la peinture redondante hors écran, donc je pense toujours que d'autres personnes pourraient trouver cette solution utile dans ce fil de discussion.

0 votes

Remplacer OnPaint.

4voto

goughy000 Points 321

Voici une combinaison des contributions de ceztko et ng5000 pour apporter une version des extensions VB qui n'utilise pas pinvoke

Imports System.Runtime.CompilerServices

Module ControlExtensions

Dim WM_SETREDRAW As Integer = 11

''' 
''' Un "SuspendLayout" plus fort qui bloque complètement le dessin des contrôles jusqu'à ce que ResumePaint soit appelé
''' 
''' 
''' 

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

''' 
''' Reprendre à partir de la méthode SuspendPaint
''' 
''' 
''' 

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

4 votes

Je travaille avec une application WPF qui utilise des formulaires Windows intercalés avec les formulaires WPF, et je suis confronté à des problèmes de scintillement de l'écran. Je suis confus sur la façon dont ce code devrait être utilisé - devrait-il aller dans la fenêtre Winform ou WPF? Ou n'est-il pas adapté à ma situation particulière?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X