none
Rafraîchissement d'une TrackBar après changement de valeur RRS feed

  • Question

  • Bonjour,

    Je rencontre actuellement un problème plutôt embêtant avec une TrackBar sous Visual Basic Express. En effet, mon programme à un moment donné doit modifier la valeur de cette TrackBar depuis une autre Form. La valeur est bien modifiée (un MsgBox avant et après modification de la valeur permet de le confirmer), cependant sur mon formulaire la TrackBar ne bouge pas.

    Un peu plus de précisions sur le programme :

    - Un formulaire principal contient plusieurs TrackBar (TrackBar1 à TrackBar32), allant de 0 à 255. Il contient également une CheckBox en dessous de chaque TrackBar.

    - Depuis ce formulaire principal, je peux ouvrir un second formulaire. Dans ce second formulaire, je peux appeler une procédure pour changer la valeur des TrackBar et CheckBox du formulaire principal, de deux manières différentes : soit en utilisant un thread créé sur le second formulaire, soit directement sans passer par un thread.

    - En modifiant directement sans passer par un thread, ça fonctionne : les valeurs sont modifiées et ça se voit sur le formulaire principal.

    - En passant par un thread (nécessaire car je souhaite faire varier progressivement les valeurs des TrackBar, faire avec un Timer fait ramer le programme), les valeurs sont bien modifiées (je le vois en affichant dans une msgbox les valeurs avant et après)... Mais le contrôleur sur le formulaire ne bouge pas ! Code de la procédure dans le formulaire principal pour un changement de valeur de 0 à 10, appelée par le formulaire secondaire :

    MsgBox(TrackBar1.Value) ' Affiche 0
    TrackBar1.Value = 10
    MsgBox(TrackBar1.Value) ' Affiche 10... Mais la TrackBar1 sur le contrôle reste à 0 !

    De même pour la CheckBox lorsque je fais changer l'état... sa valeur Checked change bien, mais la case reste décochée.

    J'ai essayé d'appeler la méthode Refresh, ainsi que testé en faisant un SuspendLayout avant modification et un ResumeLayout après... Mais rien n'y fait. De plus, InvokeRequired est bien à False sur les TrackBar et CheckBox...

    Quelqu'un aurait-il une idée quand à ce problème, qui semble bien dû à l'utilisation d'un thread créé dans un autre formulaire ?

    PS: Une précision importante, lorsque je place les procédures et le thread de mon second formulaire dans le premier formulaire, tout fonctionne sans problème. C'est au moment de déplacer le thread et les fonctions associées dans un nouveau formulaire différent de celui qui contient les trackbar que ça a commencé à poser problème...


    lundi 11 juin 2012 23:42

Réponses

  • Je n'ai jamais été un gran fan des instances par défaut et cela semble lié à cela. Si par exemple j'ouvre Form2 en faisant : Dim f as new Form2:f.Show():f.Owner=Me

    et que dans Form2.ChangeValue je fais Dim f as Form1=me.Owner et que j'utilise f au lieu de l'instance par défaut Form1, cela marche. Donc je soupçonne ici que l'appel de l'instance par défaut depuis un autre thread initialise en fait une autre instance de Form1 qui n'est donc pas celle visible à l'écran ou qq chose comme cela...

    Avec un BackgroundWorker, le principe serait plutôt d'appeler ReportProgress pour signaler l'avancement et le changement de contexte est fait à notre place.

    On pourrait avoir qq chose comme Background.ReportProgress(0,i) si par exemple je passe la valeur en tant que "UserState" plutôt que PercentProgress et ensuite on peut avoir directement dans ProgressChanged :

    Form1.TrackBar1.Value=CType(e.UserState,Integer). Là cela marche car on est DEJA repassé sur le bon thread sans avoir eu à le faire nous même.


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".


    mardi 12 juin 2012 18:20
    Modérateur

