Implementazione di una propria “fabbrica” ​​per riutilizzare le viste in WPF

Attualmente sto usando WAF (WPF Application Framework) per la programmazione in WPF. Mi piace molto l’idea di avere un ViewModel per ogni piccola View-Unit nella mia applicazione che successivamente ho implementato in questo modo.

Nel mio progetto ho ottenuto una lista complessa in cui ogni elemento di lista contiene anche un elenco. Ogni elemento list-list-list è un proprio ViewModel a causa della complessità. Lo scenario “worst-case” contiene in totale 60-90 viewmodels, solo per la visualizzazione elenco.

(È una lista di domande in cui ogni domanda ha un elenco di risposte con valutazioni e altri elementi dell’interfaccia utente).

Questa implementazione funziona alla grande ma le prestazioni sono piuttosto negative. Dopo la profilazione ho scoperto che l’errore si traduce nella creazione dei miei ViewModels quando passo da una serie di domande (perché l’intera lista deve essere generata di nuovo).

Quando passo tra le domande non riesco a riutilizzare le mie visualizzazioni 1: 1 perché non ci sono lo stesso numero di domande.

Tuttavia, pensavo di poter riutilizzare i modelli di visualizzazione dati e aggiungere (nel caso in cui il nuovo set richieda più viste) più viewmodels se necessario.

Pertanto ho scritto la seguente fabbrica:

[Export] public class ViewModelPerformanceFactory where T : IPerformanceFactoryViewModel { private List _collection; private int _index; private readonly ExportFactory _exportFactory; [ImportingConstructor] public ViewModelPerformanceFactory(ExportFactory exportFactory) { _exportFactory = exportFactory; _index = 0; } public void Reset() { _index = 0; if (_collection == null) { return; } foreach (var elem in _collection) { elem.Reset(); } } public T Get() { if (_collection == null) { _collection = new List(); } if (_collection.Count <= _index) { _collection.Add(_exportFactory.CreateExport().Value); } return _collection[_index++]; } } 

dove IPerformanceViewModel offre solo un Reset -Method per cancellare ViewModel e View.

Quindi ogni volta che viene caricata una nuova serie di domande, chiamo la funzione di reset del mio ViewModelPerformanceFactory che cancella tutti i modelli e riporta l’indice a 0 (quindi se qualcuno richiede una nuova istanza di viewmodel, otterrà il primo creato) .

In teoria, funziona alla grande.

Ora per la mia domanda / problema: più spesso cambio tra le mie domande-imposta più lentamente la mia applicazione è … Non è il caricamento degli oggetti viewmodel – questo va bene. La mia lista appare molto molto lenta – a volte anche bloccata per alcuni secondi e poi continua a crescere …

Penso che questo sia un problema WAF poiché ogni ViewModel installa una vista per vedere:

 protected ViewModel(TView view) : base(view) { this.view = view; } } 

e sembra che non riesca a riutilizzare Views facilmente come ViewModels in WAF.

Qualcuno ha un suggerimento per me o forse un altro approccio per velocizzare la mia applicazione? O qualcuno pensa che tutto il mio approccio sia stupido e io smetto di programmare? 😉

Modifica: A volte sembra che ci sia una perdita di memoria / prestazioni, ma non riproducibile ogni volta .. 🙁

Senza vedere l’intero codice, è difficile dire quali sono i tuoi problemi. Ma indovinando dalla descrizione che hai fornito:

  1. Passare assolutamente a DataTemplates e HierarchicalDataTemplates. Se stai creando nuovi controlli ogni volta che cambi i dati, non vedrai mai prestazioni stellari. Ciò ti consentirà di utilizzare anche la virtualizzazione.

  2. Il fatto che la tua applicazione stia diventando più lenta indica effettivamente una perdita di memoria. La causa più comune sono eventi che non vengono annullati.

  3. L’istanziazione di ViewModels non dovrebbe richiedere molto tempo dato che ce ne sono meno di 100. Se questo è il caso, dovresti cercare la causa del perché stanno impiegando così tanto tempo. Non si dovrebbe riutilizzare ViewModels che avvolge gli oggetti del modello. Se lo fai, hai bisogno di molta conservazione dei libri per “resettarli”, o devono essere apolidi, il che sconfigge in parte lo scopo di averli in primo luogo.

  4. Il fatto che ViewModel faccia riferimento alla View è un importante no-go per quanto riguarda MVVM. Nella maggior parte delle mie soluzioni, non ho nemmeno una class di visualizzazione, utilizzo DataTemplates per tutto tranne i controlli personalizzati come DateTimeBoxes e ComboBox personalizzati.

