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

Windows Presentation Foundation Discussion :

Binding & Threading


Sujet :

Windows Presentation Foundation

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Juin 2007
    Messages
    33
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 33
    Par défaut Binding & Threading
    Bonjour,

    Mon problème est le suivant:

    J'ai un processus long qui s'execute et j'aimerai, pendant le déroulement de ce processus, remonter des informations sur l'activité en cours.

    j'ai donc Bindé une collection d'objet TaskItem à une ListBox. Une tache ( Action globale) contient une liste de sous-tâches (information sur ce qui est executé par le processus long).

    Afin de laissé la Thread UI tranquille, mon processus long s'execute bien evidement sur une autre Thread. Du coup, lorsque je met à jour la collection de sous-taches, une exception est générée car la modification est faite par une autre thread.

    Je sais que je dois utiliser un dispatcher, mais je ne sais pas comment l'exploiter au travers du binding, les exemples que j'ai trouvé mettant toujours en oeuvre des propriétés directe d'un control. (TextBox.text).

    Code c# : 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
     
    public class TaskItem : INotifyPropertyChanged 
    {
            private String mActionTask;
            private SubTaskItems mSubItems = new SubTaskItems();
            private eTaskState mState = eTaskState.Unknown;
     
            public enum eTaskState
            {
                Unknown,
                Executing,
                Completeted,
                Failed
            }
     
            public eTaskState State
            {
                get { return mState; }
                set 
                { 
                    mState = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("State"));
                }
            }
     
            public TaskItem(String actionTask)
            {
                mActionTask = actionTask;
            }
     
            public void addSubTask(eVerboseLevel level, String msg)
            {
                SubTask.Add(new SubTaskItem(msg));
            }
     
            public String ActionTask
            {
                get { return mActionTask; }
            }
     
            public SubTaskItems SubTask
            {
                get { return mSubItems; }
            }
     
            public override string ToString()
            {
                return mActionTask;
            }
     
            #region INotifyPropertyChanged Members
     
            public event PropertyChangedEventHandler PropertyChanged;
     
            #endregion
        }

    Ci-dessous, la reprsésentation de mes ListBoxItem.

    Code xml : 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
     
    <DataTemplate x:Key="ActionTaskItem">
     
                <Grid >
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
     
                    <Image Name="mCurrentStateImage" Source="{Binding State, Converter={StaticResource taskItemStateConverter} }" Margin="5"  Grid.Column="0" Grid.RowSpan="2"  Width="24" Height="24" Stretch="Uniform" VerticalAlignment="Top" />
                    <TextBlock Text="{Binding ActionTask}" Margin="5" Grid.Row="0" Grid.Column="1" Style="{StaticResource ListBoxItemActionTitle}"/>
     
                    <Expander Header="{Binding SubTask.LastSubTaskItem}" Style="{StaticResource ListBoxItemActionDetail}"  IsExpanded="false" Margin="25,0,5,0" Grid.Row="1" Grid.Column="1" >
                        <ListBox ItemsSource="{Binding SubTask}" Margin="25,0,25,0" Background="Transparent"/>
                    </Expander>
                </Grid>
     
            </DataTemplate>

    Merci de m'eclairer,
    Cordialement,

    Ahryman40k.

  2. #2
    Invité
    Invité(e)
    Par défaut
    Je vois bien où se situe ton problème, pour pallier tu peux utiliser un BackgroundWorker au lieu de la classe Thread.
    En utilisant un BackgroundWorker, tu pourras utiliser soit l'évènement RunWorkerCompleted ou ProgressChanged pour mettre à jour ta collection sans passer par un dispatcher.

  3. #3
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Paris (Île de France)

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

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Par défaut
    En fait, la mise à jour d'un binding à partir d'un autre thread est supportée pour les propriétés (INotifyPropertyChanged), mais pas pour les modifications de collections (INotifyCollectionChanged).

    Il faut donc faire les ajouts à la collection sur le thread de l'UI, en utilisant Dispatcher.Invoke, ou alors, comme suggéré par h2s84, dans l'évènement ProgressChanged d'un BackgroundWorker (qui est déclenché sur le thread de l'UI)

    Sinon, j'avais écrit un truc à ce sujet sur mon blog, et j'avais proposé comme solution une classe qui hérite de ObservableCollection<T> et déclenche l'évènement CollectionChanged sur le thread de l'UI. Comme ça tu as juste à remplacer ton ObservableCollection par cette classe, et le reste du code ne devrait pas changer

  4. #4
    Membre averti
    Profil pro
    Inscrit en
    Juin 2007
    Messages
    33
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 33
    Par défaut
    Citation Envoyé par tomlev Voir le message
    En fait, la mise à jour d'un binding à partir d'un autre thread est supportée pour les propriétés (INotifyPropertyChanged), mais pas pour les modifications de collections (INotifyCollectionChanged).
    Il faut donc faire les ajouts à la collection sur le thread de l'UI, en utilisant Dispatcher.Invoke, ou alors, comme suggéré par h2s84, dans l'évènement ProgressChanged d'un BackgroundWorker (qui est déclenché sur le thread de l'UI)
    Ca me parait effectivement le plus simple et le plus rapide !

    Citation Envoyé par tomlev Voir le message
    Sinon, j'avais écrit un truc à ce sujet sur mon blog, et j'avais proposé comme solution une classe qui hérite de ObservableCollection<T> et déclenche l'évènement CollectionChanged sur le thread de l'UI. Comme ça tu as juste à remplacer ton ObservableCollection par cette classe, et le reste du code ne devrait pas changer
    Je vais analyser également cette solution.
    Je signale dès que mon problème est résolu.
    Merci pour toute vos réponses

  5. #5
    Membre averti
    Profil pro
    Inscrit en
    Juin 2007
    Messages
    33
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 33
    Par défaut
    Citation Envoyé par tomlev Voir le message
    Sinon, j'avais écrit un truc à ce sujet sur mon blog, et j'avais proposé comme solution une classe qui hérite de ObservableCollection<T> et déclenche l'évènement CollectionChanged sur le thread de l'UI. Comme ça tu as juste à remplacer ton ObservableCollection par cette classe, et le reste du code ne devrait pas changer
    J'ai une question a propos de ta classe AsyncObservableCollection, il est spécifié que la collection doit etre créé sur la Thread UI, que se passe t'il dans le cas contraire ? Peut-on detecté qu'on est sur une thread UI ?

  6. #6
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par Ahryman40k Voir le message
    J'ai une question a propos de ta classe AsyncObservableCollection, il est spécifié que la collection doit etre créé sur la Thread UI, que se passe t'il dans le cas contraire ? Peut-on detecté qu'on est sur une thread UI ?
    Bon, je vais répondre à la place de tomlev.

    Je pense qu'il n'y a aucun interêt que la classe soit définie dans un autre thread différent du thread UI :
    • d'une part parce que ta classe ne sera accessible que par là où elle est définie et du coup impossible au Thread UI d'y accéder.
    • d'autre part, c'est une collection qui peut être modifiée par plusieurs Thread lancés par le Thread UI donc autant définir la collection dans ce dernier.

  7. #7
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Paris (Île de France)

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

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Par défaut
    Citation Envoyé par Ahryman40k Voir le message
    L'idee du ProgressChanged n'est pas bête, je me demande juste comment je vais transmettre ce qu'il se passe ailleurs a cette méthode.
    Je crois que c'est possible à travers son evenement ? => j'etudie la solution !!
    Il y a une surcharge de ReportProgress qui permet de transmettre un objet en plus du pourcentage. Tu peux ensuite le récupérer dans le handler de l'évènement avec e.UserState :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ...
    backgroundWorker1.ReportProgress(percentage, newSubTask);
    ...
     
    void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        SubTask newSubTask = (SubTask)e.UserState;
        collection.Add(newSubTasks);
    }
    Citation Envoyé par Ahryman40k Voir le message
    J'ai une question a propos de ta classe AsyncObservableCollection, il est spécifié que la collection doit etre créé sur la Thread UI, que se passe t'il dans le cas contraire ? Peut-on detecté qu'on est sur une thread UI ?
    Les évènements seront déclenchés sur le thread où la collection a été crée. Vu que tu veux les déclencher sur le thread de l'UI, la collection doit être créée sur ce thread... ensuite tu peux la manipuler depuis n'importe quel thread.

    Pour détecter si tu es sur le thread de l'UI, tu peux faire ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    if (Application.Current.Dispatcher.CheckAccess())
    {
        ...
    }
    Citation Envoyé par h2s84 Voir le message
    d'une part parce que ta classe ne sera accessible que par là où elle est définie et du coup impossible au Thread UI d'y accéder.
    Non, n'importe quel thread peut y accéder (ce n'est pas un DispatcherObject). C'est juste que les évènements ne seront pas déclenchés sur le thread de l'UI, et donc ça fera la même erreur

  8. #8
    Membre averti
    Profil pro
    Inscrit en
    Juin 2007
    Messages
    33
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 33
    Par défaut
    En fait, je n'ai pas vraiment avancé car la solution avancée par tomlev n'est pas envisageable. Mon modele objet est comme suis:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    class SubTaskItem;
     
    class SubTaskItems : IObservableCollection<SubTaskItem>;
     
    class TaskItem
    {
            private SubTaskItems mSubItems = new SubTaskItems();
    };
     
    class TaskItems : IObservableCollection<TaskItem>;
    Ci-dessous, le code de ma page.
    j'ai retiré tout ce qui n'est pas nécéssaire à la comprehension du problème.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    public partial class ImportationStep : Page
    {
            private TaskItems mItems = new TaskItems();
            //-----------------------------------------------------------------
            public TaskItems Tasks
            {
                get { return mItems; }
            }
            //-----------------------------------------------------------------
    }
    La propriété Tasks definie ci-dessus est bindé dans une Listbox de ma page, et pour chaque ListBoxItem je vais bindé la propriété SubTasks de mon objet TaskItem.

    L'erreur n'est pas provoquée lorsque j'ajoute une tache, mais lorsque j'ajoute une sous-tache.

    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
     
    <Page>
        <Page.Resources>
            <DataTemplate x:Key="ActionTaskItem">
                <Grid >
                    <Image Name="mCurrentStateImage" Source="{Binding State, Converter={StaticResource taskItemStateConverter} }" />
                    <TextBlock Text="{Binding ActionTask}" />
     
                    <Expander Header="{Binding SubTask.LastSubTaskItem}" >
                        <ListBox ItemsSource="{Binding SubTasks}" />
                    </Expander>
                </Grid>
     
            </DataTemplate>
     
        </Page.Resources>
        <Grid >
            <ListBox Name="mImportActionList" 
                     ItemsSource="{ Binding ElementName=ImportationStepPage, Path=Tasks, UpdateSourceTrigger=PropertyChanged  }"
                     ItemTemplate="{StaticResource ActionTaskItem}"  IsSynchronizedWithCurrentItem="True"
                     >
             </ListBox>
        </Grid>
    </Page>
    Je suis dans un wizard d'importation dans lequel j'ai choisi d'utiliser des pages.
    Lorsque je navigue sur cette page, j'initialise un Timer qui va lancer une procedure d'importation.

    Durant l'importation, je vais executer plusieurs objets en tache de fond qui vont executer une etape de cette procedure d'importation. Je souhaite afficher au fur et à mesure ces etapes ainsi que des informations complementaires sur ce quelles font (les fameuses sous taches).

    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
     
    private void TimerProc(object sender, EventArgs e)
    {
         mTimerProc.Stop();
     
         TaskItem excelDataExtractionTask = new TaskItem("Excel data checking");
          excelDataExtractionTask.State = TaskItem.eTaskState.Unknown;
     
          mItems.Add(excelDataExtractionTask);
     
          BackgroundWorker  mWorker = new BackgroundWorker();
          mWorker.DoWork += new DoWorkEventHandler(DoExcelCheckCmd);
          mWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(DoExcelCheckCmdCompleted);
     
          excelDataExtractionTask.State = TaskItem.eTaskState.Executing;
          mWorker.RunWorkerAsync( new DoExcelCmdArg( excelDataExtractionTask ) );
    }
     
    private void DoExcelCheckCmd(object sender, DoWorkEventArgs e)
    {
          const String xlFile = @"G:\RJA Sample.xlsx";
     
          DoExcelCmdArg arg = e.Argument as DoExcelCmdArg;
          try
          {
              ExcelCheckCommand xlsCheckCmd = new ExcelCheckCommand(xlFile);
              xlsCheckCmd.Verbose += new VerboseHandler(arg.TaskItem.addSubTask);
     
              xlsCheckCmd.execute(mXlsControlFile);
           }
           catch (System.Exception ex)
           {
               arg.TaskItem.State = TaskItem.eTaskState.Failed;
           }
      }
    Mon erreur de thread se produit lorsque j'effectue ce lien :

    xlsCheckCmd.Verbose += new VerboseHandler(arg.TaskItem.addSubTask);
    effectivement l'ajout d'une sous-tache se fait hors d'une thread UI.

    Le temps de rediger tout ca, et ma reflexion avance, je me rend compte qu'au lieu de rendre bavarde ma commande tel que je l'ai faite, je pourrait utiliser le ReportProgress pour aboutir au même resultat et faire l'ajout de la sous-tache au même moment ...

    Pensez-vous que c'est convenable comme solution ?

    Le temps de vos reponse, je vais m'atteler a ces modifications et tester mon idée ...

    Merci pour vos pistes.

  9. #9
    Membre averti
    Profil pro
    Inscrit en
    Juin 2007
    Messages
    33
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 33
    Par défaut
    Citation Envoyé par h2s84 Voir le message
    Je vois bien où se situe ton problème, pour pallier tu peux utiliser un BackgroundWorker au lieu de la classe Thread.
    En utilisant un BackgroundWorker, tu pourras utiliser soit l'évènement RunWorkerCompleted ou ProgressChanged pour mettre à jour ta collection sans passer par un dispatcher.
    En fait, j'utilise déjà un BackgroundWorker.
    Attendre l'evenement RunWorkerCompleted ne m'avancerait pas plus car cela reviend à attendre la fin de la thread. L'idee du ProgressChanged n'est pas bête, je me demande juste comment je vais transmettre ce qu'il se passe ailleurs a cette méthode.
    Je crois que c'est possible à travers son evenement ? => j'etudie la solution !!

Discussions similaires

  1. Accélerer le binding avec le thread UI
    Par koyot3 dans le forum Windows Presentation Foundation
    Réponses: 4
    Dernier message: 04/11/2011, 16h31
  2. Bind, thread et membre template
    Par olivier1978 dans le forum Threads & Processus
    Réponses: 8
    Dernier message: 14/11/2007, 19h19
  3. Tri multi-threadé
    Par Tifauv' dans le forum C
    Réponses: 8
    Dernier message: 28/06/2007, 09h00
  4. [Kylix] Pb de Thread !!
    Par Anonymous dans le forum EDI
    Réponses: 1
    Dernier message: 25/04/2002, 13h53

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