Toutes les réponses

  • Bonjour,

    On a désactivé qq chose ? Normalement on devrait avoir une exception lorsqu'on tente de modifier un contrôle depuis un thread d'arrière plan (on ne peut modifier un contrôle que depuis son thread de création). Il faudrait utiliser Invoke pour rebasculer vers le thread du contrôle pour cette mise à jour  : http://msdn.microsoft.com/fr-fr/library/ms171728.aspx Si on est bien sur un Thread d'arrière plan InvokeRequired devrait être vrai. A partir de où vérifier vous cette valeur ?

    Si je reprends du début, je suis surpris qu'un Timer pose problème à ce point. A quel intervalle veut on faire changer ces valeurs ? Egalement je ne sais pas quel est le contexte mais à première vue cela m'évoque plus une "ProgressBar" ? C'est pour informer de l'avancement d'un traitement ?


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".

    mardi 12 juin 2012 11:18
    Modérateur
  • Bonjour,

    En effet dans une première version de mon code, lorsque je découvrais les threads (en réalité j'utilise un BackgroundWorker, d'après cette doc : http://msdn.microsoft.com/fr-fr/library/ck8bc5c6(v=vs.100).aspx ), je me suis pris une exception. J'ai alors utilisé Invoke pour modifier les controles, ce qui fonctionnait bien.
    J'avais alors un seul formulaire principal, avec le backgroundworker sur ce formulaire.

    J'ai ensuite voulu découper mon code, j'ai créé un second formulaire dans lequel j'ai mis le backgroundworker et ses procédures. Le bw est donc lancé depuis ce nouveau formulaire, à partir de là j'ai testé selon 2 méthodes :
    - en utilisant Invoke
    - en utilisant directement une méthode du formulaire principal pour mettre à jour les valeurs

    Dans les deux cas le résultat est le même : la valeur est bien mise à jour (affichage dans une msgbox), mais ça ne se voit pas graphiquement. Je vérifie la valeur ou bien dans la fonction appelée par Invoke, ou bien dans la méthode du formulaire principal.

    Enfin, j'ai effectué un test ce matin en mettant le backgroundworker sur le formulaire principal, mais en laissant les procédures appelées dans le second formulaire : le résultat est le même. Et idem en mettant le backgroundworker sur le formulaire secondaire mais les méthodes sur le principal.
    Ça ne fonctionne que si le bw et les méthodes sont tous sur le formulaire principal.

    Pour répondre aux autres questions, les valeurs sont mises à jour toutes les 20ms, il ne s'agit pas d'une progression mais bien d'une valeur que je peux modifier soit manuellement, soit automatiquement et progressivement (il s'agit d'une application de contrôle de projecteurs DMX, je veux faire varier les valeurs des canaux selon un fader que je cherche à programmer).

    mardi 12 juin 2012 12:31
  • Sur le plan du principe, mettre à jour l'interface utilisateur toutes les 20 milliseconds (sauf erreur cela fait 50 fois/seconde donc à peine moins que la fréquence de rafraichissement d'une image) risque de coincer. Je vois assez souvent ce problème avec un BackgroundWorker : le développeur utilise un BackgroundWorker pour soulager le thread de l'interface utilisateur mais finit par mettre à jour l'interface utilisateur trop souvent depuis ce travail d'arrière plan ce qui finalement continue à maintenir le thread occupé. Donc je suggérerais déjà de voir si il ne serait pas possible de mettre à jour l'interface avec une fréquence moins grande (en se basant sur le temps, par exemple toutes les 1/2 ou les 1/4 de secondes). Cela explique aussi sans doute pourquoi le Timer générait ce problème. Pour moi, la fréquence est trop grande et l'interface utilisateur reste globalement très occupée (en gros le timer est appelé si souvent que cela revient en pratique à faire une boucle).

    Pour revenir au problème d'origine, je pensais à une "cross thread exception" qui aurait peut-être été désactivée ? Sinon je pense que le mieux sera que l'un ou l'autre fasse une maquette pour voir car il risque d'être difficile d'imaginer ce qui peut coincer dans le code sans le voir. Donc généralement on fait plutôt une maquette pour ne pas montrer plein de code sans rapport avec le problème et décourager les bonnes volontés.. Souvent cela permet aussi de trouver soi même le problème sur cette maquette simplifiée. On utlise toujours un background worker (pour faire éventuellement un essai de mon côté) ?


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".

    mardi 12 juin 2012 13:46
    Modérateur
  • L'idée est d'avoir la TrackBar le plus à jour possible, d'où les 20ms. Je n'avais pas de problème de lenteur quand ça fonctionnait (= quand tout était dans un seul formulaire).

    Je n'ai désactivé aucun exception, et de toute façon la valeur TrackBar1.Value est bien mise à jour ! C'est ce qui pose problème : la valeur est bien mise à jour, mais graphiquement ça ne se voit pas.

    Une maquette simplifiée : Form1 : TrackBar1, Button1; Form2 : Button1, Button2, BackgroundWorker1.

    Public Class Form1
        Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
            Form2.Show()
        End Sub
    End Class
    
    
    Public Class Form2
        Public Delegate Sub Param(i As Integer)
        Public ThreadFade As New Param(AddressOf ChangeValue)
    
        Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
            BackgroundWorker1.RunWorkerAsync(1)
        End Sub
    
        Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
            BackgroundWorker1.RunWorkerAsync(-1)
        End Sub
    
        Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
            For t As Integer = 1 To 50
                ChangeValue(e.Argument * 20)
                System.Threading.Thread.Sleep(20)
            Next
            BackgroundWorker1.CancelAsync()
        End Sub
    
        Private Sub ChangeValue(i As Integer)
            If Form1.TrackBar1.InvokeRequired Then
                Form1.TrackBar1.Invoke(ThreadFade, New Object() {i})
            Else
                MsgBox(Form1.TrackBar1.Value) 'Avant
                Form1.TrackBar1.Value += i
                MsgBox(Form1.TrackBar1.Value) 'Après
            End If
        End Sub
    
    End Class

    La première MsgBox "Avant" affiche bien la valeur avant, et la MsgBox "Après" affiche bien que la valeur a augmenté... Mais la TrackBar ne bouge pas.

    mardi 12 juin 2012 16:21
  • Je n'ai jamais été un gran fan des instances par défaut et cela semble lié à cela. Si par exemple j'ouvre Form2 en faisant : Dim f as new Form2:f.Show():f.Owner=Me

    et que dans Form2.ChangeValue je fais Dim f as Form1=me.Owner et que j'utilise f au lieu de l'instance par défaut Form1, cela marche. Donc je soupçonne ici que l'appel de l'instance par défaut depuis un autre thread initialise en fait une autre instance de Form1 qui n'est donc pas celle visible à l'écran ou qq chose comme cela...

    Avec un BackgroundWorker, le principe serait plutôt d'appeler ReportProgress pour signaler l'avancement et le changement de contexte est fait à notre place.

    On pourrait avoir qq chose comme Background.ReportProgress(0,i) si par exemple je passe la valeur en tant que "UserState" plutôt que PercentProgress et ensuite on peut avoir directement dans ProgressChanged :

    Form1.TrackBar1.Value=CType(e.UserState,Integer). Là cela marche car on est DEJA repassé sur le bon thread sans avoir eu à le faire nous même.


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".


    mardi 12 juin 2012 18:20
    Modérateur
  • En effet, en utilisant la propriété Owner, celà fonctionne.

    Reste à voir si je garde cette méthode ou si j'utilise ReportProgress (ce qui paraîtrai en effet un peu plus propre), je vais y réfléchir.

    Merci pour la réponse !

    mercredi 13 juin 2012 00:30
  • ReportProgress permettrait de supprimer entièrement la "plomberie" InvokeRequired/Invoke et d'utiliser à nouveau l'instance par défaut Form1. Egalement une démo serait de modifier les valeurs de la trackback pendant 10 s. Au lieu d'avoir un Sleep à chaque fois (ce qui rend la main à l'interface utilisateur mais rend artificiellement le traitement plus long que nécessaire, si on le supprime l'interface ne devrait plus répondre car on finit pas mettre à jour en permanence sur le thread interface utilisateur), mon approche est plutôt d'appeler ReportProgress à intervalle régulier (par exemple tous les 1/4 de s) ce qui permet de mettre à jour l'interface utilisateur de temps en temps tout en la gardant réactive et le traitement reste aussi cours que possible.

    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".

    mercredi 13 juin 2012 09:04
    Modérateur