【问题标题】:WPF/MVVM TreeView scroll bar does not reset when items cleared清除项目时 WPF/MVVM TreeView 滚动条不会重置
【发布时间】:2019-09-06 21:47:01
【问题描述】:

我正在使用 WPF/MVVM TreeView 来显示一些数据。由于可能有数千条记录,我使用页面显示它 - 每页 100 个项目,否则树将严重滞后且无响应。我已将分页功能封装到下面由对象DuplicateFilesPageView 表示的类中,该对象公开ObservableCollection<> 对象-Collection,该对象绑定到TreeView。要更改页面DuplicateFilesPageView,请调用ObservableCollection<>.Clear(),然后为每个项目调用ObservableCollection<>.Add(item) 以重新填充集合。

问题是当项目被清除然后添加新项目时,垂直滚动条不会改变它的位置。如果它在中间滚动,它会将新视图滚动到相同的位置,如果没有足够的项目,它将滚动到列表的末尾。

我需要做的是当项目被重置时,垂直滚动条位置也需要重置为0。

我尝试在我的 ViewModel 上引发事件 OnPropertyChanged(name(DuplicateFilesPageView .Collection)),但这没有帮助。 此外,我没有看到任何可以改变这种行为的属性。

我将不胜感激有关如何解决此问题的任何建议。

<TreeView Grid.Row="1" ItemsSource="{Binding DuplicateFilesPageView.Collection}" Margin="0,0,3,0">
    <TreeView.Resources>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsExpanded" Value="True" />
        </Style>
    </TreeView.Resources>
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <Setter Property="IsExpanded" Value="True" />
            <Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>

