|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#1 |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
[C# - WPF]File Explorer: metodo più corretto per il pattern Model-View-ViewModel?
Salve a tutti, per necessità mi sono messo a studiare questo pattern e sto provando a realizzare la conversione di un'applicazione che avevo in WinForm.
L'applicazione è un semplice File Explorer, a sinistra l'elenco delle cartelle, a destra la lista dei files in modalità dettagli. Ho cominciato per gradi, ho quindi da una parte un'applicazione wpf completa che mi carica i nodi dell'albero on demand impostando un path qualsiasi e dall'altra un programmino che mi carica tutti i file di una cartella preimpostata. Ora devo fondere il tutto in un singolo programma e ho qualche dubbio su come seguire correttamente il pattern MVVM. Ho creato 3 ViewModel: un ViewModel per ogni elemento del treeview (TreeItemViewModel), uno per ogni elemento della ListView (ListItemViewModel) e un ViewModel (MainViewModel) che è quello che associo come Datacontext alla Vista. Quest'ultimo nel suo costruttore crea il nodo di root dell'albero e lo aggiunge alla lista dei TreeItemViewModel bindata con la vista (e fin qui nulla da dire) e inizializza la lista dei ListItemViewModel, anch'essa bindata con la vista, sempre al nodo di root. Ora il problema è che quando clicco su un nuovo nodo, il rispettivo TreeItemViewModel viene richiamato ed eseguito, ma ovviamente il cambiamento non viene invocato poichè il MainViewModel non sa qual'è il nodo selezionato e allo stesso modo il TreeItemViewModel non ha conoscenza del MainViewModel... Qual'è il metodo corretto? Devo aggiungere un membro alla MainViewModel che indica qual'è la cartella selezionata, aggiungendo al costruttore del TreeViewItemModel un riferimento al MainViewModel ad esso associato? O esiste un metodo più elegante? spero si capisca quel che voglio
|
|
|
|
|
|
#2 | ||
|
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
Quote:
Puoi fareglielo conoscere nel costruttore, come dici tu. Oppure esponi un evento da parte dei TreeItemViewModel, intercettato dal MainViewModel. Se fai cosi' ricordati di de-registrare tutte le sottoscrizioni. Quote:
PS: Il file explorer c'e' gia' come controllo gia fatto
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. |
||
|
|
|
|
|
#3 | |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
Quote:
Proverò così quindi: nel set di IsSelezionato aggiungerò un'istruzione per associare il nodo al MainViewModel. Davvero? Mica lo sapevo...
|
|
|
|
|
|
|
#4 |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
E' insorto un nuovo problema...
In pratica alla pressione di un tasto viene eseguita un'azione lunga e qundi volevo avviare una progress bar fino al termine dell'operazione. Precisamente è una progress bar circolare sempre attiva, quindi agisco semplicemente sulla proprietà visibility del suo viewbox, così: Codice:
Visibility="{Binding ProgressBarShow, Converter={StaticResource BoolToVisibility}, UpdateSourceTrigger=PropertyChanged}"
Codice:
public bool ProgressBarShow
{
get { return _progressbarshow; }
set
{
if (value != _progressbarshow)
{
_progressbarshow = value;
OnPropertyChanged("ProgressBarShow");
}
}
}
Codice:
public DelegateCommand RicercaCommand { get; private set; }
this.RicercaCommand = new DelegateCommand((o) => this.Ricerca(),null);
Codice:
public void Ricerca()
{
ProgressBarShow = true;
//Do stuff
Thread.Sleep(10000);
}
Se invece inserisco un Debug.Print prima di ProgressBarShow=true, esso viene mostrato immediatamente. Avete qualche suggerimento? |
|
|
|
|
|
#5 |
|
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
Il metodo Ricerca() deve essere lanciato da un altro Thread.
Anche perche' senno' bloccheresti la GUI per tutta la durata della ricerca.
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. |
|
|
|
|
|
#6 | |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
Quote:
Chissà che mi aspettavo con le WPF
|
|
|
|
|
|
|
#7 |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
Ok, ora non so veramente che pesci pigliare e non capisco cosa succede...
In pratica ho aggiunto un File System Watcher per monitorare la cartella corrente: quando un file viene eliminato, controllo la lista delle cartelle (è una proprietà del modelview stesso) e faccio ListaCartelle.Remove(file). Ho fatto un pò di prove: il file esiste nella observable collection, viene trovato correttamente, ma quando cerco di rimuoverlo dalla lista, il programma crasha, stessa cosa se provo ad aggiungerne uno nuovo. Il bello è che questo succede esclusivamente negli eventi del file system watcher, se provo a fare la stessa cosa tramite un comando associato ad un bottone funziona tranquillamente. Da che può dipendere? L'evento del filesystemwatcher è questo: Codice:
void watcher_Deleted(object sender, FileSystemEventArgs e)
{
foreach (FileViewModel f in ListaFiles)
{
if (f.Nome == e.Name && f.Percorso == e.FullPath)
{
ListaFiles.Remove(f);
break;
}
}
}
|
|
|
|
|
|
#8 |
|
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
metti un try-catch e dicci l'eccezione, che con qualche probabilita' sara' la solita dove stai cercando di cambiare una property di un DependencyObject dal di fuori del Thread che ha creato il dependencyObject stesso.
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. Ultima modifica di gugoXX : 05-10-2010 alle 12:31. |
|
|
|
|
|
#9 |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
C'ho pensato appena salito in macchina... Domattina try-catcho e vi faccio sapere, oggi pomeriggio non ho avuto tempo.
|
|
|
|
|
|
#10 |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
Il messaggio è quello che ci si aspettava:
"This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread". Ho letto un pò di documentazione sull'argomento, ma molte soluzioni mi sembrano fin troppo per il mio progetto. Ho provato quindi a farmi un delegato di questo tipo: Codice:
private delegate void RemoveFileDelegate(FileViewModel f);
private void RemoveFile(FileViewModel f)
{
ListaFiles.Remove(f);
}
void watcher_Deleted(object sender, FileSystemEventArgs e)
{
foreach (FileViewModel f in ListaFiles)
{
if (f.Nome == e.Name && f.Percorso == e.FullPath)
{
App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new RemoveFileDelegate(RemoveFile), f);
}
}
}
EDIT: così facendo, se elimino più di un file per volta crasha... Ultima modifica di Donbabbeo : 06-10-2010 alle 11:05. |
|
|
|
|
|
#11 |
|
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
Il problema e' che la ObservableCollection e' un oggetto WPF, e non si dovrebbe usare nei ViewModel, altrimenti corri il rischio di sbattere di nuovo contro il problema della sincronizzazione, il cui dimenticarsi e' invece uno degli scopi di MVVM
Ma non puoi fare a meno di implementare cio' che WPF usa della ObervableCollection, altrimenti la collezione non viene rinfrescata sulla GUI. In pratica la tua collezione deve essere una normale List (O qualunque ICollection del tipo che ti interessa) ma anche implementare INotifyCollectionChanged Esistono online diverse implementazioni di una tale ObservableCollection lookalike che se userai, e se hai implementato MVVM correttamente, ti risolveranno il problema nel modo migliore. Per iniziare e per vedere se stai andando nella direzione giusta, prima di cercare e usare una di queste nuove ObservableCollection, usa una List<> normale. Semplicemente lancia NotifyPropertyChanged ogni volta che modifichi il contenuto della lista. L'intera lista verra' quindi rinfrescata sulla GUI, il che non e' ottimale, ma almeno non dovresti piu' ricevere l'errore. Potrai quindi proseguire sostituendo la List<> con il nuovo tipo corretto per beneficiare al meglio della messaggistica.
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. Ultima modifica di gugoXX : 06-10-2010 alle 14:55. |
|
|
|
|
|
#12 | |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
Quote:
PS: usando una comune List di FileViewModel e invocando OnPropertyChanged ad ogni suo cambiamento, il programma funziona normalmente |
|
|
|
|
|
|
#13 |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
RETTIFICA: L'inserimento e la rimozione, sebbene funzionino (non essendo una ObservableCollection posso invocare semplicemente Remove ed Add senza problemi), non vengono propagati alla View.
Provo a dire quello che mi sembra, correggimi se dico una cazzata: la OnpropertyChanged della List, controlla la lista come proprietà nella sua interezza, quindi scatena il suo metodo se e solo se la lista cambia nella sua interezza, cioè l'intero contenuto viene modificato, difatti il popolamento e la ricerca funzionano perchè io azzero e poi ricarico l'intera lista. Al contrario, la rimozione e l'inserimento non modificano la lista intera, ma un suo elemento interno, perciò senza la OnCollectionChanged non ho possibilità di aggiornare la lista. Sbaglio? Ora implemento anche INotifyCollectionChanged nella mia ViewModelBase, se ho ben capito, inserendo OnCollectionChanged dopo l'inserimento o la rimozione dalla lista, dovrei vedere l'azione propagata alla UI. E' corretto il mio ragionamento? |
|
|
|
|
|
#14 | ||
|
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
Quote:
Sei tu che devi a mano lanciare OnPropertyChanged("NomedellaLista") ogni volta che aggiungi o rimuovi qualcosa dalla lista. Con tutti i contro del caso sulla gui, ovvero rinfrescare completamente da zero tutto l'elenco. Quote:
E poi dovrai usarlo nel tuo viewmodel originale al posto della ObservableCollection. Poiche' sia IList<> che INotifyCollectionChanged<> sono abbastanza lunghi, ti consiglio di usare qualcosa di gia' fatto. Ma ti consiglio questo passo solo dopo essere riuscito ad usare la semplice List<> di cui sopra.
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. Ultima modifica di gugoXX : 06-10-2010 alle 16:09. |
||
|
|
|
|
|
#15 | |
|
Senior Member
Iscritto dal: Jul 2006
Messaggi: 8152
|
Prima di tutto: grazie per l'aiuto e scusa se ti rompo, ma voglio capire bene come funziona la cosa
Quote:
Quando io la popolo faccio: Codice:
NomeLista.Clear();
NomeLista = caricaLista();
OnPropertyChanged("NomeLista");
Quando inserisco un nuovo elemento faccio Codice:
NomeLista.Add(new Elemento(parametri elemento));
OnPropertyChanged("NomeLista");
E' normale che faccia così? |
|
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 14:05.





















