2010-08-30 68 views
16

當WPF選項卡控件中的選項卡更改時,是否有辦法防止選項卡卸載/重新加載?或者,如果這是不可能的,有沒有推薦的方法來緩存標籤內容,以便它們不必在每個標籤更改時重新生成?WPF TabControl - 防止卸載選項卡更改?

例如,一個選項卡的UI完全可定製並存儲在數據庫中。當用戶選擇要處理的對象時,自定義佈局中的項目會填充該對象的數據。用戶希望在初始加載時或檢索數據時稍微延遲一些,但在選項卡之間來回切換時不會出現延遲,並且更改制表符時的延遲非常明顯。

+0

只要選擇我不認爲的TabItems卸載/再項目在選項卡控制通道安格斯。我不確定,但也許你的TabControl的SelectionChanged邏輯需要改變,以便它不會每次都重新查詢數據庫? – ASanch 2010-08-30 14:28:02

+2

每次更改選項卡(我使用MVVM設計模式)時,都會運行DataTemplates的加載/卸載事件 – Rachel 2010-08-30 14:56:33

+0

因此,在您的應用程序中,只要選定選項卡更改,就會觸發與數據庫的連接以檢索對象的數據? – ASanch 2010-08-30 16:04:17

回答

16

我在這裏找到一個解決辦法:https://web.archive.org/web/20120429044747/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28

編輯:這是糾正鏈接: http://web.archive.org/web/20110825185059/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28

它切換標籤的時候基本上存儲標籤的ContentPresenter和負載,最多而不是重新繪製它。當拖放標籤時,它仍然會導致延遲,因爲這是一個刪除/添加操作,但是有一些修改讓我也消失了(以較低的調度程序優先級運行刪除代碼,然後添加代碼,因此添加操作有機會取消刪除操作並使用舊ContentPresenter,而不是繪製一個新的)

編輯:上面的鏈接似乎不再工作,所以我會在這裏粘貼代碼的副本。它已被修改了一些允許拖放,但它仍然應該以同樣的方式工作。

// 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

我認爲StackOverflow只是錯誤地解析鏈接的降價。如果您複製/粘貼整個URL(或者不使用\ [\] markdown),它就可以工作。 http://web.archive.org/web/20110825185059/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28 – skst 2016-08-09 17:56:48

+1

我正在嘗試使用您的解決方案,因爲它似乎解決了我面臨的確切問題。但是,我不知道如何完成工作......例如,變量'_itemsHolder'總是'null'。該控件位於我的XAML中,並且所有內容都可以正確顯示,但是XAML中是否有特定的參考需要彌補差距? – DonBoitnott 2017-07-14 13:36:53

+0

@DonBoitnott什麼時候它是空的?你說它工作正常,所以它必須在某個時候填充。 – Rachel 2017-07-14 15:03:25

2

我想補充這一點,我有一個類似的問題和管理的緩存是代表一個標籤項的內容在後面的代碼一個用戶控件來解決這個問題。

在我的項目中,我有一個綁定到集合(MVVM)的選項卡控件。但是,第一個選項卡是概覽,其中顯示了列表視圖中所有其他選項卡的摘要。我遇到的問題是,無論何時用戶將其選擇從項目選項卡移至概覽選項卡,總覽都會重新繪製所有摘要數據,根據集合中的項目數量,可能需要10-15秒。 (注意它們不是從數據庫或其他任何地方重新加載實際數據,它完全是花費時間的摘要視圖的繪圖)。

我想要的是,只有當數據上下文第一次加載並且任何後續標籤之間的切換瞬間完成時,這個加載彙總視圖纔會發生。

解決方案:

類涉及: MainWindow.xaml - 包含選項卡控件的主要頁面。 MainWindow.xaml.cs - 代碼隱藏在上面。 MainWindowViewModel.cs - 查看以上視圖的模型,包含集合。 Overview.xaml - 繪製概覽標籤項目內容的用戶控件。 OverviewViewModel.cs - 查看上述視圖的模型。

步驟:

  1. 更換的DataTemplate在「主窗口。XAML」與名爲空的用戶控件繪製概述選項卡項目‘OverviewPlaceholder’

  2. 做參考,以'內公衆MainWindowViewModel.cs「

  3. 添加靜態參考‘中概述’OverviewViewModel 'MainWindow.xaml.cs'

  4. 將事件處理程序添加到用戶控件'OverviewPlaceholder'的已加載事件中,在此方法內實例化靜態引用到'Overview',僅當它爲null時,設置此引用的datacontext到當前datacontext(即'MainWindowViewModel')內的'OverviewViewModel'引用並設置佔位符的內容成爲「概述」的靜態引用。

現在,因爲每個被加載時間(即用戶點擊到概述選項卡)概述頁面只繪製一次,它把已經呈現,靜態用戶控制回到頁面上。

0

我有一個非常簡單的解決方案,以避免選項卡更改選項卡更改, 在tabItem而不是content屬性中使用contentPresenter。

例如(在MVVM風格)

更換

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

通過

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

爲什麼這個答案是upvoted? 'ContentPresenter'將在包含其內容的標籤切換時被卸載。這什麼都不做。 – Sinatr 2016-02-23 08:54:57