VirtualizingTilePanel背景滚动

时间:2013-07-23 20:16:40

标签: c# wpf listview virtualization

我正在尝试创建一个看起来像书架的视图,其中的项目位于带有虚拟化的架子上。我通过向VirtualizingTilePanel添加ListView课程来实现这一目标。我正在使用的课程来自Dan Crevier的博客: http://blogs.msdn.com/b/dancre/archive/2006/02/16/implementing-a-virtualizingpanel-part-4-the-goods.aspx

我正在将VirtualizingTilePanel和背景添加到我的ListView,如下所示:

<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <local:VirtualizingTilePanel>
            <local:VirtualizingTilePanel.Background>
                <ImageBrush ImageSource="..\Images\bookshelf.png" AlignmentX="Left" AlignmentY="Top" TileMode="Tile" Stretch="None" ViewportUnits="Absolute" Viewport="0,0,319,203" />
            </local:VirtualizingTilePanel.Background>
        </local:VirtualizingTilePanel>
    </ItemsPanelTemplate>
</ListView.ItemsPanel>

我遇到的问题是背景仅填充原始可见区域。一旦我开始向下滚动,背景向上移动(就像我想要的那样),但进入视野的背景只是白色。

如何让背景填满整个可滚动区域?

希望这是有道理的,我可以根据需要添加图片。

修改

我的VirtualizingTilePanel课程:

class VirtualizingTilePanel : VirtualizingPanel, IScrollInfo
{
    public VirtualizingTilePanel()
    {
        // For use in the IScrollInfo implementation
        this.RenderTransform = _trans;


    }

    // Dependency property that controls the size of the child elements
    public static readonly DependencyProperty ChildSizeProperty
       = DependencyProperty.RegisterAttached("ChildSize", typeof(double), typeof(VirtualizingTilePanel),
          new FrameworkPropertyMetadata(168.0d, FrameworkPropertyMetadataOptions.AffectsMeasure |
          FrameworkPropertyMetadataOptions.AffectsArrange));


    // Accessor for the child size dependency property
    public double ChildSize
    {
        get { return (double)GetValue(ChildSizeProperty); }
        set { SetValue(ChildSizeProperty, value); }
    }

    /// <summary>
    /// Measure the children
    /// </summary>
    /// <param name="availableSize">Size available</param>
    /// <returns>Size desired</returns>
    IItemContainerGenerator generator;
    protected override Size MeasureOverride(Size availableSize)
    {
        UpdateScrollInfo(availableSize);

        // Figure out range that's visible based on layout algorithm
        int firstVisibleItemIndex, lastVisibleItemIndex;
        GetVisibleRange(out firstVisibleItemIndex, out lastVisibleItemIndex);

        // We need to access InternalChildren before the generator to work around a bug
        UIElementCollection children = this.InternalChildren;
        generator = this.ItemContainerGenerator;

        // Get the generator position of the first visible data item
        GeneratorPosition startPos = generator.GeneratorPositionFromIndex(firstVisibleItemIndex);

        // Get index where we'd insert the child for this position. If the item is realized
        // (position.Offset == 0), it's just position.Index, otherwise we have to add one to
        // insert after the corresponding child
        int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;

        using (generator.StartAt(startPos, GeneratorDirection.Forward, true))
        {
            for (int itemIndex = firstVisibleItemIndex; itemIndex <= lastVisibleItemIndex; ++itemIndex, ++childIndex)
            {
                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);
                }
                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");
                }

