WPF VirtualizingPanel作为Listview ItemsPanel:如何管理未实现的项目选择/取消选择?

时间:2015-09-09 10:38:58

标签: wpf listview

我目前正在开发一种VirtualizedWrapPanel,用作ListView中的ItemsPanel。

在关注this guy's instructions之后,从代码项目中发现的这个人的实现中大量借用,但我没有发布链接的声誉,所以很抱歉...,我有一些很好的形成完全正确我需要什么。

项目大小是固定的,因此滚动是基于像素的。方向始终是水平的。

ListView:

        <ListView Name="lv" 
                  ItemsSource="{Binding CV}"   
                  IsSynchronizedWithCurrentItem="True"
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled">

            <i:Interaction.Behaviors>
                <local:ScrollToSelectionListViewBehavior/> <!-- Behavior calling ScrollIntoView whenever the selection changes -->
                <local:ListViewSelectedItemsBehavior SelectedItems="{Binding SelectedItems}"/> <!-- Behavior exposing the attached ListView's SelectedItems array -->
            </i:Interaction.Behaviors>

            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel  Height="100" Width="100" Orientation="Vertical">
                        <Border  BorderBrush="Black" BorderThickness="1" CornerRadius="5" Width="90" Height="90">
                            <TextBlock Text ="{Binding ItemText}" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center" />
                        </Border>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>

            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <local:VirtualizingWrapPanel ItemHeight="100" ItemWidth="110" />
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>

            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
                </Style>
            </ListView.ItemContainerStyle>

        </ListView>

