2011-12-23 42 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集合)。