none
Visual Basic 2010 – Problème de synchro avec Graphics RRS feed

  • Question

  • Bonjour à tous,

    J’ai réalisé ce petit modèle afin de mieux appréhender les mécanismes sous-jacents à la création et à la gestion de différents types d’événements.

    Ce modèle affiche, dans la zone graphique du formulaire, trois dessins de compteur. L’affichage des compteurs se fait au moyen d’un objet Graphics. Le premier est déclaré par la méthode CreateGraphics du formulaire, les deux autres objets Graphics sont extrais de l’objet PaintEventArgs du gestionnaire d’événement Paint du formuliare. Tous les processus événementiels fonctionne correctement (du moins, je le pense) néanmoins l’affichage du premier compteur n’apparaît pas ou bien apparaît par intermittence alors que les deux autres compteurs s’affichent normalement. Tout se passe comme s’y il y avait un problème de synchronisation.

    Voici une capture d’écran pour illustrer la problèmatique

    J’ai tenté plusieurs opérations pour corriger ce problème, notamment la « bufférisation » de la mémoire, mais sans résultat. Aux personnes qui voudraient se pencher sur la question, merci.

    Ce projet fonctionne à partir de la création d’un projet WindowsForm de Visual Basic. En voici le code source. Bonne lecture

    Public Class Form1

        Dim Timer1 As New Temp

        Dim SF As New StringFormat

        Dim WH As Integer

        Friend Zone1, Zone2, Zone3 As Rectangle

        Dim CptFont As New Font("Arial", 25, FontStyle.Bold)

        Friend Shared N As Integer = 0

        Sub New()

            InitializeComponent()

            Me.Text = "Déclenchement d'événements"

            Me.StartPosition = FormStartPosition.Manual

            Me.SetBounds(Screen.PrimaryScreen.WorkingArea.Size.Width / 5, 100, 300, 150)

            Me.SizeGripStyle = Windows.Forms.SizeGripStyle.Show

            Me.BackColor = Color.Gray

            Me.DoubleBuffered = True

            Me.ResizeRedraw = True

            ' Destiné à éviter le scintillement sur le premier compteur (Pas concluant)

            'Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)

            'Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)

            SF.Alignment = StringAlignment.Center

            SF.LineAlignment = StringAlignment.Center

            AddHandler Me.Paint, AddressOf Compteur1_Paint

            AddHandler Me.Paint, AddressOf Compteur2_Paint

            AddHandler Me.Paint, AddressOf Compteur3_Paint

        End Sub

        ' ------   Gestionnaire d'événement : Test 1

        ' Evénement Click déclenché depuis une autre classe

        Protected Friend Sub Button1_Click(sender As System.Object, e As System.EventArgs) 'Handles Button1.Click

            Beep()

        End Sub

        ' ------   Gestionnaire d'événement : Test 2

        Friend Event Bip()

        ' Gestionnaire de l'Event Bip. L'événement est déclenché à partir d'une procédure située dans une classe différente de celle-ci

        Friend Sub Form1_Bip() Handles Me.Bip

            Beep()

        End Sub

        ' ------   Gestionnaire d'événement : Test 3

        ' Utilisation de WithEvents pour créer un événement - Le processus permet de changer la couleur de fond du formulaire

        Friend WithEvents Bg As New BackgroundEvent

        Public Class BackgroundEvent

            Public Event BackgroundChange()

            Sub RaiseEvents()

                RaiseEvent BackgroundChange()

            End Sub

            Sub New()

                ' Cette procédure n'est pas nécessaire, à moins que...

            End Sub

        End Class

        Private Sub Bg_BackgroundChange() Handles Bg.BackgroundChange

            If Me.BackColor = Color.White Then

                Me.BackColor = Color.Gray

            Else

                Me.BackColor = Color.White

            End If

        End Sub

        ' ------   Gestionnaire d'événement : Test 4

        ' Les trois gestionnaires ci-dessous sont destinés à afficher trois compteurs sur la surface graphique du formulaire

        ' Initialisation des aires d'affichage (Compteurs)

        Private Sub ZonesInitialize()

            WH = Me.ClientSize.Height / 2

            Zone1 = New Rectangle(Me.ClientSize.Width * 1 / 4 - WH / 2, Me.ClientSize.Height / 2 - WH / 2, WH, WH)

            Zone2 = New Rectangle(Me.ClientSize.Width * 2 / 4 - WH / 2, Me.ClientSize.Height / 2 - WH / 2, WH, WH)

            Zone3 = New Rectangle(Me.ClientSize.Width * 3 / 4 - WH / 2, Me.ClientSize.Height / 2 - WH / 2, WH, WH)

        End Sub

        ' Compteur1 - Utilisation de l'objet Graphics pour dessiner le compteur

        Protected Friend Sub Compteur1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) 'Handles Me.Paint

            Call ZonesInitialize()

            Call DrawGraphicsElements()

            'Call BufferedGraphicMemory()

        End Sub

        ' Processus pour un fonctionnement normal de la mémoire

        Private Sub DrawGraphicsElements()

            Dim g As Graphics = Me.CreateGraphics

            g.FillEllipse(Brushes.BlanchedAlmond, Zone1)

            g.DrawString(N, CptFont, Brushes.Brown, Zone1, SF)

            g.Dispose()

        End Sub

        ' Processus permettant d'accroître la mémoire allouée aux graphiques

        Private Sub BufferedGraphicMemory()

            Dim g As Graphics = Me.CreateGraphics

            Dim Buffered As BufferedGraphics

            Dim MemBufferedGraphics As New BufferedGraphicsContext

            '

            MemBufferedGraphics = BufferedGraphicsManager.Current

            MemBufferedGraphics.Invalidate()

            MemBufferedGraphics.MaximumBuffer = New Size(Zone1.Size)

            Buffered = MemBufferedGraphics.Allocate(Me.CreateGraphics, Zone1)

            'Buffered.Render(Graphics.FromHwnd(Me.Handle))

            With Buffered.Graphics

                .FillEllipse(Brushes.BlanchedAlmond, Zone1)

                .DrawString(N, CptFont, Brushes.Brown, Zone1, SF)

            End With

            ' Voir MSDN : Comment restituer manuellement des graphiques mis en mémoire tampon

            Buffered.Render(Me.CreateGraphics)

            'Buffered.Render()

            'Buffered.Dispose()

            'MemBufferedGraphics.Dispose()

            g.Dispose()

        End Sub

        Private Sub SynchronizeGraphicsElements()

        End Sub

        ' Compteur2 - Utilisation de l'objet Graphics issu de l'objet PaintEventArgs

        Protected Friend Sub Compteur2_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs)

            Call ZonesInitialize()

            e.Graphics.FillEllipse(Brushes.BurlyWood, Zone2)

            e.Graphics.DrawString(N, CptFont, Brushes.Brown, Zone2, SF)

        End Sub

        ' Compteur3 - Utilisation de l'objet Graphics issu de l'objet PaintEventArgs

        Protected Friend Sub Compteur3_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs)

            Call ZonesInitialize()

            e.Graphics.FillEllipse(Brushes.Tomato, Zone3)

            e.Graphics.DrawString(N, CptFont, Brushes.Brown, Zone3, SF)

        End Sub

    End Class

    '                           GESTION DU DECLENCHEMENT DES EVENEMENTS

    '       (Indépendance par rapport à la classe où sont déclarés et gérés les événements)

    '        ----------------------------------------------------------------------------------------------------------

    Public Class Temp

        Inherits Timer

        Sub New()

            Me.Interval = 1000

            Me.Start()

        End Sub

        ' Variable destiné à choisir la classe d'événement à déclencher

        Dim ClassEventSelected As Byte = 3

        Private Sub Temp_Tick(sender As Object, e As System.EventArgs) Handles Me.Tick

            Select Case ClassEventSelected

                Case Is = 1

                    ' Déclenchement de l'événement Click d'un bouton

                    ' Note: Un Beep retentit lorsque l'événement se produit

                    Form1.Button1_Click(Me, Nothing)

                Case Is = 2

                    ' Déclenchement de l'Event Bip

                    Form1.Form1_Bip()

                Case Is = 3

                    ' Déclenchement des 3 événements Paint et de WithEvents

                    ' Remarque : Le compteur 1 ne s'affiche qu'épisodiquement

                    ' Déclenchement de l'événement Paint du Compteur 1

                    Form1.Compteur1_Paint(Me, Nothing)

                    ' Déclenchement de l'événement Paint du Compteur 2

                    Form1.Invalidate(Form1.Zone2)

                    ' Déclenchement de l'événement Paint du Compteur 3

                    Dim pe As New PaintEventArgs(Form1.CreateGraphics, Form1.ClientRectangle)

                    Form1.Compteur3_Paint(Me, pe)

                    ' -----

                    ' Déclenchement de WithEvents (BackgroundChange) de la classe des événements (Form1)

                    Form1.Bg.RaiseEvents()

                Case Is = 4

                    ' Déclenchement de tous les événements simultanément

                    ' Remarque : Le compteur 1 ne s'affiche qu'épisodiquement

                    Form1.Refresh()

                Case Is = 5

                    ' Déclenchement des événements au moyen de la classe EventHandlerList

                    ' Non implémenté à ce jour

            End Select

            Form1.N += 1

        End Sub

    End Class

    Cordialement

    SL

    mardi 25 novembre 2014 07:28

