2010-08-30 25 views
16

¿Hay alguna manera de evitar que la pestaña Descargar/Recargar cuando una pestaña cambie en un control de pestaña WPF? O si eso no es posible, ¿existe un método recomendado para almacenar en caché los contenidos de las pestañas para que no tengan que regenerarse con cada cambio de pestaña?WPF TabControl - ¿Cómo evitar la descarga en el cambio de pestaña?

Por ejemplo, la interfaz de usuario de una pestaña es completamente personalizable y se almacena en la base de datos. Cuando el usuario selecciona un objeto para trabajar, los elementos en el diseño personalizado se llenan con los datos de ese objeto. Los usuarios esperan una demora menor en la carga inicial o en la recuperación de datos, pero no al cambiar entre las pestañas, y la demora al cambiar las pestañas es muy notable.

+0

No creo TabItems se descargan/vuelve a cargar cada vez que el seleccionado elemento en el control de pestañas ch anges. No estoy seguro, pero tal vez la lógica SelectionChanged de su TabControl necesita ser modificada para que no vuelva a consultar la base de datos todo el tiempo. – ASanch

+2

Los eventos cargados/descargados de DataTemplates se ejecutan cada vez que cambio las pestañas (estoy usando el patrón de diseño de MVVM) – Rachel

+0

Por lo tanto, en su aplicación, cada vez que la pestaña seleccionada cambia, desencadena una conexión a la base de datos para recuperar el objeto ¿datos? – ASanch

Respuesta

16

he encontrado una solución aquí: https://web.archive.org/web/20120429044747/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28

Editar: Este es el enlace corregido: http://web.archive.org/web/20110825185059/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28

Básicamente almacena la ContentPresenter de la ficha y cargas que cuando cambiar de pestaña en lugar de volver a dibujarlo. Todavía estaba causando la demora al arrastrar/soltar pestañas ya que era una operación de quitar/agregar, sin embargo, con algunas modificaciones conseguí que también desapareciera (ejecutaba el código Eliminar en una prioridad de despachador inferior luego el código Agregar, por lo que el complemento operación tuvo la oportunidad de cancelar la operación Eliminar y usar el antiguo ContentPresenter en lugar de dibujar uno nuevo)

Editar: Parece que el enlace anterior ya no funciona, por lo tanto, pegaré una copia del código aquí. Se ha modificado un poco para permitir el arrastre/caída, pero aún debería funcionar de la misma manera.

// Extended TabControl which saves the displayed item so you don't get the performance hit of 
// unloading and reloading the VisualTree when switching tabs 

// Obtained from http://eric.burke.name/dotnetmania/2009/04/26/22.09.28 
// and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations 

[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] 
public class TabControlEx : System.Windows.Controls.TabControl 
{ 
    // Holds all items, but only marks the current tab's item as visible 
    private Panel _itemsHolder = null; 

    // Temporaily holds deleted item in case this was a drag/drop operation 
    private object _deletedObject = null; 

    public TabControlEx() 
     : base() 
    { 
     // this is necessary so that we get the initial databound selected item 
     this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; 
    } 

