Interrompere TabControl dal ricreare i suoi figli

Ho un IList di viewmodels che sono legati a un TabControl . Questo IList non cambierà per tutta la durata del TabControl .

        

Ogni viewmodel ha un DataTemplate che è specificato in un ResourceDictionary .

    

Ognuna delle viste specificate nel DataTemplate richiede abbastanza risorse per creare che preferirei creare ogni vista solo una volta, ma quando cambio le tabs, viene chiamato il costruttore per la vista rilevante. Da quello che ho letto, questo è il comportamento previsto per TabControl , ma non mi è chiaro quale sia il meccanismo che chiama il costruttore.

Ho dato uno sguardo a una domanda simile che utilizza UserControl , ma la soluzione offerta qui mi imporrebbe di associare a viste che è indesiderabile.

Per impostazione predefinita, TabControl condivide un pannello per renderlo contenuto. Per fare ciò che vuoi (e molti altri sviluppatori WPF), devi estendere TabControl modo:

TabControlEx.cs

 [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] public class TabControlEx : TabControl { private Panel ItemsHolderPanel = null; public TabControlEx() : base() { // This is necessary so that we get the initial databound selected item ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; } ///  /// If containers are done, generate the selected item ///  ///  ///  private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; UpdateSelectedItem(); } } ///  /// Get the ItemsHolder and generate any children ///  public override void OnApplyTemplate() { base.OnApplyTemplate(); ItemsHolderPanel = GetTemplateChild("PART_ItemsHolder") as Panel; UpdateSelectedItem(); } ///  /// When the items change we remove any generated panel children and add any new ones as necessary ///  ///  protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (ItemsHolderPanel == null) return; switch (e.Action) { case NotifyCollectionChangedAction.Reset: ItemsHolderPanel.Children.Clear(); break; case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Remove: if (e.OldItems != null) { foreach (var item in e.OldItems) { ContentPresenter cp = FindChildContentPresenter(item); if (cp != null) ItemsHolderPanel.Children.Remove(cp); } } // Don't do anything with new items because we don't want to // create visuals that aren't being shown UpdateSelectedItem(); break; case NotifyCollectionChangedAction.Replace: throw new NotImplementedException("Replace not implemented yet"); } } protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); UpdateSelectedItem(); } private void UpdateSelectedItem() { if (ItemsHolderPanel == null) return; // Generate a ContentPresenter if necessary TabItem item = GetSelectedTabItem(); if (item != null) CreateChildContentPresenter(item); // show the right child foreach (ContentPresenter child in ItemsHolderPanel.Children) child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; } private ContentPresenter CreateChildContentPresenter(object item) { if (item == null) return null; ContentPresenter cp = FindChildContentPresenter(item); if (cp != null) return cp; // the actual child to be added. cp.Tag is a reference to the TabItem cp = new ContentPresenter(); cp.Content = (item is TabItem) ? (item as TabItem).Content : item; cp.ContentTemplate = this.SelectedContentTemplate; cp.ContentTemplateSelector = this.SelectedContentTemplateSelector; cp.ContentStringFormat = this.SelectedContentStringFormat; cp.Visibility = Visibility.Collapsed; cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); ItemsHolderPanel.Children.Add(cp); return cp; } private ContentPresenter FindChildContentPresenter(object data) { if (data is TabItem) data = (data as TabItem).Content; if (data == null) return null; if (ItemsHolderPanel == null) return null; foreach (ContentPresenter cp in ItemsHolderPanel.Children) { if (cp.Content == data) return cp; } return null; } protected TabItem GetSelectedTabItem() { object selectedItem = base.SelectedItem; if (selectedItem == null) return null; TabItem item = selectedItem as TabItem; if (item == null) item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; return item; } } 

XAML

  

Nota: non ho trovato questa soluzione. È stato condiviso nei forum di programmazione per diversi anni e credo che sia ora uno di quei libri di ricette WPF. La fonte più antica o originale per me credo fosse il post del blog PluralSight .NET e questa risposta su StackOverflow .

HTH,

La risposta di Dennis è superba e ha funzionato molto bene per me. Tuttavia, l’articolo originale cui si fa riferimento nel suo post è ora mancante, quindi la sua risposta necessita di un po ‘più di informazioni per essere utilizzabile immediatamente.

Questa risposta è fornita da un punto di vista MVVM ed è stata testata in VS 2013.

In primo luogo, un po ‘di background. Il modo in cui la prima risposta di Dennis funziona è che nasconde e mostra il contenuto della scheda, invece di distruggere e ricreare il contenuto della scheda, ogni volta che l’utente cambia una scheda.

Questo ha i seguenti vantaggi:

  • Il contenuto delle caselle di modifica non scompare quando si cambia la scheda.
  • Se si utilizza una vista ad albero in una scheda, non collassa tra le modifiche delle tabs.
  • La selezione corrente per tutte le griglie viene mantenuta tra gli interruttori di tabulazione.
  • Questo codice è più piacevole con uno stile di programmazione MVVM.
  • Non è necessario scrivere codice per salvare e caricare le impostazioni in una scheda tra le modifiche delle tabs.
  • Se si utilizza un controllo di terze parti (come Telerik o DevExpress), le impostazioni come il layout della griglia vengono mantenute tra gli interruttori di tabulazione.
  • Grandi miglioramenti nelle prestazioni: la commutazione delle tabs è praticamente istantanea, poiché non stiamo ridisegnando tutto ogni volta che una scheda cambia.

TabControlEx.cs

 // Copy C# code from @Dennis's answer, and add the following property after the // opening " 

Questo va nella stessa class indicata da DataContext.

XAML

 // Copy XAML from @Dennis's answer. 

Questo è uno stile. Va nell'intestazione del file XAML. Questo stile non cambia mai e viene indicato da tutti i controlli di tabulazione.

Scheda originale

La tua scheda originale potrebbe essere simile a questa. Se cambi scheda, noterai che il contenuto delle caselle di modifica sparirà, poiché i contenuti della scheda vengono eliminati e ricreati di nuovo.

   Hello   Hello 2  

Scheda personalizzata

Modifica la scheda per utilizzare la nostra nuova class C # personalizzata e indirizzala al nostro nuovo stile personalizzato utilizzando il tag Style :

   Hello   Hello 2  

Ora, quando cambi scheda, troverai che il contenuto delle caselle di modifica viene mantenuto, il che dimostra che tutto funziona correttamente.

Aggiornare

Questa soluzione funziona molto bene. Tuttavia, esiste un modo più modulare e MVVM per farlo, che utilizza un comportamento collegato per ottenere lo stesso risultato. Vedi progetto di codice: WPF TabControl: distriggerszione della virtualizzazione delle tabs . Ho aggiunto questo come una risposta aggiuntiva.

Aggiornare

Se si utilizza DevExpress , è ansible utilizzare l'opzione CacheAllTabs per ottenere lo stesso effetto (distriggers la virtualizzazione delle tabs):

   Hello   Hello 2   

Per la cronaca, non sono affiliato con DevExpress, sono sicuro che Telerik ha l'equivalente.

Questa soluzione esistente di @Dennis (con note aggiuntive di @Gravitas) funziona molto bene.

Tuttavia, esiste un’altra soluzione che è più modulare e MVVM, poiché utilizza un comportamento collegato per ottenere lo stesso risultato.

Vedi progetto di codice: WPF TabControl: distriggerszione della virtualizzazione delle tabs . Poiché l’autore è il capo tecnico di Reuters, il codice è probabilmente solido.

Il codice demo è davvero ben messo insieme, mostra un regolare TabControl, accanto a quello con il comportamento allegato.

inserisci la descrizione dell'immagine qui

Si prega di controllare la mia risposta da questo post in SO. Spero che risolva il problema, ma è un po ‘fuori strada MVVM. collegamento