52 messaggi dal 26 aprile 2006
Ciao Ragazzi,

ho una classe che ha, tra le sue property, una collection di oggetti di tipo "Rigo":
Public Class MiaClasse
Implements INotifyPropertyChanged
...
Private _righi As List(of Rigo)

Public Property righi() As List(of Rigo)
Get ....
Set ...
End Property

End Class

Ho implementato INotifyPropertyChanged.
Per questo, riesco a ricevere correttamente le notifiche che mi interessano anche sulla collection (intercetto i metodi Add, Remove,..) ma se vado in modifica di un Item della collection non ricevo notifiche.

Mi spiego meglio con un po' di codice:

dim mia as MiaClasse = new MiaClasse
mia.righi.Add(new Rigo) --> ricevo notifica dell'Add
mia.righi(0).campo1 = 10 --> non ricevo alcuna modifica

Non mi stupisco, ma come posso ricevere notifica sulle modifiche agli item della property righi ? C'è qualche interfaccia da implementare ?

p.s.
ho provato ad implementare IList(Of Rigo), ma riesco ad intercettare solo gli eventi Add, Clear, Remove, etc, ma non la modifica di un item...

Idee ?
grazie
franco
Ciao,

devi implementare INotifyPropertyChanged anche su Rigo (e ovviamente sottoscrivere anche gli eventi di ogni istanza).

A presto,
m.
179 messaggi dal 12 luglio 2007
Tecnicamente stai accedendo ad un puntatore all'oggetto contenuto nella collection, quindi la collection non sa cosa poi ci fai con l'oggetto in questione. Dovrebbe essere quest'ultimo a sollevare una chiamata per segnalare che è stato modificato.
52 messaggi dal 26 aprile 2006
Sono daccordo con amuro, non con Cradle.

Il punto è proprio questo. Una possibile soluzione è implementare INotifyPropertyChanged per la classe Rigo e dare, alla stessa classe Rigo, il puntatore alla classe che lo contiene. In questo modo, OnChange di Rigo può sollevare l'evento a MiaClasse.
Questo però mi fa storcere il naso: dal punto di vista del design, intendo.

Infatti:
1) MiaClasse contiene una List(of Rigo)
2) Rigo dovrebbe contenere un campi di tipo MiaClasse (solo per sollevare l'evento)
3) il costruttore di Rigo dovrebbe esporre un parametro che indichi il puntatore di Miaclasse.

Oggi pero' faccio una prova con classi nested...

che ne dite ?
Grazie per interessarvi del mio post.
Modificato da k4soft il 12 gennaio 2010 12.52 -
Dico che forse mi sono spiegato male io (o forse non hai capito tu  )

Mi spieghi a cosa serve una reference da Rigo a MiaClasse? Io sto dicendo che
1) Rigo implementa INotifyPropertyChanged e quindi solleva un *suo* evento PropertyChanged se cambi un valore di una sua proprietà
2) Chi deve essere notificato di cambiamenti su Rigo, sottoscrive questo evento su ogni istanza di Rigo.
3) Se con il tuo gestore "MiaClasse" vuoi semplicemente un qualcosa che ti segnali se gli elementi di una collection o la collection stessa vengono modificati, non devi far altro che aggiungere un handler da MiaClasse a ogni Rigo che aggiungi (e rimuoverlo quando lo rimuovi) e rilanciare da questo l'evento PropertyChanged di MiaClasse. Per capirci

in MiaClasse:
Public Sub Add(item as Rigo)
AddHandler(Rigo.PropertyChanged, Me.PropertyChangedHandler)
End Sub

Public Sub PropertyChangedHandler(sender as Object, e as PropertyChangedEventArgs)
If Not Me.PropertyChanged Is Nothing Then
Me.PropertyChanged(Sender, e)
End If
End Sub


Come vedi, dentro Rigo non serve nessuna reference a MiaClasse

