none
Comment notifier les update dans une hiérarchie d'ObservableCollection RRS feed

  • Discussion générale

  • Salut à tous,

    Comme j'ai l'impression que je passe mon temps à poser des questions sur ce forum, j'espère que je ne vais pas trop passer pour un débutant (bien que je le sois sur le plateforme .NET...).

    Bref, j'ai identifié un problème que je n'arrive pas à résoudre.

    Je vous explique : J'ai bindé mon UI sur une ObservableCollection "AllGroups". Celle ci contient une liste d'objets "MyGroup" qui eux-mêmes contiennent une ObservableCollection d'objet "Item" (cf le schéma joint au post)

    Parfois, je met à jour une des propriétés d'un Item (mettons le titre par exemple). Mais ce changement ne se voit pas à l'écran puisque je binde ma UI sur "AllGroups" et non sur la liste des Items directement.

    Du coup, je ne sais pas comment envoyer l'évènement "un élément a changé" ou "met toi à jour" à l'ObservableCollection "AllGroups".

    On pense direct à l'évènement "CollectionChanged" mais comme son nom l'indique, il réagit à la modification directe (Add, Remove) de la collection, or ici la collection ne bouge pas.

    Quelqu'un pourrait m'aider la dessus svp?

    Merci d'avance à tous pour votre patience ;)


    Christophe H.


    • Modifié Christophe Hvd mardi 26 juin 2012 13:23 précisions
    • Type modifié Florin Ciuca mardi 3 juillet 2012 09:00 attente de feedback
    • Déplacé Aurel Bera jeudi 20 septembre 2012 13:21 (Origine :Développement Windows 8 )
    mardi 26 juin 2012 13:21