                // Measurements will depend on layout algorithm
                child.Measure(GetChildSize());
            }
        }

        // Note: this could be deferred to idle time for efficiency
        CleanUpItems(firstVisibleItemIndex, lastVisibleItemIndex);

        return availableSize;
    }

    /// <summary>
    /// Arrange the children
    /// </summary>
    /// <param name="finalSize">Size available</param>
    /// <returns>Size used</returns>
    protected override Size ArrangeOverride(Size finalSize)
    {
        IItemContainerGenerator generator = this.ItemContainerGenerator;

        UpdateScrollInfo(finalSize);

        for (int i = 0; i < this.Children.Count; i++)
        {
            UIElement child = this.Children[i];

            // Map the child offset to an item offset
            int itemIndex = generator.IndexFromGeneratorPosition(new GeneratorPosition(i, 0));

            ArrangeChild(itemIndex, child, finalSize);
        }

        return finalSize;
    }

    /// <summary>
    /// Revirtualize items that are no longer visible
    /// </summary>
    /// <param name="minDesiredGenerated">first item index that should be visible</param>
    /// <param name="maxDesiredGenerated">last item index that should be visible</param>
    private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
    {
        UIElementCollection children = this.InternalChildren;
        IItemContainerGenerator generator = this.ItemContainerGenerator;

        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)
            {
                generator.Remove(childGeneratorPos, 1);
                RemoveInternalChildRange(i, 1);
            }
        }
    }

    /// <summary>
    /// When items are removed, remove the corresponding UI if necessary
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
    {
        switch (args.Action)
        {
            case NotifyCollectionChangedAction.Remove:
            case NotifyCollectionChangedAction.Replace:
            case NotifyCollectionChangedAction.Move:
                RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
                break;
        }
    }

    #region Layout specific code
    // I've isolated the layout specific code to this region. If you want to do something other than tiling, this is
    // where you'll make your changes

    int width = 100;
    int height = 203;

    /// <summary>
    /// Calculate the extent of the view based on the available size
    /// </summary>
    /// <param name="availableSize">available size</param>
    /// <param name="itemCount">number of data items</param>
    /// <returns></returns>
    private Size CalculateExtent(Size availableSize, int itemCount)
    {
        int childrenPerRow = CalculateChildrenPerRow(availableSize);

        // See how big we are
        return new Size(childrenPerRow * width,
            height * Math.Ceiling((double)itemCount / childrenPerRow));
    }

    /// <summary>
    /// Get the range of children that are visible
    /// </summary>
    /// <param name="firstVisibleItemIndex">The item index of the first visible item</param>
    /// <param name="lastVisibleItemIndex">The item index of the last visible item</param>
    private void GetVisibleRange(out int firstVisibleItemIndex, out int lastVisibleItemIndex)
    {
        int childrenPerRow = CalculateChildrenPerRow(_extent);

        firstVisibleItemIndex = (int) Math.Floor(_offset.Y / height) * childrenPerRow;
        lastVisibleItemIndex = (int) Math.Ceiling((_offset.Y + _viewport.Height) / height) * childrenPerRow - 1;

        ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
        int itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;
        if (lastVisibleItemIndex >= itemCount)
            lastVisibleItemIndex = itemCount-1;

    }

    /// <summary>
    /// Get the size of the children. We assume they are all the same
    /// </summary>
    /// <returns>The size</returns>
    private Size GetChildSize()
    {
        //return new Size(this.ChildSize, this.ChildSize);
        return new Size(width, height);
    }

    /// <summary>
    /// Position a child
    /// </summary>
    /// <param name="itemIndex">The data item index of the child</param>
    /// <param name="child">The element to position</param>
    /// <param name="finalSize">The size of the panel</param>
    private void ArrangeChild(int itemIndex, UIElement child, Size finalSize)
    {
        int childrenPerRow = CalculateChildrenPerRow(finalSize);

        int row = itemIndex / childrenPerRow;
        int column = itemIndex % childrenPerRow;

        child.Arrange(new Rect(column * width, row * height, width, height));
    }

    /// <summary>
    /// Helper function for tiling layout
    /// </summary>
    /// <param name="availableSize">Size available</param>
    /// <returns></returns>
    private int CalculateChildrenPerRow(Size availableSize)
    {
        // Figure out how many children fit on each row
        int childrenPerRow;
        if (availableSize.Width == Double.PositiveInfinity)
            childrenPerRow = this.Children.Count;
        else
            childrenPerRow = Math.Max(1, (int)Math.Floor(availableSize.Width / width));
        return childrenPerRow;
    }

    #endregion

    #region IScrollInfo Members
    private Size _extent = new Size(0, 0);
    private Size _viewport = new Size(0, 0);
    private Point _offset;
    private TranslateTransform _trans = new TranslateTransform();

    private void UpdateScrollInfo(Size availableSize)
    {
        // See how many items there are
        ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
        int itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;

        Size extent = CalculateExtent(availableSize, itemCount);


        // Update extent
        if (extent != _extent)
        {
            _extent = extent;

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

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

    private bool _canHScroll = false;
    public bool CanHorizontallyScroll
    {
        get { return _canHScroll; }
        set { _canHScroll = value; }
    }

    private bool _canVScroll = false;
    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 Rect MakeVisible(Visual visual, Rect rectangle)
    {
        return new Rect();
    }

    public void MouseWheelDown()
    {
        PageDown();
    }

    public void MouseWheelLeft()
    {
        throw new InvalidOperationException();
    }

    public void MouseWheelRight()
    {
        throw new InvalidOperationException();
    }

    public void MouseWheelUp()
    {
        PageUp();
    }

    public void PageDown()
    {
        SetVerticalOffset(VerticalOffset + _viewport.Height * 0.1);
    }

    public void PageLeft()
    {
        SetHorizontalOffset(HorizontalOffset - _viewport.Width * 0.1);
    }

    public void PageRight()
    {
        SetHorizontalOffset(HorizontalOffset + _viewport.Width * 0.8);
    }

    public void PageUp()
    {
        SetVerticalOffset(VerticalOffset - _viewport.Height * 0.1);
    }

    private ScrollViewer _owner;
    public ScrollViewer ScrollOwner
    {
        get { return _owner; }
        set { _owner = value; }
    }

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

        _offset.X = offset;

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

        InvalidateMeasure();
    }

    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();

        _trans.Y = -offset;

        InvalidateMeasure();
    }

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

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

    public void LineUp()
    {
        SetVerticalOffset(this.VerticalOffset - 10);
    }

    public void LineDown()
    {
        SetVerticalOffset(this.VerticalOffset + 10);
    }

    public void LineLeft()
    {
        throw new InvalidOperationException();
    }

    public void LineRight()
    {
        throw new InvalidOperationException();
    }

    #endregion



    #region helper data structures

    class ItemAbstraction
    {
        public ItemAbstraction(WrapPanelAbstraction panel, int index)
        {
            _panel = panel;
            _index = index;
        }

        WrapPanelAbstraction _panel;

        public readonly int _index;

        int _sectionIndex = -1;
        public int SectionIndex
        {
            get
            {
                if (_sectionIndex == -1)
                {
                    return _index % _panel._averageItemsPerSection - 1;
                }
                return _sectionIndex;
            }
            set
            {
                if (_sectionIndex == -1)
                    _sectionIndex = value;
            }
        }

        int _section = -1;
        public int Section
        {
            get
            {
                if (_section == -1)
                {
                    return _index / _panel._averageItemsPerSection;
                }
                return _section;
            }
            set
            {
                if (_section == -1)
                    _section = value;
            }
        }
    }

    class WrapPanelAbstraction : IEnumerable<ItemAbstraction>
    {
        public WrapPanelAbstraction(int itemCount)
        {
            List<ItemAbstraction> items = new List<ItemAbstraction>(itemCount);
            for (int i = 0; i < itemCount; i++)
            {
                ItemAbstraction item = new ItemAbstraction(this, i);
                items.Add(item);
            }

            Items = new ReadOnlyCollection<ItemAbstraction>(items);
            _averageItemsPerSection = itemCount;
            _itemCount = itemCount;
        }

        public readonly int _itemCount;
        public int _averageItemsPerSection;
        private int _currentSetSection = -1;
        private int _currentSetItemIndex = -1;
        private int _itemsInCurrentSecction = 0;
        private object _syncRoot = new object();

        public int SectionCount
        {
            get
            {
                int ret = _currentSetSection + 1;
                if (_currentSetItemIndex + 1 < Items.Count)
                {
                    int itemsLeft = Items.Count - _currentSetItemIndex;
                    ret += itemsLeft / _averageItemsPerSection + 1;
                }
                return ret;
            }
        }

        private ReadOnlyCollection<ItemAbstraction> Items { get; set; }

        public void SetItemSection(int index, int section)
        {
            lock (_syncRoot)
            {
                if (section <= _currentSetSection + 1 && index == _currentSetItemIndex + 1)
                {
                    _currentSetItemIndex++;
                    Items[index].Section = section;
                    if (section == _currentSetSection + 1)
                    {
                        _currentSetSection = section;
                        if (section > 0)
                        {
                            _averageItemsPerSection = (index) / (section);
                        }
                        _itemsInCurrentSecction = 1;
                    }
                    else
                        _itemsInCurrentSecction++;
                    Items[index].SectionIndex = _itemsInCurrentSecction - 1;
                }
            }
        }

        public ItemAbstraction this[int index]
        {
            get { return Items[index]; }
        }

        #region IEnumerable<ItemAbstraction> Members

        public IEnumerator<ItemAbstraction> GetEnumerator()
        {
            return Items.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }

    #endregion
}

1 个答案:

答案 0 :(得分:0)

我的自定义面板实现IScrollInfo出现了类似的问题。在我的实现中,背景根本没有移动(我认为你的背景因为你正在设置RenderTransform而移动)。我最终在SetHorizontalOffset / SetVerticalOffset中手动更新了指定画笔上的视口,如下所示:

    public void SetHorizontalOffset(double offset)
    {       
        offsetX_ = offset;
        ((TileBrush)Background).Viewport = new Rect(-offsetX_, -offsetY_, width_, height_);
        ((TileBrush)Background).ViewportUnits = BrushMappingMode.Absolute;
    }

首先需要在画笔项目上设置ViewboxUnits = BrushMappingMode.Absolute