Ciò detto, come premio per tutto quello che ho scritto, mi togli la curiosità di spiegarmi a cosa ti serve tutto ciò? Te lo chiedo perché mi sembra un'esigenza un po' strana per una web app, mentre in WPF ad es. (in cui INotifyPropertyChanged è praticamente obbligatoria se usi il binding), le cose non andrebbero fatte proprio così: per dirti... la collection dovrebbe implementare INotifyCollectionChanged, in modo da dare evidenza dei membri aggiunti/rimossi/sostituiti quando ciò accade, e non INotifyPropertyChanged

Ciao
m.
52 messaggi dal 26 aprile 2006
Cradle ha scritto:
non devi far altro che aggiungere un handler da MiaClasse a ogni Rigo che aggiungi (e rimuoverlo quando lo rimuovi) e rilanciare da questo l'evento PropertyChanged di MiaClasse. Per capirci

in MiaClasse:
Public Sub Add(item as Rigo)     
AddHandler(Rigo.PropertyChanged, Me.PropertyChangedHandler)     
End Sub     
     
Public Sub PropertyChangedHandler(sender as Object, e as PropertyChangedEventArgs)     
If Not Me.PropertyChanged Is Nothing Then     
Me.PropertyChanged(Sender, e)     
End If     
End Sub


Come vedi, dentro Rigo non serve nessuna reference a MiaClasse


Credo che aggiungere l'handler da MiaClassse "equivale" a dare a Rigo la reference di MiaClasse (infatti inietti l'indirizzo di Me.PropertyChangedHandler).
Comunque, per evitare incomprensioni, posto il codice:

    
    
    Public Class MiaClasse    
        Private _righi As List(Of Rigo)    
        Private _sommaDiProdotti As Decimal    
    
        Public Sub New()    
            _righi = New List(Of Rigo)    
            _sommaDiProdotti = 0    
        End Sub    
    
        Public Property righi() As List(Of Rigo)    
            Get    
                Return _righi    
            End Get    
            Set(ByVal value As List(Of Rigo))    
                _righi = value    
            End Set    
        End Property    
    
        Public ReadOnly Property sommaDiProdotti() As Decimal    
            Get    
                Return _sommaDiProdotti    
            End Get    
        End Property    
    
        Protected Sub calcolaSommaDiProdotti()    
            _sommaDiProdotti = 0    
            For Each r As Rigo In _righi    
                _sommaDiProdotti = _sommaDiProdotti + (r.fattore1 * r.fattore2)    
            Next    
        End Sub    
    
    End Class    
    
    
    Public Class Rigo    
        Implements INotifyPropertyChanged    
    
        Private _fattore1 As Decimal    
        Private _fattore2 As Decimal    
    
        Public Property fattore1() As Decimal    
            Get    
                Return _fattore1    
            End Get    
            Set(ByVal value As Decimal)    
                _fattore1 = value    
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("fattore1"))    
            End Set    
        End Property    
        Public Property fattore2() As Decimal    
            Get    
                Return _fattore2    
            End Get    
            Set(ByVal value As Decimal)    
                _fattore2 = value    
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("fattore2"))    
            End Set    
        End Property    
    
        Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged    
    
    
        Private Sub Rigo_PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Handles Me.PropertyChanged    
            ' questo codice viene eseguito se cambia fattore1 o fattore2    
' e da qui vorrei forzare l'esecuzione di calcolaSommaDiProdotti    
        End Sub    
    End Class    


Da notare che per brevità non ho implementato INotifyPropertyChanged su MiaClasse. Inoltre, non vorrei implementare IList(Of Rigo) perchè mi sembra quasi inutile. Infatti si presenterebbero 2 scenari:
A)
 
Dim m as New MiaClasse  
Dim r1 as New Rigo 
m.Add(r1)  ' qui tutto ok gli eventi vengono sollevati 

B)
  
Dim L as new List(of Rigo)  
Dim r1 as New Rigo 
L.Add(r1)  
Dim m as New MiaClasse  
m.righi = L   '  qui non è possibile settare gli handler: m viene creata dopo 
              ' non uso il metodo Add di MiaClasse, ma quello della List(Of T)


Nello scenario B, avrei evidenza dell'errore solo a runtime.