    /// <summary> 
    /// if containers are done, generate the selected item 
    /// </summary> 
    /// <param name="sender"></param> 
    /// <param name="e"></param> 
    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) 
    { 
     if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) 
     { 
      this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; 
      UpdateSelectedItem(); 
     } 
    } 

    /// <summary> 
    /// get the ItemsHolder and generate any children 
    /// </summary> 
    public override void OnApplyTemplate() 
    { 
     base.OnApplyTemplate(); 
     _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel; 
     UpdateSelectedItem(); 
    } 

    /// <summary> 
    /// when the items change we remove any generated panel children and add any new ones as necessary 
    /// </summary> 
    /// <param name="e"></param> 
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
    { 
     base.OnItemsChanged(e); 

     if (_itemsHolder == null) 
     { 
      return; 
     } 

     switch (e.Action) 
     { 
      case NotifyCollectionChangedAction.Reset: 
       _itemsHolder.Children.Clear(); 

       if (base.Items.Count > 0) 
       { 
        base.SelectedItem = base.Items[0]; 
        UpdateSelectedItem(); 
       } 

       break; 

      case NotifyCollectionChangedAction.Add: 
      case NotifyCollectionChangedAction.Remove: 

       // Search for recently deleted items caused by a Drag/Drop operation 
       if (e.NewItems != null && _deletedObject != null) 
       { 
        foreach (var item in e.NewItems) 
        { 
         if (_deletedObject == item) 
         { 
          // If the new item is the same as the recently deleted one (i.e. a drag/drop event) 
          // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be 
          // redrawn. We do need to link the presenter to the new item though (using the Tag) 
          ContentPresenter cp = FindChildContentPresenter(_deletedObject); 
          if (cp != null) 
          { 
           int index = _itemsHolder.Children.IndexOf(cp); 

           (_itemsHolder.Children[index] as ContentPresenter).Tag = 
            (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); 
          } 
          _deletedObject = null; 
         } 
        } 
       } 

       if (e.OldItems != null) 
       { 
        foreach (var item in e.OldItems) 
        { 

         _deletedObject = item; 

         // We want to run this at a slightly later priority in case this 
         // is a drag/drop operation so that we can reuse the template 
         this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, 
          new Action(delegate() 
         { 
          if (_deletedObject != null) 
          { 
           ContentPresenter cp = FindChildContentPresenter(_deletedObject); 
           if (cp != null) 
           { 
            this._itemsHolder.Children.Remove(cp); 
           } 
          } 
         } 
         )); 
        } 
       } 

       UpdateSelectedItem(); 
       break; 

      case NotifyCollectionChangedAction.Replace: 
       throw new NotImplementedException("Replace not implemented yet"); 
     } 
    } 

    /// <summary> 
    /// update the visible child in the ItemsHolder 
    /// </summary> 
    /// <param name="e"></param> 
    protected override void OnSelectionChanged(SelectionChangedEventArgs e) 
    { 
     base.OnSelectionChanged(e); 
     UpdateSelectedItem(); 
    } 

    /// <summary> 
    /// generate a ContentPresenter for the selected item 
    /// </summary> 
    void UpdateSelectedItem() 
    { 
     if (_itemsHolder == null) 
     { 
      return; 
     } 

     // generate a ContentPresenter if necessary 
     TabItem item = GetSelectedTabItem(); 
     if (item != null) 
     { 
      CreateChildContentPresenter(item); 
     } 

     // show the right child 
     foreach (ContentPresenter child in _itemsHolder.Children) 
     { 
      child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; 
     } 
    } 

    /// <summary> 
    /// create the child ContentPresenter for the given item (could be data or a TabItem) 
    /// </summary> 
    /// <param name="item"></param> 
    /// <returns></returns> 
    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)); 
     _itemsHolder.Children.Add(cp); 
     return cp; 
    } 

    /// <summary> 
    /// Find the CP for the given object. data could be a TabItem or a piece of data 
    /// </summary> 
    /// <param name="data"></param> 
    /// <returns></returns> 
    ContentPresenter FindChildContentPresenter(object data) 
    { 
     if (data is TabItem) 
     { 
      data = (data as TabItem).Content; 
     } 

     if (data == null) 
     { 
      return null; 
     } 

     if (_itemsHolder == null) 
     { 
      return null; 
     } 

     foreach (ContentPresenter cp in _itemsHolder.Children) 
     { 
      if (cp.Content == data) 
      { 
       return cp; 
      } 
     } 

     return null; 
    } 

    /// <summary> 
    /// copied from TabControl; wish it were protected in that class instead of private 
    /// </summary> 
    /// <returns></returns> 
    protected TabItem GetSelectedTabItem() 
    { 
     object selectedItem = base.SelectedItem; 
     if (selectedItem == null) 
     { 
      return null; 
     } 

     if (_deletedObject == selectedItem) 
     { 

     } 

     TabItem item = selectedItem as TabItem; 
     if (item == null) 
     { 
      item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; 
     } 
     return item; 
    } 
} 
+1

