Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
.net WPF ListView虚拟化分组_.net_Wpf_Performance_Listview_Virtualization - Fatal编程技术网

.net WPF ListView虚拟化分组

.net WPF ListView虚拟化分组,.net,wpf,performance,listview,virtualization,.net,Wpf,Performance,Listview,Virtualization,有人知道在启用分组时支持UI虚拟化的ListView实现吗?默认情况下,在设置分组时禁用VirtualzingStackPanel 微软似乎不打算在.NET Framework的v4.0版本中实现这一点,因此我正在寻找替代解决方案。一个选择是看看Bea Stollniz关于改进TreeView性能的系列文章: ,及。虽然她所做的更适合于TreeView,因为默认情况下它们是分组的,所以没有任何虚拟化,但所学到的经验肯定可以应用于具有虚拟化组的自定义ListView。事实上,在第3部分中,她使用列

有人知道在启用分组时支持UI虚拟化的ListView实现吗?默认情况下,在设置分组时禁用VirtualzingStackPanel


微软似乎不打算在.NET Framework的v4.0版本中实现这一点,因此我正在寻找替代解决方案。

一个选择是看看Bea Stollniz关于改进TreeView性能的系列文章:
,及。虽然她所做的更适合于TreeView,因为默认情况下它们是分组的,所以没有任何虚拟化,但所学到的经验肯定可以应用于具有虚拟化组的自定义ListView。事实上,在第3部分中,她使用列表框作为基础来创建虚拟化树,这也是虚拟化分组的一个良好开端。显然,在树状视图中显示项目与分组的ListView有一些不同,例如组节点的选择,但这可以通过捕获SelectionChanged来解决。

我找到了一个示例,该示例将分组的ListView转换为支持虚拟化的平面列表。但是,我无法想象如何模拟头的扩展动作。

wpf/.net4.5现在支持这一点


如果您仍然以4.0为目标,您可以将其设置为反射,这样至少一些用户可以从中受益。

我希望这不会太离题,但我最近遇到了类似的问题。如上所述,它只是.NET4.0版本。我甚至同意,在大多数情况下,对于组合框,您通常不需要虚拟化,因为它不应该有那么多的项,如果需要分组,那么应该实现某种主-细节解决方案。但可能存在一些灰色区域

Luke提供的关于MSDN上分组和虚拟化的链接对我帮助很大。在我的情况下,这是唯一的方法,我能够找到或找到任何地方,在我需要的方向。它不支持ListViewCollection中的所有功能。我必须覆盖一些方法,否则项目的选择将无法正常工作。显然还有更多的工作要做

以下是FlatGroupListCollectionView的更新解决方案:

