IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

Treeview et Thread


Sujet :

C#

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    12
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Novembre 2012
    Messages : 12
    Par défaut Treeview et Thread
    Bonjour/soir,

    J'essaie dernièrement d’utiliser l’élément treeview de WPF et je rencontre un problème avec son utilisation.

    Je m'entraîne en récupérant l'arborescence de mon ordinateur depuis un dossier, et avec de la récursivité je récupère les sous-dossiers et les fichiers présents.
    Ça sa marche très bien, l'inconvénient est que ça prend quelques secondes à tout charger et donc j'ai mis mon code dans un thread.

    Le problème survient quand je veux repasser toute l'arborescence créée avec des TreeViewItem à mon treeView. A ce moment là j'obtient un InvalidOperationException "Le thread appelant ne peut pas accéder à cet objet parce qu'un autre thread en est propriétaire."

    Ce problème je l'ai déjà eu dans d'autres programmes et je l'ai résolu en utilisant un Dispatcher.BeginInvoke

    Mon code appelé depuis le thread pour mettre à jour le treeview se présente comme suit.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    public void MiseAJourNode(TreeViewItem node)
            {
                treeViewMain.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(
                    delegate()
                    {
                        treeViewMain.Items.Add(node);
                    }));
            }
    Le node reçu comporte tous les nodes et sous nodes de mon arborescence et j'essaie ensuite simplement de l'ajouter à mon treeview. Habituellement le bout de code au-dessus me permet de régler les problèmes de communications d'un thread à un autre. Mais là même avec, je n'arrive pas à éviter une exception.

    Une expérience à partager à propos des treeview et des threads?

    Je vous remercie d'avance et vous souhaite un bon week-end.

  2. #2
    Membre Expert


    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2006
    Messages
    970
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2006
    Messages : 970
    Par défaut
    Bonjour,

    Comme tu l'as surement déjà compris au vu du message d'erreur, ton thread ne peut pas accéder à un objet de ton interface vu que celle-ci fait partie de ton programme et donc d'un autre thread.

    Je te propose donc l'utilisation du BackgroundWorker qui rend plus simple la création et l'utilisation des threads. Voici ci dessous un exemple de code d'une application avec une progressbar qui évolue grâce à celui-ci

    Code Xaml :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    <Window x:Class="TestBackgroundWorker.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Test BackgroundWorker" Height="146" Width="525">
        <Grid>
            <ProgressBar Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="Progress" VerticalAlignment="Top" Width="480" />
            <Button Content="Début" Height="23" HorizontalAlignment="Left" Margin="12,46,0,0" Name="btnStart" VerticalAlignment="Top" Width="75" Click="btnStart_Click" />
            <Button Content="Fin" Height="23" HorizontalAlignment="Left" Margin="93,46,0,0" Name="btnEnd" VerticalAlignment="Top" Width="75" Click="btnEnd_Click" />
            <Label Content="0/0" Height="32" HorizontalAlignment="Right" Margin="0,46,11,0" Name="lblAvancement" VerticalAlignment="Top" Width="133" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" />
            <Label Content="Action en cours : " Height="28" HorizontalAlignment="Left" Margin="12,75,0,0" Name="label1" VerticalAlignment="Top" Width="101" />
            <Label Height="28" HorizontalAlignment="Left" Margin="119,75,0,0" Name="lblAction" VerticalAlignment="Top" Width="372" Content="En attente" />
        </Grid>
    </Window>
    Le code behind :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
     
    public partial class MainWindow : Window
        {
            private BackgroundWorker work;
            int nMaxValue = 500;
     
            /* MainWindow
             * 
             * Initialisation de la fenêtre
             */
            public MainWindow()
            {
                InitializeComponent();
     
                // Création du BackgroundWorker
                work = new BackgroundWorker();
     
                // Indique la fonction de traitement lors du déclenchement de l'événement DoWork
                work.DoWork += new DoWorkEventHandler(Traitement);
                // Indique la fonction de traitement lors du déclenchement de l'événement indiquant un changement de progression
                work.ProgressChanged += new ProgressChangedEventHandler(work_ProgressChanged);
                // Indique au BackgroundWorker qu'il peut etre interrompu 
                work.WorkerSupportsCancellation = true;
                // Indique au BackgroundWorker qu'il doit traiter l'événement de progression
                work.WorkerReportsProgress = true;
                // Indique la fonction exécutée à la fin du traitement 
                work.RunWorkerCompleted += new RunWorkerCompletedEventHandler(work_RunWorkerCompleted);
     
                // Initialisation de la progressbar
                Progress.Minimum = 0;
                Progress.Maximum = nMaxValue;
                Progress.Value = 0;
     
                btnEnd.IsEnabled = false; 
            }
     
            // Fonction appelée lors du changement de la progression du traitement 
            private void work_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                // Mise à jour de la progress bar
                Progress.Value = e.ProgressPercentage;
     
                // et du label
                lblAvancement.Content = e.ProgressPercentage.ToString() + " / " + Progress.Maximum;
     
                // et le label action
                lblAction.Content = e.UserState.ToString();
            }
     
            // Fonction de traitement 
            private void Traitement(object data, DoWorkEventArgs e)
            {
                // On vérifie si la fin du traitement a été demandée
                int i = 0;
                while (!work.CancellationPending && i < int.Parse(e.Argument.ToString()))
                {
                    System.Threading.Thread.Sleep(100);
     
                    i++;
     
                    // Indique au BackgroundWorker qu'il y a eu progression avec le pourcentage en paramètre
                    work.ReportProgress(i,"En cours de traitement");
                }
     
                // A la fin du traitement on peut renvoyer un résultat via la propriété result de l'objet DoWorkEventArgs
                e.Result = "Traitement terminé";
            }
     
            // Fonction exécutée lorsque l'événement de fin de traitement est déclenchée
            void work_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                // L'objet RunWorkerCompletedEventArgs peut renvoyer une propriété (Error) contenant une information en cas d'erreur
                if (e.Error != null)
                {
                    MessageBox.Show(e.Error.ToString());
                }
                else
                {
                    // Ou un résultat via la propriété Result
                    lblAction.Content = e.Result.ToString();
                }
                btnStart.IsEnabled = true;
                btnEnd.IsEnabled = false;
            }
     
            // Bouton Start, lance le BackgroundWorker
            private void btnStart_Click(object sender, RoutedEventArgs e)
            {
                // nMaxValue est un argument pour la fonction de traitement
                work.RunWorkerAsync(nMaxValue);
                btnStart.IsEnabled = false;
                btnEnd.IsEnabled = true;
            }
     
            // Bouton pour annuler le traitement
            private void btnEnd_Click(object sender, RoutedEventArgs e)
            {
                work.CancelAsync();
                btnStart.IsEnabled = true;
                btnEnd.IsEnabled = false;
            }
        }
    J'espère que le code est suffisamment compréhensif.
    Articles sur les technologies .NET

    Une réponse vous a aidé ? utilisez le bouton

    Votre problème est résolu ? utilisez le bouton

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    12
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Novembre 2012
    Messages : 12
    Par défaut
    Merci de la réponse rapide.

    J'ai essayé ta solution et j'obtient une exception InvalidOperationException "Le thread appelant doit être en mode STA, comme l'exigent de nombreux composants de l'interface utilisateur."

    Ça arrive quand j'essaie de créer un contrôle dans le code... D'après ce que j'ai lu ce type de thread n'est pas adapté pour travailler avec du GUI.

    Une idée?

  4. #4
    Membre Expert


    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2006
    Messages
    970
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2006
    Messages : 970
    Par défaut
    Dans quelle fonction crée tu ton contrôle?
    Articles sur les technologies .NET

    Une réponse vous a aidé ? utilisez le bouton

    Votre problème est résolu ? utilisez le bouton

  5. #5
    Membre Expert


    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2006
    Messages
    970
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2006
    Messages : 970
    Par défaut
    A mon avis , tu essaies de créer ton contrôle dans la fonction de traitement du thread. Dans le bout de code suivant je crée un simple label sur ma fenêtre (n'importe où ce n'est qu'un test ;-) ) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    // Fonction exécutée lorsque l'événement de fin de traitement est déclenchée
            void work_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                // L'objet RunWorkerCompletedEventArgs peut renvoyer une propriété (Error) contenant une information en cas d'erreur
                if (e.Error != null)
                {
                    MessageBox.Show(e.Error.ToString());
                }
                else
                {
                    // Ou un résultat via la propriété Result
                    lblAction.Content = e.Result.ToString();
                }
                btnStart.IsEnabled = true;
                btnEnd.IsEnabled = false;
     
                Label lbl = new Label();
                lbl.Content = "Je suis la ";
     
                grdPrinc.Children.Add(lbl);
            }
    Cette fonction étant celle appelée par l'événement "RunWorkerCompleted" du thread principal, elle fait partie de celui ci et donc pas de problème pour créer tes contrôles.

    Sinon j'ai trouvé cette discutions qui traite d'un problème similaire :

    http://www.developpez.net/forums/d96...stathread-wpf/

    J'espère que cela va t'aider
    Articles sur les technologies .NET

    Une réponse vous a aidé ? utilisez le bouton

    Votre problème est résolu ? utilisez le bouton

  6. #6
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    12
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Novembre 2012
    Messages : 12
    Par défaut
    Après avoir relu et encore relu le code que tu as proposé dans ta première réponse je crois surtout que je n'arrive pas à comprendre qu'est-ce qui fait quoi...

    entre
    work_ProgressChanged
    Traitement
    work_RunWorkerCompleted

    je ne vois pas du tout où je suis censé mettre mon code...

  7. #7
    Membre Expert Avatar de meziantou
    Homme Profil pro
    autre
    Inscrit en
    Avril 2010
    Messages
    1 223
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Autre

    Informations professionnelles :
    Activité : autre
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2010
    Messages : 1 223
    Par défaut
    Faire les changements de l'interface graphique dans ProgressChanged et RunWorkerCompleted
    Faire les calculs dans DoWork

    Documentation : http://msdn.microsoft.com/fr-fr/libr...v=vs.110).aspx

  8. #8
    Membre Expert


    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2006
    Messages
    970
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2006
    Messages : 970
    Par défaut
    Et dans le cas de mon exemple de code Dowork est la fonction "Traitement". Donc cette fonction ne peux avoir d’interaction directe avec l'interface. Comme le précise meziantou il faut utiliser ProgressChanged et RunWorkerCompleted

    Si tu regardes la fonction "ReportProgress" dans la fonction de traitement, le deuxième paramètre est de type objet, donc tu peux très bien prévoir une classe avec plusieurs informations à transmettre à ton interface via cette fonction qui déclenchera l'événement qui lui est associé.
    Articles sur les technologies .NET

    Une réponse vous a aidé ? utilisez le bouton

    Votre problème est résolu ? utilisez le bouton

  9. #9
    Membre émérite
    Homme Profil pro
    Développeur / architecte
    Inscrit en
    Juillet 2009
    Messages
    473
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Développeur / architecte

    Informations forums :
    Inscription : Juillet 2009
    Messages : 473
    Par défaut
    Je suis pas sépécialiste en WPF, mais en Silverlight on utilse le Dispatcher pour ce genre de choses.
    Apparemment ça existe aussi en WPF, et ça devrait règler ton problème. (voir l'article)
    Y'a même moyen de l'encapsuler, en SL ça s'écrirait comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    private void _SafeInvoke(Action action)
    {
        if (Dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            Dispatcher.BeginInvoke(action);
        }
    }

  10. #10
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    12
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Novembre 2012
    Messages : 12
    Par défaut
    Bonsoir,

    Je vous remercie pour vous réponses, désolé de ne pas vous avoir répondu plus tôt.

    Je n'ai pas réussi à faire fonctionner le background worker et mon code, il ne me semble pas que ce soit adapté pour ce que je veux faire justement.

    Sinon chrisdot utiliser un Dispatcher c'est ce que j'ai fait depuis le début le problème c'est que même dans un dispatcher mon code rencontre des problème d'accès illégal de thread...

    Je vais laisser tomber momentanément, si je trouve une solution assez rapidement je vous en ferai part.

    Encore merci pour vos conseils.

  11. #11
    Membre émérite
    Homme Profil pro
    Développeur / architecte
    Inscrit en
    Juillet 2009
    Messages
    473
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Développeur / architecte

    Informations forums :
    Inscription : Juillet 2009
    Messages : 473
    Par défaut
    Oups! OK, désolé j'avais zappé bout de code !

    Dans quel thread crées-tu le TreeViewItem à ajouter ? Dans ton thread de travail (pas celui de l'UI)?
    Il faut aussi le faire dans le thread de l'UI! (donc dans un Dispatcher.BeginInvoke(), sinon ça merde!)

  12. #12
    Membre Expert


    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2006
    Messages
    970
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2006
    Messages : 970
    Par défaut
    +1 avec chrisdot

    Je viens de tester et effectivement le BackGroundWorker à le même soucis. Le TreeViewItem est considéré comme un élément d'interface (ce qui est logique en y réfléchissant).

    Maintenant au lieu d'utiliser les threads dans ce cas si tu pourrais juste charger les dossiers à la volée. C'est à dire quand on déclenche l'événement Expand ou click de l'élément Parent.

    Ex : Tu cliques sur D: qui est "fermé" et à ce moment tu recherches les dossiers contenu sur D:
    Articles sur les technologies .NET

    Une réponse vous a aidé ? utilisez le bouton

    Votre problème est résolu ? utilisez le bouton

  13. #13
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2012
    Messages
    12
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Novembre 2012
    Messages : 12
    Par défaut
    Effectivement je n'avais pas pensé à juste chercher les sous-dossiers en ouvrant les noeuds... C'est peut être pour ça que treeview se comporte pareillement, pour nous forcer à faire ce genre de recherche.

    Je vais tester cela.

  14. #14
    Membre Expert


    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2006
    Messages
    970
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2006
    Messages : 970
    Par défaut
    Et petite astuce pour avoir le petit "+" donc pouvoir ouvrir le noeud, à la création d'un nouveau noeud, tu lui ajoutes un noeud "temporaire" avec un tag connu (ou le header peut importe).

    Dans l'événement click ou Expand, tu testes si le noeud à un enfant et si oui si c'est un noeud "temporaire". Si oui tu supprimes celui-ci et recherche tes sous-dossiers. Si non c'est que les sous-dossiers ont déjà étés chargés.
    Articles sur les technologies .NET

    Une réponse vous a aidé ? utilisez le bouton

    Votre problème est résolu ? utilisez le bouton

Discussions similaires

  1. Mise à jour TreeView à partir de différents thread
    Par 1234567890 dans le forum Windows Forms
    Réponses: 2
    Dernier message: 17/07/2009, 10h40
  2. Remplir une treeview dans un nouveau thread
    Par vaxxx dans le forum VB.NET
    Réponses: 1
    Dernier message: 18/12/2008, 15h44
  3. [RCP] Treeviewer non thread-safe ?
    Par Guildux dans le forum Eclipse Platform
    Réponses: 4
    Dernier message: 09/01/2007, 13h00
  4. [C#] Thread et treeviews
    Par bart64 dans le forum Windows Forms
    Réponses: 6
    Dernier message: 08/11/2006, 21h06
  5. [C#] Thread et Treeview
    Par Royd938 dans le forum Windows Forms
    Réponses: 13
    Dernier message: 02/12/2005, 10h40

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo