Newsgroup: it.comp.dotnet
Titolo: [WPF] "Refresh" del binding a una collection

Ciao a tutti, vorrei porvi una domanda su un problema che credo sia non 
troppo atipico, ma ho difficoltï¿œ nel cercare di sintetizzarne il 
contesto... ci provo:
sto usando uno user control che contiene una treeview in cui 
l'ItemsSource ï¿œ in binding con una ObservableCollection chiamata 
TreeViewItemsVMCollection.
Dato che ho la necessitï¿œ di mostrare le linee di connessione dei nodi 
nell'albero, uso uno style per i treeviewitems che va a impostare 
"inline" il template per i suddetti treeviewitems, aggiungendo in testa 
ad ogni nodo una linea verticale ed una orizzontale di lunghezze diverse 
a seconda se sto in un nodo terminale o no, e questa discriminazione 
viene fatta da un datatrigger basato su binding con converter, un po' cosï¿œ:
[...]
<!-- Connecting Lines -->
<Rectangle x:Name="HorLn" Height="1" Stroke="#8888"  Margin="10,0,0,0" 
SnapsToDevicePixels="true"/>
<Rectangle x:Name="VerLn" Width="1" Stroke="#8888" Grid.RowSpan="2" 
SnapsToDevicePixels="true"/>
[...]
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, 
Converter={StaticResource LineConverter}}" Value="true">
	<Setter TargetName="VerLn" Property="Height" Value="{Binding 
ElementName=Bd, Path=ActualHeight, Converter={StaticResource 
LineHeightConverter}}"/>
	<Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
</DataTrigger>
Qual ï¿œ il problema?
Il problema ï¿œ che quando faccio un Add alla collection bindata 
all'itemssource, il binding con relativo converter del datatrigger viene 
richiamato solo per l'item effettivamente aggiunto, non per tutta la 
collection, dunque il meccanismo di disegno delle linee va a farsi 
benedire, visto che quello che prima dell'aggiunta era un nodo foglia 
non viene ridisegnato come nodo intermedio quando viene "appeso" il 
nuovo nodo foglia.
C'ï¿œ un modo per forzare un "re-bind" della ObservableCollection della 
treeview?
Io ho provato, con chiamate che risalgono l'albero fino alla radice, a 
chiamare una OnPropertyChanged("TreeViewItemsVMCollection") dopo aver 
verificato che il datacontext fosse quello giusto, ma la notifica viene 
bellamente ignorata, dunque sono costretto a ricreare l'albero da zero 
per costringere il sistema a rivalutare tutti i nodi, con problemi di 
altro tipo con cui non vi tedio ulteriormente.
Mi scuso per la poca chiarezza con cui mi sono espresso e confido nella 
vostra grande capacitï¿œ di comprensione ;)
Ciao e grazie

 </DataTrigger>
ehm...
ho dimenticato di dire che il converter mi dice se sto su nodo o foglia 
andando a controllare l'indice dell'item considerato, funziona, quando 
costruisco l'albero la prima volta ï¿œ tutto corretto.


La risposta piï¿œ facile dovrebbe essere quella di sollevare l'evento di 
INotifyCollectionChanged (implementata dalla observablecollection) 
indicando nell'eventargs che la action ï¿œ di tipo 'reset'.
Personalmente non mi piace come soluzione perchï¿œ in pratica stai 
dicendo di rifare tutto da zero.
Se usi il hierarchical data template, in teoria dovresti giï¿œ essere a 
posto con le notifiche al parent, perchï¿œ ogni nodo deve sapere se ha 
figli o meno e si ridisegna automaticamente.
Se non riesci su questo fronte, prova a creare un meccanismo per cui 
l'elemento parent sollevi il propertychanged nei confronti del tree, 
ridisegnando gli elementi in questione.
-- 
Raffaele Rialdi  
http://www.iamraf.net
Weblog: 
http://blogs.ugidotnet.org/raffaele
Microsoft MVP profile 
https://mvp.support.microsoft.com/profile/raffaele
UGIdotNET - 
http://www.ugidotnet.org/

 ridisegnando gli elementi in questione.
praticamente visitando l'albero "visuale" e lanciando propertychanged 
per la collection children di ogni livello?
Intanto grazie mille!