//
///提供一个视图,用于将组展平到列表中
///这是为了避免ListCollectionView在.NET 4.0中的限制,如果使用分组,则虚拟化将不起作用
///为了支持这种分组方式,它假设视图中有一些适当的推进(XAML)
///注意:在实现时,它不支持嵌套分组
///注意:只有重写的属性和方法行为正确,与项选择相关的某些方法和属性可能无法按预期工作,需要新的实现
/// 
公共类FlatGroupListCollectionView:ListCollectionView
{
/// 
///初始化类的新实例。
/// 
///此集合中使用的列表
公共FlatGroupListCollectionView(IList列表)
:基本(列表)
{
}
/// 
///目前只支持一级分组
///如果索引与标题匹配,则返回CollectionViewGroups
///否则,将索引映射到基本范围以获取实际项目
/// 
///从中获取项目的索引
///在给定索引中找到的项
公共重写对象GetItemAt(int索引)
{
int delta=0;
ReadOnlyObservableCollection groups=this.BaseGroups;
如果(组!=null)
{
int totalCount=0;
对于(int i=0;i/// <summary>
///     Provides a view that flattens groups into a list
///     This is used to avoid limitation that ListCollectionView has in .NET 4.0, if grouping is used then Virtialuzation would not work
///     It assumes some appropriate impelmentation in view(XAML) in order to support this way of grouping
///     Note: As implemented, it does not support nested grouping
///     Note: Only overriden properties and method behaves correctly, some of methods and properties related to selection of item might not work as expected and would require new implementation 
/// </summary>
public class FlatGroupListCollectionView : ListCollectionView
{
    /// <summary>
    /// Initializes a new instance of the <see cref="FlatGroupListCollectionView"/> class.
    /// </summary>
    /// <param name="list">A list used in this collection</param>
    public FlatGroupListCollectionView(IList list)
        : base(list)
    {
    }

    /// <summary>
    ///     This currently only supports one level of grouping
    ///     Returns CollectionViewGroups if the index matches a header
    ///     Otherwise, maps the index into the base range to get the actual item
    /// </summary>
    /// <param name="index">Index from which get an item</param>
    /// <returns>Item that was found on given index</returns>
    public override object GetItemAt(int index)
    {
        int delta = 0;
        ReadOnlyObservableCollection<object> groups = this.BaseGroups;
        if (groups != null)
        {
            int totalCount = 0;
            for (int i = 0; i < groups.Count; i++)
            {
                CollectionViewGroup group = groups[i] as CollectionViewGroup;
                if (group != null)
                {
                    if (index == totalCount)
                    {
                        return group;
                    }

                    delta++;
                    int numInGroup = group.ItemCount;
                    totalCount += numInGroup + 1;

                    if (index < totalCount)
                    {
                        break;
                    }
                }
            }
        }

        object item = base.GetItemAt(index - delta);
        return item;
    }

    /// <summary>
    ///     In the flat list, the base count is incremented by the number of groups since there are that many headers
    ///     To support nested groups, the nested groups must also be counted and added to the count
    /// </summary>
    public override int Count
    {
        get
        {
            int count = base.Count;

            if (this.BaseGroups != null)
            {
                count += this.BaseGroups.Count;
            }

            return count;
        }
    }

    /// <summary>
    ///     By returning null, we trick the generator into thinking  that we are not grouping
    ///     Thus, we avoid the default grouping code
    /// </summary>
    public override ReadOnlyObservableCollection<object> Groups
    {
        get
        {
            return null;
        }
    }

    /// <summary>
    ///     Gets the Groups collection from the base class
    /// </summary>
    private ReadOnlyObservableCollection<object> BaseGroups
    {
        get
        {
            return base.Groups;
        }
    }

    /// <summary>
    ///     DetectGroupHeaders is a way to get access to the containers by setting the value to true in the container style 
    ///     That way, the change handler can hook up to the container and provide a value for IsHeader
    /// </summary>
    public static readonly DependencyProperty DetectGroupHeadersProperty =
        DependencyProperty.RegisterAttached("DetectGroupHeaders", typeof(bool), typeof(FlatGroupListCollectionView), new FrameworkPropertyMetadata(false, OnDetectGroupHeaders));

    /// <summary>
    /// Gets the Detect Group Headers property
    /// </summary>
    /// <param name="obj">Dependency Object from which the property is get</param>
    /// <returns>Value of Detect Group Headers property</returns>
    public static bool GetDetectGroupHeaders(DependencyObject obj)
    {
        return (bool)obj.GetValue(DetectGroupHeadersProperty);
    }

    /// <summary>
    /// Sets the Detect Group Headers property
    /// </summary>
    /// <param name="obj">Dependency Object on which the property is set</param>
    /// <param name="value">Value to set to property</param>
    public static void SetDetectGroupHeaders(DependencyObject obj, bool value)
    {
        obj.SetValue(DetectGroupHeadersProperty, value);
    }

    /// <summary>
    ///     IsHeader can be used to style the container differently when it is a header
    ///     For instance, it can be disabled to prevent selection
    /// </summary>
    public static readonly DependencyProperty IsHeaderProperty =
        DependencyProperty.RegisterAttached("IsHeader", typeof(bool), typeof(FlatGroupListCollectionView), new FrameworkPropertyMetadata(false));

    /// <summary>
    /// Gets the Is Header property
    /// </summary>
    /// <param name="obj">Dependency Object from which the property is get</param>
    /// <returns>Value of Is Header property</returns>
    public static bool GetIsHeader(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsHeaderProperty);
    }

    /// <summary>
    /// Sets the Is Header property
    /// </summary>
    /// <param name="obj">Dependency Object on which the property is set</param>
    /// <param name="value">Value to set to property</param>
    public static void SetIsHeader(DependencyObject obj, bool value)
    {
        obj.SetValue(IsHeaderProperty, value);
    }

    /// <summary>
    /// Raises the System.Windows.Data.CollectionView.CollectionChanged event.
    /// </summary>
    /// <param name="args">The System.Collections.Specialized.NotifyCollectionChangedEventArgs object to pass to the event handler</param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
    {
        switch (args.Action)
        {
            case NotifyCollectionChangedAction.Add:
                {
                    int flatIndex = this.ConvertFromItemToFlat(args.NewStartingIndex, false);
                    int headerIndex = Math.Max(0, flatIndex - 1);
                    object o = this.GetItemAt(headerIndex);
                    CollectionViewGroup group = o as CollectionViewGroup;
                    if ((group != null) && (group.ItemCount == args.NewItems.Count))
                    {
                        // Notify that a header was added
                        base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new object[] { group }, headerIndex));
                    }

                    base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, args.NewItems, flatIndex));
                }

                break;

            case NotifyCollectionChangedAction.Remove:
                // TODO: Implement this action
                break;

            case NotifyCollectionChangedAction.Move:
                // TODO: Implement this action
                break;

            case NotifyCollectionChangedAction.Replace:
                // TODO: Implement this action
                break;

            default:
                base.OnCollectionChanged(args);
                break;
        }
    }

    /// <summary>
    /// Sets the specified item to be the System.Windows.Data.CollectionView.CurrentItem in the view
    /// This is an override of base method, an item index is get first and its needed to convert that index to flat version which includes groups
    /// Then adjusted version of MoveCurrentToPosition base method is called
    /// </summary>
    /// <param name="item">The item to set as the System.Windows.Data.CollectionView.CurrentItem</param>
    /// <returns>true if the resulting System.Windows.Data.CollectionView.CurrentItem is within the view; otherwise, false</returns>
    public override bool MoveCurrentTo(object item)
    {
        int index = this.IndexOf(item);

        int newIndex = this.ConvertFromItemToFlat(index, false);

        return this.MoveCurrentToPositionBase(newIndex);
    }

    /// <summary>
    /// Sets the item at the specified index to be the System.Windows.Data.CollectionView.CurrentItem in the view
    /// This is an override of base method, Its called when user selects new item from this collection
    /// A delta is get of which is the possition shifted because of groups and we shift this position by this delta and then base method is called
    /// </summary>
    /// <param name="position">The index to set the System.Windows.Data.CollectionView.CurrentItem to</param>
    /// <returns>true if the resulting System.Windows.Data.CollectionView.CurrentItem is an item within the view; otherwise, false</returns>
    public override bool MoveCurrentToPosition(int position)
    {
        int delta = this.GetDelta(position);

        int newPosition = position - delta;

        return base.MoveCurrentToPosition(newPosition);
    }

    private static void OnDetectGroupHeaders(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // This assumes that a container will not change between being a header and not
        // If using ContainerRecycling this may not be the case
        ((FrameworkElement)d).Loaded += OnContainerLoaded;
    }

    private static void OnContainerLoaded(object sender, RoutedEventArgs e)
    {
        FrameworkElement element = (FrameworkElement)sender;
        element.Loaded -= OnContainerLoaded; // If recycling, remove this line

        // CollectionViewGroup is the type of the header in this sample
        // Add more types or change the type as necessary
        if (element.DataContext is CollectionViewGroup)
        {
            SetIsHeader(element, true);
        }
    }

    private int ConvertFromItemToFlat(int index, bool removed)
    {
        ReadOnlyObservableCollection<object> groups = this.BaseGroups;
        if (groups != null)
        {
            int start = 1;
            for (int i = 0; i < groups.Count; i++)
            {
                CollectionViewGroup group = groups[i] as CollectionViewGroup;
                if (group != null)
                {
                    index++;
                    int end = start + group.ItemCount;

                    if ((start <= index) && ((!removed && index < end) || (removed && index <= end)))
                    {
                        break;
                    }

                    start = end + 1;
                }
            }
        }

        return index;
    }

    /// <summary>
    /// Move <seealso cref="CollectionView.CurrentItem"/> to the item at the given index.
    /// This is a replacement for base method
    /// </summary>
    /// <param name="position">Move CurrentItem to this index</param>
    /// <returns>true if <seealso cref="CollectionView.CurrentItem"/> points to an item within the view.</returns>
    private bool MoveCurrentToPositionBase(int position)
    {
        // VerifyRefreshNotDeferred was removed
        bool result = false;

        // Instead of property InternalCount we use Count property
        if (position < -1 || position > this.Count)
        {
            throw new ArgumentOutOfRangeException("position");
        }

        if (position != this.CurrentPosition || !this.IsCurrentInSync)
        {
            // Instead of property InternalCount we use Count property from this class
            // Instead of InternalItemAt we use GetItemAt from this class
            object proposedCurrentItem = (0 <= position && position < this.Count) ? this.GetItemAt(position) : null;

            // ignore moves to the placeholder
            if (proposedCurrentItem != CollectionView.NewItemPlaceholder)
            {
                if (this.OKToChangeCurrent())
                {
                    bool oldIsCurrentAfterLast = this.IsCurrentAfterLast;
                    bool oldIsCurrentBeforeFirst = this.IsCurrentBeforeFirst;

                    this.SetCurrent(proposedCurrentItem, position);

                    this.OnCurrentChanged();

                    // notify that the properties have changed.
                    if (this.IsCurrentAfterLast != oldIsCurrentAfterLast)
                    {
                        this.OnPropertyChanged(PropertySupport.ExtractPropertyName(() => this.IsCurrentAfterLast));
                    }

                    if (this.IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
                    {
                        this.OnPropertyChanged(PropertySupport.ExtractPropertyName(() => this.IsCurrentBeforeFirst));
                    }

                    this.OnPropertyChanged(PropertySupport.ExtractPropertyName(() => this.CurrentPosition));
                    this.OnPropertyChanged(PropertySupport.ExtractPropertyName(() => this.CurrentItem));

                    result = true;
                }
            }
        }

        // Instead of IsCurrentInView we return result 
        return result;
    }

    private int GetDelta(int index)
    {
        int delta = 0;
        ReadOnlyObservableCollection<object> groups = this.BaseGroups;
        if (groups != null)
        {
            int totalCount = 0;
            for (int i = 0; i < groups.Count; i++)
            {
                CollectionViewGroup group = groups[i] as CollectionViewGroup;
                if (group != null)
                {
                    if (index == totalCount)
                    {
                        break;
                    }

                    delta++;
                    int numInGroup = group.ItemCount;
                    totalCount += numInGroup + 1;

                    if (index < totalCount)
                    {
                        break;
                    }
                }
            }
        }

        return delta;
    }

    /// <summary>
    /// Helper to raise a PropertyChanged event
    /// </summary>
    /// <param name="propertyName">Name of the property</param>
    private void OnPropertyChanged(string propertyName)
    {
        base.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
}