【问题讨论】:

    标签: c# wpf treeview scrollview treeviewitem


    【解决方案1】:

    当您选择新页面时,会触发一个事件。由于我不太明白您在哪里执行此操作,因此我使用了 Treeview 中的“SelectedItemChanged”。

    然后你必须: 首先获取 Scrollviewer,如下:Get datagrid's scrollviewer

    使用该 Scrollviewer 调用 yourScrollviewer.ScrollToTop()

    private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            GetScrollViewer(sender as UIElement).ScrollToTop();
        }
    
        public static ScrollViewer GetScrollViewer(UIElement element)
        {
            if (element == null) return null;
    
            ScrollViewer retour = null;
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element) && retour == null; i++)
            {
                if (VisualTreeHelper.GetChild(element, i) is ScrollViewer)
                {
                    retour = (ScrollViewer)(VisualTreeHelper.GetChild(element, i));
                }
                else
                {
                    retour = GetScrollViewer(VisualTreeHelper.GetChild(element, i) as UIElement);
                }
            }
            return retour;
        }
    

    【讨论】:

    • 是的,这可行,但它会破坏 MVVM 模式 =(
    【解决方案2】:
    /// <summary>
    ///     Behavior that makes the <see cref="System.Windows.Controls.TreeView.SelectedItem" /> bindable.
    /// </summary>
    internal class BindableTreeViewSelectedItemBehavior : Behavior<TreeView>
    {
        #region " SelectedItem "
        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableTreeViewSelectedItemBehavior), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));
    
        public object SelectedItem
        {
            get { return this.GetValue(SelectedItemProperty); }
            set { this.SetValue(SelectedItemProperty, value); }
        }
    
        private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue is TreeViewItem item)
            {
                item.SetValue(TreeViewItem.IsSelectedProperty, true);
                item.Focus();
                return;
            }
    
            if (sender is BindableTreeViewSelectedItemBehavior behavior)
            {
                var treeView = behavior.AssociatedObject;
                if (treeView != null)
                {
                    item = GetTreeViewItem(treeView, e.NewValue);
                    if (item != null)
                    {
                        item.IsSelected = true;
                        item.Focus();
                    }
                }
            }
        }
        #endregion
    
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.SelectedItemChanged += this.OnTreeViewSelectedItemChanged;
        }
    
        private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            this.SelectedItem = e.NewValue;
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
            if (this.AssociatedObject != null)
            {
                this.AssociatedObject.SelectedItemChanged -= this.OnTreeViewSelectedItemChanged;
            }
        }
    
        #region " GetBringIndexIntoView "
        /// <summary>
        /// GetBringIndexIntoView
        /// </summary>
        /// <param name="itemsHostPanel"></param>
        /// <returns>Action<int></returns>
        private static Action<int> GetBringIndexIntoView(Panel itemsHostPanel)
        {
            if (itemsHostPanel is VirtualizingStackPanel virtualizingPanel)
            {
                var method = virtualizingPanel.GetType().GetMethod("BringIndexIntoView", BindingFlags.Instance | BindingFlags.NonPublic, Type.DefaultBinder, new[] { typeof(int) }, null);
                if (method != null)
                {
                    return i => method.Invoke(virtualizingPanel, new object[] { i });
                }
            }
    
            return null;
        }
        #endregion
    
        #region " GetTreeViewItem "
        /// <summary>
        /// GetTreeViewItem
        /// Recursively search for an item in this subtree.
        /// </summary>
        /// <param name="container">The parent ItemsControl. This can be a TreeView or a TreeViewItem.</param>
        /// <param name="item">The item to search for.</param>
        /// <returns>TreeViewItem</returns>
        private static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
        {
            if (container != null)
            {
                if (container.DataContext == item)
                {
                    return container as TreeViewItem;
                }
    
                // Expand the current container
                if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
                {
                    container.SetValue(TreeViewItem.IsExpandedProperty, true);
                }
    
                // Try to generate the ItemsPresenter and the ItemsPanel.
                // by calling ApplyTemplate.  Note that in the 
                // virtualizing case even if the item is marked 
                // expanded we still need to do this step in order to 
                // regenerate the visuals because they may have been virtualized away.
                container.ApplyTemplate();
                var itemsPresenter = (ItemsPresenter)container.Template.FindName("ItemsHost", container);
                if (itemsPresenter != null)
                {
                    itemsPresenter.ApplyTemplate();
                }
                else
                {
                    // The Tree template has not named the ItemsPresenter, 
                    // so walk the descendents and find the child.
                    itemsPresenter = container.GetVisualDescendant<ItemsPresenter>();
                    if (itemsPresenter == null)
                    {
                        container.UpdateLayout();
                        itemsPresenter = container.GetVisualDescendant<ItemsPresenter>();
                    }
                }
    
                if (itemsPresenter != null)
                {
                    var itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
    
                    // Ensure that the generator for this panel has been created.
    #pragma warning disable 168
                    var children = itemsHostPanel.Children;
    #pragma warning restore 168
    
                    var bringIndexIntoView = GetBringIndexIntoView(itemsHostPanel);
                    for (int i = 0, count = container.Items.Count; i < count; i++)
                    {
                        TreeViewItem subContainer;
                        if (bringIndexIntoView != null)
                        {
                            // Bring the item into view so 
                            // that the container will be generated.
                            bringIndexIntoView(i);
                            subContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromIndex(i);
                        }
                        else
                        {
                            subContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromIndex(i);
    
                            // Bring the item into view to maintain the 
                            // same behavior as with a virtualizing panel.
                            subContainer.BringIntoView();
                        }
    
                        if (subContainer == null)
                        {
                            continue;
                        }
    
                        // Search the next level for the object.
                        var resultContainer = GetTreeViewItem(subContainer, item);
                        if (resultContainer != null)
                        {
                            return resultContainer;
                        }
    
                        // The object is not under this TreeViewItem
                        // so collapse it.
                        subContainer.IsExpanded = false;
                    }
                }
            }
    
            return null;
        }
        #endregion
    }
    

    我过去曾使用此行为来获取树视图,以便在通过绑定更改选定项以使项目滚动到视图时进行更新。你可以这样使用它:

    <TreeView Grid.Column="1" Grid.Row="1" ItemsSource="{Binding MyTree}" MaxHeight="270" ScrollViewer.VerticalScrollBarVisibility="Auto" VirtualizingStackPanel.IsVirtualizing="True">
                    <i:Interaction.Behaviors>
                        <h:BindableTreeViewSelectedItemBehavior SelectedItem="{Binding SelectedTV}"></h:BindableTreeViewSelectedItemBehavior>
                    </i:Interaction.Behaviors>
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type wiz:TVTest}">
                            <Label Content="{Binding Name}"></Label>
                            <HierarchicalDataTemplate.ItemTemplate>
                                <HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type wiz:TVTest}">
                                    <Label Content="{Binding Name}"></Label>
                                    <HierarchicalDataTemplate.ItemTemplate>
                                        <DataTemplate DataType="{x:Type wiz:TVTest}">
                                            <Label Content="{Binding Name}"></Label>
                                        </DataTemplate>
                                    </HierarchicalDataTemplate.ItemTemplate>
                                </HierarchicalDataTemplate>
                            </HierarchicalDataTemplate.ItemTemplate>
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </TreeView>
    

    然后您可以将所选项目设置为树视图中的第一项,它应该滚动到顶部。

    这里是辅助函数:

    public static T GetVisualDescendant<T>(this DependencyObject visual) where T : DependencyObject
        {
            return (T)visual.GetVisualDescendants().FirstOrDefault(d => d is T);
        }
    
    public static IEnumerable<DependencyObject> GetVisualDescendants(this DependencyObject visual)
        {
            if (visual == null)
            {
                yield break;
            }
    
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(visual, i);
                yield return child;
    
                if (VisualTreeHelper.GetChildrenCount(child) == 0)
                {
                    continue;
                }
    
                foreach (DependencyObject subChild in GetVisualDescendants(child))
                {
                    yield return subChild;
                }
            }
        }
    

    【讨论】:

    • 谢谢!看起来是正确的解决方案,我将在这个周末尝试它(这是一个开源项目)。 WPF 继续让我感到惊讶,- 做一些简单的事情你需要通过火,但看起来复杂的事情通常很容易完成。
    猜你喜欢
    • 2016-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-01
    相关资源
    最近更新 更多