Réponses

  • Si c'est pour tester des concepts, il faut tout d'abord que vos classes soient bien élaborées :

    La class Temp par exemple n'est pas du tout indépendante puisqu'elle suppose qu'une classe Form1 existe, qu'une instance de cette classe existe et qu'elles possèdent les méthodes Compteur1_Paint, etc.

    Bref, elle ne sert à rien et complique votre code.


    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mercredi 26 novembre 2014 09:27

Toutes les réponses

  • Bonjour,

    Je ne suis pas entré dans le code en détail mais quand vous dessinez sur une zone, ce dessin n'est pas persistant.

    Par exemple si vous dessinez sur un Panel et que vous faites apparaitre une autre fenêtre au dessus de votre fenêtre, votre dessin disparaitra.

    C'est pourquoi il faut sauvegarder ce que vous dessinez en mémoire et à chaque évènement Paint, redessiner.


    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 25 novembre 2014 11:21
  • Bonjour Richard,

    Tous les éléments de dessin sont redessinés à chaque Tick du Timer (1000 ms) dans la zone cliente du formulaire. Aucun autre contrôle ne vient s’interfacer dans le processus.

    Dans ce programme trois gestionnaires d’événement Paint sont implémentés, le premier gestionnaire implémente une méthode de type CreateGraphics pour définir la zone graphique du formulaire et les deux sont implémentés directement depuis la classe PaintEventArgs avec une procédure du type e.Graphics.(…). Seul le code du premier gestionnaire fait défaut, les deux fonctionnant 10/10 même lorsque le formulaire est redimensionné. En fait, il semblerait à première vue que le gestionnaire de Paint accomplisse bien son travail de peinture mais un processus sous-jacent vient détruire ce travail !

    Cordialement

    SL

    mardi 25 novembre 2014 15:13
  •     Protected Friend Sub Compteur1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) 'Handles Me.Paint
            If e Is Nothing Then Return
            ZonesInitialize()
    
            e.Graphics.FillEllipse(Brushes.BlanchedAlmond, Zone1)
            e.Graphics.DrawString(N, CptFont, Brushes.Brown, Zone1, SF)
        End Sub
    Ca marche comme ça.

    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 25 novembre 2014 15:25
  • Dans le corps de la procédure Paint, je veux absolument utiliser l’objet Graphics tel que :

        ' Compteur1 - Utilisation de l'objet Graphics pour dessiner le compteur

        Protected Friend Sub Compteur1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) 'Handles Me.Paint

            Call ZonesInitialize()

            Dim g As Graphics = Me.CreateGraphics

            g.FillEllipse(Brushes.BlanchedAlmond, Zone1)

            g.DrawString(N, CptFont, Brushes.Brown, Zone1, SF)

            g.Dispose()

        End Sub

    mardi 25 novembre 2014 15:44
  • Je ne sais pas pourquoi vous voulez procéder ainsi mais comme vous avez l’événement qui est déclenché, il invalide entièrement votre votre, donc redessine sur votre Compteur1.

    Encore une fois, il faut laisser au Framework le soin de faire les appels aux events Paint.


    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 25 novembre 2014 16:34
  • NB : mettez des Debug.WriteLine dans vos différents événements pour comprendre l'enchainement.

    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 25 novembre 2014 16:34
  • Bonjour Richard,

    Ce modèle est juste fait afin de mieux appréhender les concepts liés à la gestion des événements d’une part, et d’autre part de mieux percevoir les mécanismes de peinture au niveau d’un formulaire.

    Depuis notre dernière discution, j’ai :

    -      Réduit le code en enlevant l’information ne concernant pas les gestionnaires Paint du formulaire.

    -      Modifier un argument de PaintEventArgs

    > de : Dim pe As New PaintEventArgs(Form1.CreateGraphics, Form1.ClientRectangle)

    > à : Dim pe As New PaintEventArgs(Form1.CreateGraphics, Form1.Zone3)

    En limitant ainsi la zone de dessin à l’emplacement du Compteur3 et non à l’ensemble

    du formulaire, l’affichage du Compteur1 n’en est plus affecté.

    Cependant si l’on redimensionne le formulaire, l’affichage du Compteur1 redevient précaire.

    -     De l’étude du processus de déclenchement des événements, (Case 1) les événements sont déclenchés séquentiellement. Par contre si l’on opte pour le choix 2 (Case 2) ou encore si l’on redimensionne le formulaire alors les événements sont déclenchés en même temps (ou de façon synchrone). Dans ce cas l’affichage du premier compteur devient précaire ! Peut-on en déduire que la logique de fonctionnement des objets Graphics soit différente selon la provenance de l’objet (CreateGraphics ou PaintEventArgs) ? Peut-il y avoir « collision » entre des événements propres au formulaire et des événements imputés à des processus externes ?

    -     L’étude du cas 3 (Case 3), où un événement modifie la couleur de la zone client, permet de constater que lors de la modification de la zone cliente l’affichage des compteurs est aussi actualisé de façon synchrone avec l’événement modifiant la couleur de fond. Là encore l’affichage du premier compteur est précaire comme si la logique liée à l’événement Paint n’intégrait pas un mode synchrone !?

    Voici le nouveau processus simplifié (voir ci-après)

    Désolais mais la zone d'édition ne valide pas tout le texte au delà d'un certain nombre de caractères, aussi je vais retourner le code à la suite sur une nouvelle fenêtre d'édition.

    Cordialement

    SL


    • Modifié Santa Lina mercredi 26 novembre 2014 09:17
    mercredi 26 novembre 2014 09:12
  • ... Code source

    Public Class Form1

        Dim Timer1 As New Temp

        Dim SF As New StringFormat

        Dim WH As Integer

        Friend Zone1, Zone2, Zone3 As Rectangle

        Dim CptFont As New Font("Arial", 25, FontStyle.Bold)

        Friend Shared N As Integer = 0

        Sub New()

            InitializeComponent()

            Me.Text = "Déclenchement d'événements"

            Me.StartPosition = FormStartPosition.Manual

            Me.SetBounds(Screen.PrimaryScreen.WorkingArea.Size.Width / 5, 100, 300, 150)

            Me.SizeGripStyle = Windows.Forms.SizeGripStyle.Show

            Me.BackColor = Color.Gray

            Me.DoubleBuffered = True

            Me.ResizeRedraw = True

            SF.Alignment = StringAlignment.Center

            SF.LineAlignment = StringAlignment.Center

            AddHandler Me.Paint, AddressOf Compteur1_Paint

            AddHandler Me.Paint, AddressOf Compteur2_Paint

            AddHandler Me.Paint, AddressOf Compteur3_Paint

        End Sub

        ' ------   Gestionnaire d'événement : Test 1

        ' Utilisation de WithEvents pour créer un événement

        ‘ Le processus permet de changer la couleur de fond du formulaire

        Friend WithEvents Bg As New BackgroundEvent

        Public Class BackgroundEvent

            Public Event BackgroundChange()

            Sub RaiseEvents()

                RaiseEvent BackgroundChange()

            End Sub

        End Class

        Private Sub Bg_BackgroundChange() Handles Bg.BackgroundChange

            If Me.BackColor = Color.White Then

                Me.BackColor = Color.Gray

            Else

                Me.BackColor = Color.White

            End If

        End Sub

        ' ------   Gestionnaire d'événement : Test 2

        ' Les trois gestionnaires ci-dessous sont destinés à afficher trois compteurs indépendants

        ‘ sur la surface graphique du formulaire

        ' Initialisation des aires d'affichage (Compteurs)

        Private Sub ZonesInitialize()

            WH = Me.ClientSize.Height / 2

            Zone1 = New Rectangle(Me.ClientSize.Width * 1 / 4 - WH / 2, Me.ClientSize.Height / 2 - WH / 2, WH, WH)

            Zone2 = New Rectangle(Me.ClientSize.Width * 2 / 4 - WH / 2, Me.ClientSize.Height / 2 - WH / 2, WH, WH)

            Zone3 = New Rectangle(Me.ClientSize.Width * 3 / 4 - WH / 2, Me.ClientSize.Height / 2 - WH / 2, WH, WH)

        End Sub

        ' Compteur1 - Utilisation de l'objet Graphics pour dessiner le compteur

        Protected Friend Sub Compteur1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs)

            Call ZonesInitialize()

            Dim g As Graphics = Me.CreateGraphics

            g.FillEllipse(Brushes.BlanchedAlmond, Zone1)

            g.DrawString(N, CptFont, Brushes.Brown, Zone1, SF)

            g.Dispose()

        End Sub

        ' Compteur2 - Utilisation de l'objet Graphics issu de l'objet PaintEventArgs

        Protected Friend Sub Compteur2_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs)

            Call ZonesInitialize()

            e.Graphics.FillEllipse(Brushes.BurlyWood, Zone2)

            e.Graphics.DrawString(N, CptFont, Brushes.Brown, Zone2, SF)

        End Sub

        ' Compteur3 - Utilisation de l'objet Graphics issu de l'objet PaintEventArgs

        Protected Friend Sub Compteur3_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs)

            Call ZonesInitialize()

            e.Graphics.FillEllipse(Brushes.Tomato, Zone3)

            e.Graphics.DrawString(N, CptFont, Brushes.Brown, Zone3, SF)

        End Sub

    End Class

    '                           GESTION DU DECLENCHEMENT DES EVENEMENTS

    '       (Indépendance par rapport à la classe où sont déclarés et gérés les événements)

    '        ----------------------------------------------------------------------------------------------------------

    Public Class Temp

        Inherits Timer

        Sub New()

            Me.Interval = 1000

            Me.Start()

        End Sub

        Dim ClassEventSelected As Byte = 1

        Private Sub Temp_Tick(sender As Object, e As System.EventArgs) Handles Me.Tick

            Select Case ClassEventSelected

                Case Is = 1

                    ' Déclenchement des 3 événements Paint séparément

                    ' Remarque 1: Le compteur 1 s'affiche normalement

                    ' Remarque 2: Si l'on redimensionne le formulaire alors le compteur1

                    '                   ne s'affiche plus correctement

                    ' Déclenchement de l'événement Paint du Compteur 1

                    Form1.Compteur1_Paint(Me, Nothing)

                    ' Déclenchement de l'événement Paint du Compteur 2

                    Form1.Invalidate(Form1.Zone2)

                    ' Déclenchement de l'événement Paint du Compteur 3

                    Dim pe As New PaintEventArgs(Form1.CreateGraphics, Form1.Zone3)

                    Form1.Compteur3_Paint(Me, pe)

                Case Is = 2

                    ' Déclenchement de tous les événements simultanément

                    ' Remarque : Le compteur 1 ne s'affiche qu'épisodiquement

                    ' Le Compteur1 ne s'affiche pas correctement

                    'Form1.Refresh()

                    Form1.Invalidate()

                Case Is = 3

                    ' Déclenchement de WithEvents (BackgroundChange)

                    ' Modification du BackColor du Formulaire pour passer d'une couleur à une autre

                    ' Note: Changer la couleur du formulaire déclenche les trois événements Paint

                    ' Remarque: Le Compteur1 ne s'affiche pas correctement

                    Form1.Bg.RaiseEvents()

            End Select

            Form1.N += 1

        End Sub

    End Class

    Cordialement

    SL

    mercredi 26 novembre 2014 09:21
  • Si c'est pour tester des concepts, il faut tout d'abord que vos classes soient bien élaborées :

    La class Temp par exemple n'est pas du tout indépendante puisqu'elle suppose qu'une classe Form1 existe, qu'une instance de cette classe existe et qu'elles possèdent les méthodes Compteur1_Paint, etc.

    Bref, elle ne sert à rien et complique votre code.


    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mercredi 26 novembre 2014 09:27
  • Bonsoir Richard,

    J'espère faire mieux dans quelques temps.

    Cordialement

    SL

    vendredi 28 novembre 2014 21:25