Creo que StackOverflow solo está analizando incorrectamente el marcado del enlace. Funciona si copia/pega la URL completa (o no usa el valor de \ [\]). http://web.archive.org/web/20110825185059/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28 – skst

+1

Estoy tratando de hacer uso de su solución, ya que parece resolver el problema exacto que enfrento. Sin embargo, no sé exactamente cómo terminar el trabajo ... La variable '_itemsHolder' siempre es' null', por ejemplo. El control está en mi XAML y todo se muestra correctamente, pero ¿hay alguna referencia particular en XAML que necesite para cerrar la brecha? – DonBoitnott

+0

@DonBoitnott ¿En qué momento es nulo? Dijiste que funciona correctamente, por lo que debe poblarse en algún momento. – Rachel

2

Sólo para añadir a esto, tuve un problema similar y lograron resolverlo mediante el almacenamiento de un control de usuario que representa el contenido de un elemento de ficha en el código subyacente.

En mi proyecto tengo un control de pestañas vinculado a una colección (MVVM). Sin embargo, la primera pestaña es una vista general que muestra un resumen de todas las otras pestañas en una vista de lista. El problema que tenía era que cada vez que un usuario mueve su selección de una pestaña de elemento a la pestaña de vista general, la descripción general se vuelve a dibujar con todos los datos de resumen, que pueden tomar de 10 a 15 segundos dependiendo de la cantidad de elementos en la colección. (tenga en cuenta que no se trata de volver a cargar los datos reales de una base de datos ni nada por el estilo, es simplemente el dibujo de la vista de resumen lo que estaba demorando).

Lo que quería era que esta carga de la vista de resumen solo se produjera una vez cuando el contexto de datos se carga por primera vez y cualquier conmutación posterior entre pestañas sea instantánea.

Solución:

clases involucradas: MainWindow.xaml - la página principal que contiene la tarjeta de registro. MainWindow.xaml.cs - Código detrás para arriba. MainWindowViewModel.cs - Ver modelo para la vista anterior, contiene la colección. Overview.xaml: control del usuario que dibuja el contenido del elemento de la pestaña de descripción general. Visión generalViewModel.cs - Ver modelo para la vista de arriba.

Pasos:

  1. reemplazar el DataTemplate en 'MainWindow.xaml' que dibuja el elemento de ficha visión general con un control de usuario en blanco denominado 'OverviewPlaceholder'

  2. la referencia a la 'OverviewViewModel' público dentro de '' MainWindowViewModel.cs

  3. Añadir una referencia estática a 'Visión general' en 'MainWindow.xaml.cs'

  4. Agregue un controlador de eventos al evento cargado del control de usuario 'OverviewPlaceholder', con este método instancia la referencia estática a 'Descripción general' solo si es nulo, configure el contexto de datos de esta referencia a la referencia 'OverviewViewModel' dentro del contexto de datos actual (que es 'MainWindowViewModel') y establecer el contenido del marcador de posición ser la referencia estática a 'Descripción general'.

Ahora la página de información general solamente se extrae una vez porque cada vez que se carga (es decir, el usuario hace clic en la pestaña visión general), que pone el control de usuario estática ya rendido de nuevo en la página.

0

Tengo una solución muy simple para evitar la pestaña de recargar en el cambio de pestaña, use un contentPresenter en el tabItem en lugar de la propiedad de contenido.

por ejemplo (en el estilo MVVM)

reemplazar

 <TabItem Header="Tab1" Content="{Binding Tab1ViewModel}" /> 

por

 <TabItem Header="Tab1"> 
      <ContentPresenter Content="{Binding Tab1ViewModel}" /> 
     </TabItem> 
+0

¿Por qué esta respuesta es votada? 'ContentPresenter' se descargará al cambiar las pestañas, incluido su contenido. Esto no hace nada. – Sinatr

Cuestiones relacionadas