Toutes les réponses

  • Hello,

    Tes classes  (MyGroup et Item) implémentent bien INotifyPropertyChanged (soit à l'ancienne soit via BindableBase et SetProperty), car tel quel le scenario que tu décris devrais fonctionner?

    Bon courage,


    Cyprien Autexier



    mardi 26 juin 2012 13:29
  • Dans la mesure où effectivement elles héritent toute deux de BindableBase, oui elles implémentent par défaut INotifyPropertyChanged.

    Par exemple, j'ai rajouté à la fin de mon constructeur d'Item ceci : 

    this.PropertyChanged += onPropertyChanged;
    

    Puis j'ai défini le handler comme ceci : 

    private void onPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                Debug.WriteLine("Une propriété d'Item a ete modifié");
            }

    Or lorsque je modifie une propriété comme le Title de mon Item, je n'ai aucun message en console...

    J'ai oublié une étape non?


    Christophe H.

    mardi 26 juin 2012 13:45
  • Hello,

    Comment as-tu implémenté ta propriété title? Attention, BindableBase ne faisant pas de miracle,  les auto-properties ne sont pas viables, et le snippet de base pour les propriété complète n'est pas correct pour cet usage. Elles doivent être implémentées comme suit :

    private string title;
    
    public string Title
    {
      get {return this.title;}
      set
      {
        this.SetProperty(ref this.title, value);
      }
    }

    Cyprien Autexier


    mercredi 27 juin 2012 15:59
  • Ca c'est balo parce que effectivement, ma propriété Title est implémenté comme suit : 

    public string Title
            {
                get { return this._title; }
                set { this.SetProperty(ref this._title, value); }
            }

    J'en conclu que ça devrait marcher non? Je devrais bien avoir une notif non?

    On récapitule : mon constructeur d'Item initalise certaines variables, fait des trucs, puis s'abonne à l'évènement "PropertyChanged" via le code de mon premier post.

    Puis, lorsque je modifie le Title par exemple, le code ci-dessus (le SetProperty) va balancer un évènement de type "PropertyChanged", capté par l'Item qui lui va exécuter la méthode "onPropertyChanged".

    Mais ça ça ne fonctionne pas.

    J'ai encore raté un truc?


    Christophe H.

    jeudi 28 juin 2012 07:08
  • Et bien a priori si tu as bien toutes tes propriétés implémentées comme ci-dessus et que tu passes bien toujours par ton setter pour test updates, tu ne devrais pas rencontrer de problèmes. Peut-être qu'en voyant ton code on aurait plus d'idées.


    Cyprien Autexier

    jeudi 28 juin 2012 17:26
  • La mise à jour de la donnée ne se fait-elle pas dans un autre thread que le thread de l'UI ?

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

    lundi 2 juillet 2012 07:31
  • @Richard Clark : dans la mesure où l'UI est bindé sur une ObservableColleciton (ici "AllGroups", cf mon schéma), même si la MAJ se fait dans un thread différent, l'évènement déclenché par la modification d'une propriété devrait être remonté jusqu'à l'UI. 

    Donc le problème ne vient pas de là.

    @sandør : Lorsque je fais des modif, des fois je modifie une propriété existante, des fois je rajoute carrément un élément à l'ObservableCollection d'Item. Le "Add" étant un mécanisme de base, il devrait effectivement générer les évènements qu'il faut.

    Je vais refaire quelques tests et je vous poserai peut-être un peu plus de code.. :p

    Merci pour votre aide en tout cas


    Christophe H.

    mardi 3 juillet 2012 13:05
  • Bonjour à tous,

    Ma question du départ n'ayant toujours pas été résolue, voici un petit bout de code qui vous permettra de réfléchir un peu plus facilement ^^

    Tout d'abord, je construit une source de données hyper simple en me calquant sur le schéma du début : 

    namespace test_hierarchie
    {
        class DataSource : BindableBase
        {
            private ObservableCollection<MyGroup> _groups = new ObservableCollection<MyGroup>();
            public ObservableCollection<MyGroup> Groups
            { 
                get { return this._groups; }
            }
    
            public DataSource()
            {
                // creation de plusieurs Item
                Item item1 = new Item("item1");
                Item item2 = new Item("item2");
                Item item3 = new Item("item3");
                Item item4 = new Item("item4");
                Item item5 = new Item("item5");
                Item item6 = new Item("item6");
    
                // creation de plusieurs MyGroup
                MyGroup group1 = new MyGroup("group1");
                group1.Items.Add(item1);
                group1.Items.Add(item2);
                group1.Items.Add(item3);
    
                MyGroup group2 = new MyGroup("group2");
                group2.Items.Add(item4);
                group2.Items.Add(item5);
    
                MyGroup group3 = new MyGroup("group3");
                group2.Items.Add(item6);
    
                // puis on ajoute les groupes à la liste principale
                Groups.Add(group1);
                Groups.Add(group2);
                Groups.Add(group3);
    
                Groups.CollectionChanged += onGroupsChanged;
            }
    
            public void onGroupsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                Debug.WriteLine("Groups has been modified");
            }
        }
    
        class MyGroup : BindableBase
        {
            public ObservableCollection<Item> _items = new ObservableCollection<Item>();
            public ObservableCollection<Item> Items
            {
                get { return this._items; }
            }
    
            public String GroupTitle { get; set; }
    
            public MyGroup(String title)
            {
                this.GroupTitle = title;
    
                Items.CollectionChanged += onItemsChanged;
            }
    
            public void onItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                Debug.WriteLine("Items has been modified");
            }
        }
    
        class Item : BindableBase
        {
            public String Title { get; set; }
    
            public Item(String title)
            {
                this.Title = title;
            }
        }
    }


    Comme vous pouvez le voir, au changement de chacune de mes listes, il y aura un affichage en console.

    Pour tester ceci, j'ai créer une UI avec juste un bouton qui va ajouter un élément dans les "Items" du premier groupe.

     protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                DS = new DataSource();  
            }
    
            private void AddItem(object sender, RoutedEventArgs e)
            {
                Item item = new Item("item" + Guid.NewGuid());
                this.DS.Groups[0].Items.Add(item);
            }

    Forcément, lorsque j'ajoute cet item, la liste "Groups" n'est pas modifié donc elle ne notifie personne d'un changement. Logique.

    Mais la question est donc : comment faire pour que l'évènement "ajout d'un item" soit diffusé un cran plus haut dans la hiérarchie? 

    La finalité : Si je binde mon UI sur la liste de tous les groupes (ici "Groups"), comment faire pour que celle-ci se mette à jour toute seule si je rajoute un item dans un groupe?

    La réponse est peut-être simple mais je n'y est juste pas encore pensé... :p

    Merci d'avance à tous!


    Christophe H.

    jeudi 5 juillet 2012 14:43
  • Il manque le XAML, car dans ton exemple on a du mal à comprendre pourquoi un ajout d'un item provoquerait un rafraichissement au plus haut niveau...
    lundi 9 juillet 2012 17:28
  • Il est tard, je  ne vais pas trop rentrer dans les détails et j'ai pas trop réfléchit mais il y a peut-être une solution. 

    En changeant le code comme ça :

     class DataSource : BindableBase
        {
            private ObservableCollection<MyGroup> _groups = new ObservableCollection<MyGroup>();
            public ObservableCollection<MyGroup> Groups
            {
                get { return this._groups; }
            }
    
            public DataSource()
            {
                // creation de plusieurs Item
                Item item1 = new Item("item1");
                Item item2 = new Item("item2");
                Item item3 = new Item("item3");
                Item item4 = new Item("item4");
                Item item5 = new Item("item5");
                Item item6 = new Item("item6");
    
                // creation de plusieurs MyGroup
                MyGroup group1 = new MyGroup("group1");
                group1.Items.Add(item1);
                group1.Items.Add(item2);
                group1.Items.Add(item3);
    
                MyGroup group2 = new MyGroup("group2");
                group2.Items.Add(item4);
                group2.Items.Add(item5);
    
                MyGroup group3 = new MyGroup("group3");
                group2.Items.Add(item6);
    
                Groups.CollectionChanged += onGroupsChanged;
    
                // puis on ajoute les groupes à la liste principale
                Groups.Add(group1);
                Groups.Add(group2);
                Groups.Add(group3);
    
                
            }
    
            public void onGroupsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    foreach (MyGroup newItem in e.NewItems)
                    {
                        newItem.PropertyChanged += newItem_PropertyChanged;
                    }
                }
                
            }
    
            void newItem_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                Debug.WriteLine("Groups has been modified");
            }
        }

    L'idée est que, lorsqu'un sous-groupe est modifié, il appelle OnPropertyChange. Le groupe principal souscrit a l’évènement OnPropertyChange des sous-groupes lorsque ceux-ci sont ajoutés à la collection. Si tu essayes, tu vas voir apparaître "Groups has been modified".

    Evidemment ici c'est une base, l'idée serait de créer une classe Groups qui dérive de ObservableCollection<MyGroup> qui prendrait tout ça en charge.

     
    lundi 9 juillet 2012 19:39
  • Attention avec cette solution :)

    Il faut aussi gérer le NotifyCollectionChangedAction.Remove qui est assez facile (désabonnement) mais également le NotifyCollectionChangedAction.Clear et là c'est mission impossible car lors d'un Clear on a aucun moyen d'accéder à ce qui a été "clearé".

    lundi 9 juillet 2012 19:42
  • Pour être notifié des changements dans un graphe d'objets, Paul Stovell a réalisé une petite librairie qui fait ça comme un charme:

    http://www.paulstovell.com/observal

    Je l'utilise actuellement dans plusieurs projets, de plus elle a l’avantage d'être complètement extensible et offre aussi la possibilité d'utiliser des WeakEvents.

    Du coup pour ton modèle, en combinant les 4 extentions on aurait:

        var observer = new Observer();
        observer.Extend(new TraverseExtension())
                              .Follow<MyGroup>(e => e.Items)
                              .Follow<Item>(e => e.SubItems); //j'ai ajouté un niveau pour l'exemple
        observer.Extend(new CollectionExpansionExtension());
        observer.Extend(new PropertyChangedExtension())
                              .WhenPropertyChanges<Item>(x => Debug.WriteLine("Item has been modified"));
        observer.Extend(new ItemsChangedExtension())
                              .WhenAddedOrRemoved<Item>(item => Debug.WriteLine("Items has been modified"));
        observer.Add(this.Groups);

    Je l'utilise beaucoup dans les ViewModels où j'ai des propriétés calculées issues de plusieurs "sous-objets" dans mon graphe et ce à plusieurs niveaux.

    lundi 9 juillet 2012 22:33
  • Oui j'ai pas mis juste le add parce que j'avais la flemme ! Mais c'est vrai que si cela va sans dire, ça ira encore mieux en l'écrivant.

    Pour le Clear c'est pas trop un problème vu que le Clear appelle ClearItem qui est une méthode virtuelle, on pourrait imaginer un override qui lancerait un l'event remove. Celui-ci est intercepté par la ObservableCollection parente et s'occupe de désabonner.

    Bon, c'est juste un délire nocturne, a réfléchir plus posément si c'est une solution :p !

    lundi 9 juillet 2012 22:53
  • @Bertrand Jurado : Ta solution me semble bien sympathique mais je t'avoue que je galère toujours à porter Observal sur W8. 

    J'ai créer un nouveau projet "Portable Class Library", importé toutes les sources d'Observal, compilé le tout sans problème en ayant changé le framework cible (j'ai mis .NET 4.5). Le problème c'est que lorsque dans mon projet à moi, je veux rajouter une référence vers la dll d'Observal, j'ai l'erreur jointe en PJ.

    Etant un peu débutant sur la plateforme .NET, j'avoue ne pas bien comprendre.

    Tu peux m'éclairer?

    J'ai pas encore chercher sur le net, je préfère d'abord poser la question ici, je perdrai surement moins de temps :)

    Merci d'avance!


    Christophe H.

    dimanche 15 juillet 2012 11:33