2011-12-23 69 views
3

我在TabControl中遇到了WPF ListBox的問題。當我更改標籤時,ListBox將其滾動條位置重置爲0。下面是攝製代碼:TabControl滾動問題中的列表框

<TabControl x:Name="_tabs"> 
    <TabItem Header="1"> 
     <ListBox ItemsSource="{Binding}" ScrollViewer.VerticalScrollBarVisibility="Auto"/> 
    </TabItem> 
    <TabItem Header="2"> 
     <ListBox ItemsSource="{Binding}" ScrollViewer.VerticalScrollBarVisibility="Auto"/> 
    </TabItem> 
</TabControl> 

_tabs.DataContext = Enumerable.Range(1, 300).ToArray(); 

當窗口打開我的地方打開第二個選項卡,滾動列表到中間,回到第一個選項卡,然後再打開第二個。由於某種原因,列表滾動到頂部。

爲什麼會發生這種情況?我犯了一些愚蠢的錯誤?

+0

您的代碼適用於我(3.5和4.0)。你在使用什麼環境? – 2011-12-24 01:59:06

回答

5

WPF的默認行爲是卸載不可見的項目,其中包括卸載不可見的TabItems。這意味着當您返回到選項卡時,TabItem會重新加載,並且任何未綁定的內容(例如滾動位置)將被重置。

是一個很好的網站here其中包含的代碼擴展的TabControl和毀壞切換標籤 當它的TabItems停止,但該網站似乎已關閉ATM

這是我使用的代碼。它最初來自該網站,儘管我已對其進行了一些更改。它在切換標籤頁時保留TabItems的ContentPresenter,並在您返回頁面時使用它來重新繪製TabItem。它佔用更多的內存,但是我發現它在性能上更好,因爲TabItem不再需要重新創建其上的所有控件。

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

的TabControl的模板,我通常使用看起來像這樣:

<Style x:Key="TabControlEx_NoHeadersStyle" TargetType="{x:Type local:TabControlEx}"> 
    <Setter Property="SnapsToDevicePixels" Value="true"/> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type localControls:TabControlEx}"> 
       <DockPanel> 
        <!-- This is needed to draw TabControls with Bound items --> 
        <StackPanel IsItemsHost="True" Height="0" Width="0" /> 
        <Grid x:Name="PART_ItemsHolder" /> 
       </DockPanel> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 
+0

感謝您的幫助。我試圖使用你的代碼,但沒有改變。問題是默認模板中沒有「PART_ItemsHolder」部分(我正在使用.NET 4.0,未檢查3.5)。您是否將自己的模板與此代碼結合使用? – 2012-01-12 15:35:02

+0

@ alpha-mouse我在我的答案中加入了我通常使用的TabControl模板。我相當確定我已經使用這個類的默認TabControls,但我可能是錯的。我有一個我認爲是未修改的類的副本,它也使用'PART_ItemsHolder',雖然它來自幾年前,我認爲.Net 3.5是當時的標準。 – Rachel 2012-01-12 15:59:46

+0

@ alpha-mouse其實現在我正在看我的模板並思考它,我認爲在你的模板中需要具有'IsItemsHost =「True」'的東西才能工作。 – Rachel 2012-01-12 16:02:33

0

偉大的答案!歡呼瑞秋!我不能評論現有的職位,所以添加了我自己的答案。

我發現上述設法解決了這個問題。不過,我也發現有必要補充:

​​3210

獲取初始選定的標籤正確加載 - 在ItemContainerGenerator沒有內容等GetSelectedTabItem未能返回的TabItem。據推測,這與應用模板時尚未發生的渲染有關。

我發現這個問題只有在綁定一個選項卡控件的ItemsSource和SelectedItem時纔會體現出來 - 在我正在使用的應用程序中,我們最近切換到了使用它,所以我們可以通過編程切換選項卡來執行一些自定義導航。

的標籤項內容最初是由手工指定例如:

<TabItem Header="blah"> 
    <someControl/> 
</TabItem> 

使用這個配置改變所選擇的項目是罰款(對選擇沒有加載標籤),儘管它是由WPF只做過。這似乎表明,這種行爲可以在默認控件中關閉,但僅限於不綁定到項目列表(儘管我沒有證實這是事實)。

我嘗試的另一個解決方案是將ItemSource綁定模式更改爲OneTime,但這並沒有解決問題(我們在這裏不使用INotifyCollectionChanged集合)。