本地:VirtualizingWrapPanel:

 public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
{

    private ScrollViewer _owner;
    private const bool _canHScroll = false;
    private bool _canVScroll = false;
    private Size _extent = new Size(0, 0);
    private Size _viewport = new Size(0, 0);
    private Point _offset;
    UIElementCollection _children;
    ItemsControl _itemsControl;
    IItemContainerGenerator _generator;

    Dictionary<UIElement, Rect> _realizedChildLayout = new Dictionary<UIElement, Rect>();

    #region Properties

    private Size ChildSlotSize
    {
        get { return new Size(ItemWidth, ItemHeight); }
    }

    #endregion

    #region Dependency Properties

    [TypeConverter(typeof(LengthConverter))]
    public double ItemHeight
    {
        get { return (double)base.GetValue(ItemHeightProperty); }
        set { base.SetValue(ItemHeightProperty, value); }
    }

    [TypeConverter(typeof(LengthConverter))]
    public double ItemWidth
    {
        get { return (double)base.GetValue(ItemWidthProperty); }
        set { base.SetValue(ItemWidthProperty, value); }
    }


    public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
    public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));

    private int LineCapacity
    { get { return Math.Max((_viewport.Width != 0) ? (int)(_viewport.Width / ItemWidth) : 0, 1); } }

    private int LinesCount
    { get { return (ItemsCount > 0) ? ItemsCount / LineCapacity : 0 ; } }

    private int ItemsCount
    { get { return _itemsControl.Items.Count; } }

    public int FirstVisibleLine
    { get { return (int)(_offset.Y / ItemHeight); } }

    public int FirstVisibleItemVPos
    { get { return (int)((FirstVisibleLine * ItemHeight) - _offset.Y); } }

    public int FirstVisibleIndex
    { get {  return (FirstVisibleLine * LineCapacity); } }

    #endregion


    #region VirtualizingPanel overrides
    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);
        _itemsControl = ItemsControl.GetItemsOwner(this);
        _children = InternalChildren;
        _generator = ItemContainerGenerator;
        this.SizeChanged += new SizeChangedEventHandler(this.Resizing);
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        if (_itemsControl == null || _itemsControl.Items.Count == 0)
            return availableSize;

        if (availableSize != _viewport)
        {
            _viewport = availableSize;
            if (_owner != null)
                _owner.InvalidateScrollInfo();
        }

        Size childSize = new Size(ItemWidth, ItemHeight);
        Size extent = new Size(availableSize.Width, LinesCount * ItemHeight);

        if (extent != _extent)
        {
            _extent = extent;
            if (_owner != null)
                _owner.InvalidateScrollInfo();
        }



        foreach (UIElement child in this.InternalChildren)
        {
            child.Measure(childSize);
        }

        _realizedChildLayout.Clear();

        Size realizedFrameSize = availableSize;


        int firstVisibleIndex = FirstVisibleIndex;

        GeneratorPosition startPos = _generator.GeneratorPositionFromIndex(firstVisibleIndex);

        int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
        int current = firstVisibleIndex;
        using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
        {
            bool stop = false;
            double currentX = 0;
            double currentY = FirstVisibleItemVPos;

            while (current < ItemsCount)
            {
                bool newlyRealized;

                // Get or create the child                    
                UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
                if (newlyRealized)
                {
                    // Figure out if we need to insert the child at the end or somewhere in the middle
                    if (childIndex >= _children.Count)
                    {
                        base.AddInternalChild(child);
                    }
                    else
                    {
                        base.InsertInternalChild(childIndex, child);
                    }
                    _generator.PrepareItemContainer(child);
                    child.Measure(ChildSlotSize);
                }
                else
                {
                    // The child has already been created, let's be sure it's in the right spot
                    Debug.Assert(child == _children[childIndex], "Wrong child was generated");
                }
                childSize = child.DesiredSize;
                Rect childRect = new Rect(new Point(currentX, currentY), childSize);

                if (childRect.Right > realizedFrameSize.Width) //wrap to a new line
                {
                    currentY = currentY + ItemHeight;
                    currentX = 0;
                    childRect.X = currentX;
                    childRect.Y = currentY;
                }

                if (currentY > realizedFrameSize.Height)
                    stop = true;
                currentX = childRect.Right;

                _realizedChildLayout.Add(child, childRect);

                if (stop)
                    break;

                current++;
                childIndex++;
            }
        }
        CleanUpItems(firstVisibleIndex, current - 1);

        return availableSize;
    }
    public void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
    {
        for (int i = _children.Count - 1; i >= 0; i--)
        {
            GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
            int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
            if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
            {
                //var c = _children[i] as ListViewItem;
                //if(c!= null && c.IsSelected)
                //{

                //}
                _generator.Remove(childGeneratorPos, 1);
                RemoveInternalChildRange(i, 1);
            }
        }
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        if (finalSize != _viewport)
        {
            _viewport = finalSize;
            if (_owner != null)
                _owner.InvalidateScrollInfo();
        }

        Size childSize = new Size(ItemWidth, ItemHeight);
        Size extent = new Size(finalSize.Width, LinesCount * ItemHeight);

        if (extent != _extent)
        {
            _extent = extent;
            if (_owner != null)
                _owner.InvalidateScrollInfo();
        }

        if (_children != null)
        {
            foreach (UIElement child in _children)
            {
                var layoutInfo = _realizedChildLayout[child];
                child.Arrange(layoutInfo);
            }
        }
        return finalSize;
    }

    protected override void BringIndexIntoView(int index)
    {
        SetVerticalOffset((index / LineCapacity) * ItemHeight);
    }

    protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
    {
        base.OnItemsChanged(sender, args);

        _offset.X = 0;
        _offset.Y = 0;

        switch (args.Action)
        {
            case NotifyCollectionChangedAction.Remove:
            case NotifyCollectionChangedAction.Replace:
                RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
                break;
            case NotifyCollectionChangedAction.Move:
                RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
                break;
        }
    }

    #endregion


    #region EventHandlers
    public void Resizing(object sender, EventArgs e)
    {
        var args = e as SizeChangedEventArgs;
        if(args.WidthChanged)
        {
            int lineCapacity = LineCapacity;
            int previousLineCapacity = (int)(args.PreviousSize.Width / ItemWidth);
            if (previousLineCapacity != lineCapacity)
            {
                int previousFirstItem = ((int)(_offset.Y / ItemHeight) <= 0) ? 0 : ((int)(_offset.Y / ItemHeight) * previousLineCapacity);
                BringIndexIntoView(previousFirstItem);
            }
        }
        if (_viewport.Width != 0)
        {
            MeasureOverride(_viewport);
        }
    }


    #endregion


    #region IScrollInfo Implementation
    public ScrollViewer ScrollOwner
    {
        get { return _owner; }
        set { _owner = value; }
    }

    public bool CanHorizontallyScroll
    {
        get { return false; }
        set { if (value == true) throw (new ArgumentException("VirtualizingWrapPanel does not support Horizontal scrolling")); }
    }

    public bool CanVerticallyScroll
    {
        get { return _canVScroll; }
        set { _canVScroll = value; }
    }

    public double ExtentHeight
    {
        get { return _extent.Height;}
    }

    public double ExtentWidth
    {
        get { return _extent.Width; }
    }

    public double HorizontalOffset
    {
        get { return _offset.X; }
    }

    public double VerticalOffset
    {
        get { return _offset.Y; }
    }

    public double ViewportHeight
    {
        get { return _viewport.Height; }
    }

    public double ViewportWidth
    {
        get { return _viewport.Width; }
    }



    public Rect MakeVisible(Visual visual, Rect rectangle)
    {
        var gen = (ItemContainerGenerator)_generator.GetItemContainerGeneratorForPanel(this);
        var element = (UIElement)visual;
        int itemIndex = gen.IndexFromContainer(element);
        while (itemIndex == -1)
        {
            element = (UIElement)VisualTreeHelper.GetParent(element);
            itemIndex = gen.IndexFromContainer(element);
        }

        Rect elementRect = _realizedChildLayout[element];
        if (elementRect.Bottom > ViewportHeight)
        { 
            double translation = elementRect.Bottom - ViewportHeight;
            _offset.Y += translation;
        }
        else if (elementRect.Top < 0)
        { 
            double translation = elementRect.Top;
            _offset.Y += translation;
        }
        InvalidateMeasure();
        return elementRect;
    }

    public void LineDown()
    {
        SetVerticalOffset(VerticalOffset + 50);
    }

    public void LineUp()
    {
        SetVerticalOffset(VerticalOffset - 50);
    }

    public void MouseWheelDown()
    {
        SetVerticalOffset(VerticalOffset + 50);
    }

    public void MouseWheelUp()
    {
        SetVerticalOffset(VerticalOffset - 50);
    }

    public void PageDown()
    {
        int fullyVisibleLines = (int)(_viewport.Height / ItemHeight);
        SetVerticalOffset(VerticalOffset + (fullyVisibleLines * ItemHeight));
    }

    public void PageUp()
    {
        int fullyVisibleLines = (int)(_viewport.Height / ItemHeight);
        SetVerticalOffset(VerticalOffset - (fullyVisibleLines * ItemHeight));
    }

    public void SetVerticalOffset(double offset)
    {
        if (offset < 0 || _viewport.Height >= _extent.Height)
        {
            offset = 0;
        }
        else
        {
            if (offset + _viewport.Height >= _extent.Height)
            {
                offset = _extent.Height - _viewport.Height;
            }
        }

        _offset.Y = offset;

        if (_owner != null)
            _owner.InvalidateScrollInfo();

        InvalidateMeasure();
    }

    public void LineLeft()  { throw new NotImplementedException(); }
    public void LineRight() { throw new NotImplementedException(); }
    public void MouseWheelLeft() { throw new NotImplementedException(); }
    public void MouseWheelRight() { throw new NotImplementedException(); }
    public void PageLeft() { throw new NotImplementedException(); }
    public void PageRight() { throw new NotImplementedException(); }
    public void SetHorizontalOffset(double offset) { throw new NotImplementedException(); }
    #endregion

    #region methods


    #endregion
}