non intendo l'albero visuale ma proprio le entitï¿œ che bindi.
Nel mio caso io ho un treeview bindato ad una struttura di viewmodel 
organizzati in modo gerarchico (ricalcano ovviamente la visualizzazione 
del tree). Per semplificarmi la navigazione, ogni VM ha il riferimento 
al suo parent, quindi sono sempre in grado di "dire" al VM parent di 
cambiare il valore di una proprietï¿œ bindata la quale ovviamente 
scatenerï¿œ il proprio INotifyPropertyChanged che farï¿œ riflettere i 
cambiamenti sul tree.


 cambiamenti sul tree.
Grazie ai tuoi suggerimenti e a tentativi effettuati su un progetto di 
prova, sono venuto a capo del problema, o meglio, ho trovato una 
soluzione semplice da implementare, in cui fondamentalmente forzo un 
refresh all'atto dell'aggiunta/rimozione di figli ai nodi dell'albero in 
questo modo:
data la property Children bindata allo HierarchicalDataTemplate del 
treeview, così fatta:
         private ObservableCollection<SonViewModel> _children;
         /// <summary>
         /// Gets the Children property.
         /// TODO Update documentation:
         /// Changes to that property's value raise the PropertyChanged 
event.
         /// This property's value is broadcasted by the Messenger's 
default instance when it changes.
         /// </summary>
         public ObservableCollection<SonViewModel> Children
         {
             get
             {
                 return _children;
             }
             set
             {
                 if (_children == value)
                 {
                     return;
                 }
                 var oldValue = _children;
                 _children = value;
                 // Update bindings, no broadcast
                 RaisePropertyChanged(ChildrenPropertyName);
             }
         }
quando aggiungo/rimuovo un figlio (tramite menu contestuale e 
RelayCommand) chiamo:
         private void addNewSon()
         {
             _children.Add(new SonViewModel(new Son() { Name = "NewSon" 
}, this));
             //questo che segue serve per forzare una nuova chiamata dei 
binding del template,
             //non ho trovato altro modo (se non quello di ricostruire 
da capo il tree)
             var view = CollectionViewSource.GetDefaultView(_children);
             view.Refresh();
         }
         private void removeSon()
         {
             Parent.Children.Remove(this);
             //questo che segue serve per forzare una nuova chiamata dei 
binding del template,
             //non ho trovato altro modo (se non quello di ricostruire 
da capo il tree)
             var view = 
CollectionViewSource.GetDefaultView(Parent.Children);
             view.Refresh();
         }
Questo forza, per ogni item della collection del livello interessato 
dalla modifica dell'albero, la richiamata del converter che vede se sto 
disegnando un nodo o una foglia, e permette al template che ho postato 
tempo fa di disegnare linee di connessione corrette.
Grazie ancora

          }
Quindi tu sostituisci sempre la collection?
Io preferisco tenere privata la collection ed eseguire Clear/Remove + 
Add. Queste operazioni sollevano la inotifycollectionchanged che 
possono essere utili al tuo scopo.
Non so se è applicabile nel tuo caso ma potrebbe essere ancora più 
semplice.
Ciao!


 essere utili al tuo scopo.
No, nel caso postato era solo pigrizia, perchï¿œ ho semplicemente inserito 
Children come property usando lo snippet di mvvmlight :)
Comunque sempre nel mio caso faccio anch'io Clear/Remove/Add, quindi la 
set della property non viene praticamente mai usata, anche nel 
costruttore faccio la new su _children, Children potrebbe benissimo 
essere read only.
A parte questo,
var view = CollectionViewSource.GetDefaultView(_children);
view.Refresh();
sono necessarie, nell'Add, per richiamare il binding di tutti gli 
elementi della collection anzichï¿œ del solo elemento aggiunto, nel 
Remove, per richiamare il binding degli elementi restanti.
Avevo provato a intercettare la CollectionChanged per forzare il 
suddetto refresh, ma avevo effetti collaterali, nonostante sganciassi 
temporaneamente il gestore dell'evento e lo riagganciassi subito dopo il 
refresh (elementi disegnati due volte, boh...).
Con il metodo sopra "ï¿œ semplice e funziona" :)

 Con il metodo sopra "ï¿œ semplice e funziona" :)
buona la prima :)