Non sono sicuro del tuo approccio o del perché ci sono problemi di prestazioni, ma qui è la mia soluzione a un problema simile.

La soluzione completa può essere trovata https://github.com/steinborge/ProxyTypeHelper/wiki

Quello che volevo ottenere era la possibilità di creare un modello di visualizzazione “generico” che potesse quindi essere assegnato a un datatemplate. Il modello dati è solo un controllo utente. Nel caso in cui si disponga di molte semplici schermate di manutenzione dei dati si risparmia molto codice ripetitivo.

Ma ha avuto un paio di problemi. Datatemplates non funziona bene con XAML con generics e se hai molti modelli di dati stai creando un sacco di XAML – specialmente se volessi usarlo in viste separate. Nel tuo caso menzioni fino a 90 visualizzazioni – questo sarebbe un sacco di XAML.

La soluzione consiste nel memorizzare i modelli in una ricerca e utilizzare un controllo del contenuto e DataTemplateSelector popolano a seconda del DataContext. Quindi, per prima cosa è necessario registrare il modello / le viste dati:

  manager.RegisterDataTemplate(typeof(GenericViewModel), typeof(WPFEventInter.UserControls.CarTypesView)); manager.RegisterDataTemplate(typeof(GenericViewModel), typeof(WPFEventInter.UserControls.ColourView)); 

RegisterDataTemplate aggiunge semplicemente il datatemplate a un dizionario:

  public void RegisterDataTemplate(Type viewModelType, Type dataTemplateType, string Tag="") { var template = BuildDataTemplate(viewModelType, dataTemplateType) ; templates.Add(viewModelType.ToString() + Tag, template); } private DataTemplate BuildDataTemplate(Type viewModelType, Type viewType) { var template = new DataTemplate() { DataType = viewModelType, VisualTree = new FrameworkElementFactory(viewType) }; return template; } 

Ora crea una vista con un controllo ContentPresenter. Questo visualizzerà la vista in base al Datacontext della vista.

Il DataTemplateSelector ha il seguente aspetto. Ciò restituisce la vista appropriata in base al datacontext:

 public class ContentControlGenericTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { DataTemplate retVal = null; try { retVal = Core.WPF.Infrastructure.DataTemplateManager.templates[item.GetType().ToString()]; } catch //empty catch to prevent design time errors.. { } return retVal; } 

È difficile fornire la soluzione senza vedere l’intero codice e quali sono i tuoi problemi:

Puoi farci il codice e sostituirlo con la tua domanda.

 public class MyICommand: ICommand { Action _TargetExecuteMethod; Func _TargetCanExecuteMethod; public MyICommand(Action executeMethod) { _TargetExecuteMethod = executeMethod; } public MyICommand(Action executeMethod, Func canExecuteMethod){ _TargetExecuteMethod = executeMethod; _TargetCanExecuteMethod = canExecuteMethod; } public void RaiseCanExecuteChanged() { CanExecuteChanged(this, EventArgs.Empty); } bool ICommand.CanExecute(object parameter) { if (_TargetCanExecuteMethod != null) { return _TargetCanExecuteMethod(); } if (_TargetExecuteMethod != null) { return true; } return false; } // Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command // Prism commands solve this in their implementation public event EventHandler CanExecuteChanged = delegate { }; void ICommand.Execute(object parameter) { if (_TargetExecuteMethod != null) { _TargetExecuteMethod(); } } 

}