现在我的问题是:项目选择应该总是取消选择以前选择的项目,当使用普通的WrapPanel时,在新选择的ListViewItem的IsSelected设置为true之前,先前选择的ListViewItem的IsSelected属性总是设置为false。

当我之前选择的项目不再实现时(当它在视口中不可见时),我的VirtualizingPanel不会发生这种取消选择,所以我最终会同时选择两个或多个所选项目并且面板的行为变得非常奇怪。有时它甚至会陷入无限循环,两个选定的项目在永无止境的战斗中相互瞄准。

我搜索了一下这个问题的解决方案,但我真的不知道从哪里开始或搜索什么。

如果你想试验它,这是一个test project。 感谢

1 个答案:

答案 0 :(得分:0)

我通过阻止所选项目的虚拟化找到了一种方法。现在控件表现正常。

            if (_children != null)
        {
            foreach (UIElement child in _children)
            {
                if (child.IsEnabled)
                { 
                    var layoutInfo = _realizedChildLayout[child];
                    child.Arrange(layoutInfo);
                }
            }
        }

在ArrangeOverride中:

 while (current < ItemsCount)
            {
                bool newlyRealized;

                // Get or create the child                    
                UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
                child.IsEnabled = true;

在MeasureOverride中:

db.Table1.include("Table2").include("Table3").orderBy("it.Table1Field.Table3FieldName").toArray();

我仍然很想知道是否有更好的方法。与此同时,这样做。 顺便说一下,如果有人想要虚拟化包装,我会使用此修复程序更新测试项目。