Questo è il codice per testare il funzionamento desiderato:

    
        Dim m As New MiaClasse    
    
        Dim r1 As New Rigo    
        r1.fattore1 = 1    
        r1.fattore2 = 2    
    
        m.righi.Add(r1)    
        ' ora dovrebbe stampare 2    
        Console.WriteLine("la somma di prodotti è {0}", m.sommaDiProdotti.ToString)    
    
        Dim r2 As New Rigo    
        r2.fattore1 = 1    
        r2.fattore2 = 2    
    
        m.righi.Add(r2)    
        ' ora dovrebbe stampare 4  = 1*2 + 2*2  
        Console.WriteLine("la somma di prodotti è {0}", m.sommaDiProdotti.ToString)    
    
        m.righi(0).fattore1 = 3    
        ' ora dovrebbe stampare 8 = 3*2 + 1*2   
        Console.WriteLine("la somma di prodotti è {0}", m.sommaDiProdotti.ToString)    
    


La problematica che mi sto ponendo non è relativa alla web app (forse avrei fatto meglio a postare nel forum .net framework), ma al solo domain model.

Si tratta di implementare una regola del business object: MiaClasse può avere una list(of Rigo) ed espone la somma dei prodotti dei fattori contenuti nei righi.
Ovviamente, potevo mettere un For Each nel Get di sommaDiProdotti e ricalcolarlo ogni volta. Pero' mi sembrava uno spreco di operazioni, specie se sommaDiProdotti è molto acceduto. Conviene ricalcolarlo solo quando serve effettivamente.

Spero che il problema sia piu' chiaro. Sei libero di modificare il codice da me postato.

Qualcun altro si è posto il mio problema e l'ha risolto così:
http://www.codeproject.com/KB/cs/ListOfMutablesWithEvents.aspx?msg=2762342

L'ho testato e funziona, anche se mi sembra un po' macchinoso.
Grazie e scusami se ti sto facendo scervellare :)
Modificato da k4soft il 13 gennaio 2010 01.17 -
179 messaggi dal 12 luglio 2007
Ho capito cosa vuoi fare, un ricalcolo automatico del totale delle righe! Secondo me non dei esporre direttamente la collection delle righe ma costruire in miaClasse le funzioni AddRigo, ecc. In questo modo quando aggiungi un rigo a miaclasse registri PropertyChanged di rigo con un addhandler verso una funzione RicalcolaTotale.

Io però personalmente, per risparmiare inutili cicli di clock nel caso in cui aggiungi 40 prodotti, sposterei il ricalcolo sulla property sommaDiProdotti in MiaClasse. In questo modo il ricalcolo avviene una volta sola quando ne hai effettivo bisogno.

Inoltre, se mi posso permettere un altro consiglio, quando esponi una collezione all'esterno usa sempre una property readonly, a meno che la collezione intera possa essere sostituita. Tanto per accedere il lettura/scrittura degli elementi devi solo ricevere il puntatore a righi e non sovrascrivere il puntatore di righi con un altro.
Allora.. ci sono diverse cose che non mi quadrano nel tuo codice

Partiamo dalla considerazione più banale: la tua proprietà Righi deve essere readonly (è una best practice), quindi non puoi assegnarla e risolvi da subito il problema B.

Ora, discorso filosofico  . Sospettavo che avessi la necessità di aggiornare un totalizzatore o qualcosa di simile al variare della collection. Intanto ti faccio una riflessione di fondo: tu dici che una proprietà calcolata è uno spreco di risorse, io dico che si tratta di sommare cosa, 10 righe? 30 righe? tutto in memory. Per evitarlo invece devi mettere insieme un sistema di messaging non banale, impostando n gestori di eventi, rimuovendoli quando rimuovi gli elementi e soprattutto ricalcolando di volta in volta questo totale anche quando tu stai aggiungendo elementi e il totale non lo leggi mai. Ti chiedo...
1) Sei sicuro che complicandoti così la vita risparmi risorse?
2) Sei sicuro che queste risorse in ogni caso siano così penalizzanti per il sistema?
3) Sei sicuro che scrivere oggetti più complessi per evitare un calcolo on-the-fly equivalga a "risparmiare"?

Ciao,
m.

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.