WPF Virtualizing Panel

都是国外的偷笑


VirtualizingWrapPanel:

public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
{
    #region Fields

    UIElementCollection _children;
    ItemsControl _itemsControl;
    IItemContainerGenerator _generator;
    private Point _offset = new Point(0, 0);
    private Size _extent = new Size(0, 0);
    private Size _viewport = new Size(0, 0);
    private int firstIndex = 0;
    private Size childSize;
    private Size _pixelMeasuredViewport = new Size(0, 0);
    Dictionary<UIElement, Rect> _realizedChildLayout = new Dictionary<UIElement, Rect>();
    WrapPanelAbstraction _abstractPanel;


    #endregion

    #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 Orientation Orientation
    {
        get { return (Orientation)GetValue(OrientationProperty); }
        set { SetValue(OrientationProperty, 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));
    public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal));

    #endregion

    #region Methods

    public void SetFirstRowViewItemIndex(int index)
    {
        SetVerticalOffset((index) / Math.Floor((_viewport.Width) / childSize.Width));
        SetHorizontalOffset((index) / Math.Floor((_viewport.Height) / childSize.Height));
    }

    private void Resizing(object sender, EventArgs e)
    {
        if (_viewport.Width != 0)
        {
            int firstIndexCache = firstIndex;
            _abstractPanel = null;
            MeasureOverride(_viewport);
            SetFirstRowViewItemIndex(firstIndex);
            firstIndex = firstIndexCache;
        }
    }

    public int GetFirstVisibleSection()
    {
        int section;
        var maxSection = _abstractPanel.Max(x => x.Section);
        if (Orientation == Orientation.Horizontal)
        {
            section = (int)_offset.Y;
        }
        else
        {
            section = (int)_offset.X;
        }
        if (section > maxSection)
            section = maxSection;
        return section;
    }

    public int GetFirstVisibleIndex()
    {
        int section = GetFirstVisibleSection();
        var item = _abstractPanel.Where(x => x.Section == section).FirstOrDefault();
        if (item != null)
            return item._index;
        return 0;
    }

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

    private void ComputeExtentAndViewport(Size pixelMeasuredViewportSize, int visibleSections)
    {
        if (Orientation == Orientation.Horizontal)
        {
            _viewport.Height = visibleSections;
            _viewport.Width = pixelMeasuredViewportSize.Width;
        }
        else
        {
            _viewport.Width = visibleSections;
            _viewport.Height = pixelMeasuredViewportSize.Height;
        }

        if (Orientation == Orientation.Horizontal)
        {
            _extent.Height = _abstractPanel.SectionCount + ViewportHeight - 1;

        }
        else
        {
            _extent.Width = _abstractPanel.SectionCount + ViewportWidth - 1;
        }
        _owner.InvalidateScrollInfo();
    }

    private void ResetScrollInfo()
    {
        _offset.X = 0;
        _offset.Y = 0;
    }

    private int GetNextSectionClosestIndex(int itemIndex)
    {
        var abstractItem = _abstractPanel[itemIndex];
        if (abstractItem.Section < _abstractPanel.SectionCount - 1)
        {
            var ret = _abstractPanel.
                Where(x => x.Section == abstractItem.Section + 1).
                OrderBy(x => Math.Abs(x.SectionIndex - abstractItem.SectionIndex)).
                First();
            return ret._index;
        }
        else
            return itemIndex;
    }

    private int GetLastSectionClosestIndex(int itemIndex)
    {
        var abstractItem = _abstractPanel[itemIndex];
        if (abstractItem.Section > 0)
        {
            var ret = _abstractPanel.
                Where(x => x.Section == abstractItem.Section - 1).
                OrderBy(x => Math.Abs(x.SectionIndex - abstractItem.SectionIndex)).
                First();
            return ret._index;
        }
        else
            return itemIndex;
    }

    private void NavigateDown()
    {
        var gen = _generator.GetItemContainerGeneratorForPanel(this);
        UIElement selected = (UIElement)Keyboard.FocusedElement;
        int itemIndex = gen.IndexFromContainer(selected);
        int depth = 0;
        while (itemIndex == -1)
        {
            selected = (UIElement)VisualTreeHelper.GetParent(selected);
            itemIndex = gen.IndexFromContainer(selected);
            depth++;
        }
        DependencyObject next = null;
        if (Orientation == Orientation.Horizontal)
        {
            int nextIndex = GetNextSectionClosestIndex(itemIndex);
            next = gen.ContainerFromIndex(nextIndex);
            while (next == null)
            {
                SetVerticalOffset(VerticalOffset + 1);
                UpdateLayout();
                next = gen.ContainerFromIndex(nextIndex);
            }
        }
        else
        {
            if (itemIndex == _abstractPanel._itemCount - 1)
                return;
            next = gen.ContainerFromIndex(itemIndex + 1);
            while (next == null)
            {
                SetHorizontalOffset(HorizontalOffset + 1);
                UpdateLayout();
                next = gen.ContainerFromIndex(itemIndex + 1);
            }
        }
        while (depth != 0)
        {
            next = VisualTreeHelper.GetChild(next, 0);
            depth--;
        }
        (next as UIElement).Focus();
    }

    private void NavigateLeft()
    {
        var gen = _generator.GetItemContainerGeneratorForPanel(this);

        UIElement selected = (UIElement)Keyboard.FocusedElement;
        int itemIndex = gen.IndexFromContainer(selected);
        int depth = 0;
        while (itemIndex == -1)
        {
            selected = (UIElement)VisualTreeHelper.GetParent(selected);
            itemIndex = gen.IndexFromContainer(selected);
            depth++;
        }
        DependencyObject next = null;
        if (Orientation == Orientation.Vertical)
        {
            int nextIndex = GetLastSectionClosestIndex(itemIndex);
            next = gen.ContainerFromIndex(nextIndex);
            while (next == null)
            {
                SetHorizontalOffset(HorizontalOffset - 1);
                UpdateLayout();
                next = gen.ContainerFromIndex(nextIndex);
            }
        }
        else
        {
            if (itemIndex == 0)
                return;
            next = gen.ContainerFromIndex(itemIndex - 1);
            while (next == null)
            {
                SetVerticalOffset(VerticalOffset - 1);
                UpdateLayout();
                next = gen.ContainerFromIndex(itemIndex - 1);
            }
        }
        while (depth != 0)
        {
            next = VisualTreeHelper.GetChild(next, 0);
            depth--;
        }
        (next as UIElement).Focus();
    }

    private void NavigateRight()
    {
        var gen = _generator.GetItemContainerGeneratorForPanel(this);
        UIElement selected = (UIElement)Keyboard.FocusedElement;
        int itemIndex = gen.IndexFromContainer(selected);
        int depth = 0;
        while (itemIndex == -1)
        {
            selected = (UIElement)VisualTreeHelper.GetParent(selected);
            itemIndex = gen.IndexFromContainer(selected);
            depth++;
        }
        DependencyObject next = null;
        if (Orientation == Orientation.Vertical)
        {
            int nextIndex = GetNextSectionClosestIndex(itemIndex);
            next = gen.ContainerFromIndex(nextIndex);
            while (next == null)
            {
                SetHorizontalOffset(HorizontalOffset + 1);
                UpdateLayout();
                next = gen.ContainerFromIndex(nextIndex);
            }
        }
        else
        {
            if (itemIndex == _abstractPanel._itemCount - 1)
                return;
            next = gen.ContainerFromIndex(itemIndex + 1);
            while (next == null)
            {
                SetVerticalOffset(VerticalOffset + 1);
                UpdateLayout();
                next = gen.ContainerFromIndex(itemIndex + 1);
            }
        }
        while (depth != 0)
        {
            next = VisualTreeHelper.GetChild(next, 0);
            depth--;
        }
        (next as UIElement).Focus();
    }

    private void NavigateUp()
    {
        var gen = _generator.GetItemContainerGeneratorForPanel(this);
        UIElement selected = (UIElement)Keyboard.FocusedElement;
        int itemIndex = gen.IndexFromContainer(selected);
        int depth = 0;
        while (itemIndex == -1)
        {
            selected = (UIElement)VisualTreeHelper.GetParent(selected);
            itemIndex = gen.IndexFromContainer(selected);
            depth++;
        }
        DependencyObject next = null;
        if (Orientation == Orientation.Horizontal)
        {
            int nextIndex = GetLastSectionClosestIndex(itemIndex);
            next = gen.ContainerFromIndex(nextIndex);
            while (next == null)
            {
                SetVerticalOffset(VerticalOffset - 1);
                UpdateLayout();
                next = gen.ContainerFromIndex(nextIndex);
            }
        }
        else
        {
            if (itemIndex == 0)
                return;
            next = gen.ContainerFromIndex(itemIndex - 1);
            while (next == null)
            {
                SetHorizontalOffset(HorizontalOffset - 1);
                UpdateLayout();
                next = gen.ContainerFromIndex(itemIndex - 1);
            }
        }
        while (depth != 0)
        {
            next = VisualTreeHelper.GetChild(next, 0);
            depth--;
        }
        (next as UIElement).Focus();
    }


    #endregion

    #region Override

    protected override void OnKeyDown(KeyEventArgs e)
    {
        switch (e.Key)
        {
            case Key.Down:
                NavigateDown();
                e.Handled = true;
                break;
            case Key.Left:
                NavigateLeft();
                e.Handled = true;
                break;
            case Key.Right:
                NavigateRight();
                e.Handled = true;
                break;
            case Key.Up:
                NavigateUp();
                e.Handled = true;
                break;
            default:
                base.OnKeyDown(e);
                break;
        }
    }


    protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
    {
        base.OnItemsChanged(sender, args);
        _abstractPanel = null;
        ResetScrollInfo();
    }

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

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

        _pixelMeasuredViewport = availableSize;

        _realizedChildLayout.Clear();

        Size realizedFrameSize = availableSize;

        int itemCount = _itemsControl.Items.Count;
        int firstVisibleIndex = GetFirstVisibleIndex();

        GeneratorPosition startPos = _generator.GeneratorPositionFromIndex(firstVisibleIndex);

        int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
        int current = firstVisibleIndex;
        int visibleSections = 1;
        using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
        {
            bool stop = false;
            bool isHorizontal = Orientation == Orientation.Horizontal;
            double currentX = 0;
            double currentY = 0;
            double maxItemSize = 0;
            int currentSection = GetFirstVisibleSection();
            while (current < itemCount)
            {
                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 (isHorizontal)
                {
                    maxItemSize = Math.Max(maxItemSize, childRect.Height);
                    if (childRect.Right > realizedFrameSize.Width) //wrap to a new line
                    {
                        currentY = currentY + maxItemSize;
                        currentX = 0;
                        maxItemSize = childRect.Height;
                        childRect.X = currentX;
                        childRect.Y = currentY;
                        currentSection++;
                        visibleSections++;
                    }
                    if (currentY > realizedFrameSize.Height)
                        stop = true;
                    currentX = childRect.Right;
                }
                else
                {
                    maxItemSize = Math.Max(maxItemSize, childRect.Width);
                    if (childRect.Bottom > realizedFrameSize.Height) //wrap to a new column
                    {
                        currentX = currentX + maxItemSize;
                        currentY = 0;
                        maxItemSize = childRect.Width;
                        childRect.X = currentX;
                        childRect.Y = currentY;
                        currentSection++;
                        visibleSections++;
                    }
                    if (currentX > realizedFrameSize.Width)
                        stop = true;
                    currentY = childRect.Bottom;
                }
                _realizedChildLayout.Add(child, childRect);
                _abstractPanel.SetItemSection(current, currentSection);

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

        ComputeExtentAndViewport(availableSize, visibleSections);

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

    #endregion

    #region IScrollInfo Members

    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 void LineDown()
    {
        if (Orientation == Orientation.Vertical)
            SetVerticalOffset(VerticalOffset + 20);
        else
            SetVerticalOffset(VerticalOffset + 1);
    }

    public void LineLeft()
    {
        if (Orientation == Orientation.Horizontal)
            SetHorizontalOffset(HorizontalOffset - 20);
        else
            SetHorizontalOffset(HorizontalOffset - 1);
    }

    public void LineRight()
    {
        if (Orientation == Orientation.Horizontal)
            SetHorizontalOffset(HorizontalOffset + 20);
        else
            SetHorizontalOffset(HorizontalOffset + 1);
    }

    public void LineUp()
    {
        if (Orientation == Orientation.Vertical)
            SetVerticalOffset(VerticalOffset - 20);
        else
            SetVerticalOffset(VerticalOffset - 1);
    }

    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);
        }
        int section = _abstractPanel[itemIndex].Section;
        Rect elementRect = _realizedChildLayout[element];
        if (Orientation == Orientation.Horizontal)
        {
            double viewportHeight = _pixelMeasuredViewport.Height;
            if (elementRect.Bottom > viewportHeight)
                _offset.Y += 1;
            else if (elementRect.Top < 0)
                _offset.Y -= 1;
        }
        else
        {
            double viewportWidth = _pixelMeasuredViewport.Width;
            if (elementRect.Right > viewportWidth)
                _offset.X += 1;
            else if (elementRect.Left < 0)
                _offset.X -= 1;
        }
        InvalidateMeasure();
        return elementRect;
    }

    public void MouseWheelDown()
    {
        PageDown();
    }

    public void MouseWheelLeft()
    {
        PageLeft();
    }

    public void MouseWheelRight()
    {
        PageRight();
    }

    public void MouseWheelUp()
    {
        PageUp();
    }

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

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

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

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

    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();
        firstIndex = GetFirstVisibleIndex();
    }

    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();
        firstIndex = GetFirstVisibleIndex();
    }

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

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

    #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
}


VirtualizingUniformGridPanel

class VirtualizingUniformGridPanel : VirtualizingPanel, IScrollInfo
{
    private Size _extent = new Size(0, 0);
    private Size _viewport = new Size(0, 0);
    private Point _offset = new Point(0, 0);
    private bool _canHorizontallyScroll = false;
    private bool _canVerticallyScroll = false;
    private ScrollViewer _owner;
    private int _scrollLength = 25;

    //-----------------------------------------
    //
    // Dependency Properties
    //
    //-----------------------------------------

    #region Dependency Properties 

    /// <summary>
    /// Columns DependencyProperty
    /// </summary>
    public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(VirtualizingUniformGridPanel),
        new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));

    /// <summary>
    /// Rows DependencyProperty
    /// </summary>
    public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows", typeof(int), typeof(VirtualizingUniformGridPanel),
        new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));

    /// <summary>
    /// Orientation DependencyProperty
    /// </summary>
    public static readonly DependencyProperty OrientationProperty = DependencyProperty.RegisterAttached("Orientation", typeof(Orientation), typeof(VirtualizingUniformGridPanel),
        new FrameworkPropertyMetadata(Orientation.Vertical, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));

    #endregion Dependency Properties


    //-----------------------------------------
    //
    // Public Properties
    //
    //-----------------------------------------

    #region Public Properties

    /// <summary>
    /// Get/Set the amount of columns this grid should have
    /// </summary>
    public int Columns
    {
        get { return (int)this.GetValue(ColumnsProperty); }
        set { this.SetValue(ColumnsProperty, value); }
    }

    /// <summary>
    /// Get/Set the amount of rows this grid should have
    /// </summary>
    public int Rows
    {
        get { return (int)this.GetValue(RowsProperty); }
        set { this.SetValue(RowsProperty, value); }
    }

    /// <summary>
    /// Get/Set the orientation of the panel
    /// </summary>
    public Orientation Orientation
    {
        get { return (Orientation)this.GetValue(OrientationProperty); }
        set { this.SetValue(OrientationProperty, value); }
    }

    #endregion Public Properties


    //-----------------------------------------
    //
    // Overrides
    //
    //-----------------------------------------

    #region Overrides

    /// <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;
        }
    }

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

        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;
        IItemContainerGenerator 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;

                childIndex = Math.Max(0, childIndex);

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

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

        if (availableSize.Height.Equals(double.PositiveInfinity))
        {
            Debug.WriteLine(_extent);
            return new Size(200, 200);
        }

        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];

            int itemIndex = generator.IndexFromGeneratorPosition(new GeneratorPosition(i, 0));

            ArrangeChild(itemIndex, child, finalSize);
        }

        return finalSize;
    }

    #endregion Overrides

    //-----------------------------------------
    //
    // Layout Specific Code
    //
    //-----------------------------------------

    #region Layout Specific Code

    /// <summary>
    /// Revisualizes 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>
    /// Calculate the extent of the view based on the available size
    /// </summary>
    /// <param name="availableSize"></param>
    /// <returns></returns>
    private Size MeasureExtent(Size availableSize, int itemsCount)
    {
        Size childSize = GetChildSize(availableSize);

        if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
        {
            return new Size((this.Columns * childSize.Width) * Math.Ceiling((double)itemsCount / (this.Columns * this.Rows)), _viewport.Height);
        }
        else
        {
            var pageHeight = (this.Rows * childSize.Height);

            var sizeWidth = _viewport.Width;
            var sizeHeight = pageHeight * Math.Ceiling((double)itemsCount / (this.Rows * this.Columns));

            return new Size(sizeWidth, sizeHeight);
        }
    }


    /// <summary>
    /// Arrange the individual children
    /// </summary>
    /// <param name="index"></param>
    /// <param name="child"></param>
    /// <param name="finalSize"></param>
    private void ArrangeChild(int index, UIElement child, Size finalSize)
    {
        int row = index / this.Columns;
        int column = index % this.Columns;

        double xPosition, yPosition;

        int currentPage;
        Size childSize = GetChildSize(finalSize);

        if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
        {
            currentPage = (int)Math.Floor((double)index / (this.Columns * this.Rows));

            xPosition = (currentPage * this._viewport.Width) + (column * childSize.Width);
            yPosition = (row % this.Rows) * childSize.Height;

            xPosition -= this._offset.X;
            yPosition -= this._offset.Y;
        }
        else
        {
            xPosition = (column * childSize.Width) - this._offset.X;
            yPosition = (row * childSize.Height) - this._offset.Y;
        }

        child.Arrange(new Rect(xPosition, yPosition, childSize.Width, childSize.Height));
    }

    /// <summary>
    /// Get the size of the child element
    /// </summary>
    /// <param name="availableSize"></param>
    /// <returns>Returns the size of the child</returns>
    private Size GetChildSize(Size availableSize)
    {
        double width = availableSize.Width / this.Columns;
        double height = availableSize.Height / this.Rows;

        return new Size(width, height);
    }

    /// <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)
    {
        Size childSize = GetChildSize(this._extent);

        int pageSize = this.Columns * this.Rows;
        int pageNumber = this.Orientation == System.Windows.Controls.Orientation.Horizontal ?
            (int)Math.Floor((double)this._offset.X / this._viewport.Width) :
            (int)Math.Floor((double)this._offset.Y / this._viewport.Height);

        firstVisibleItemIndex = (pageNumber * pageSize);
        lastVisibleItemIndex = firstVisibleItemIndex + (pageSize * 2) - 1;

        ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
        int itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;


        if (lastVisibleItemIndex >= itemCount)
        {
            lastVisibleItemIndex = itemCount - 1;
        }
    }

    #endregion


    //-----------------------------------------
    //
    // IScrollInfo Implementation
    //
    //-----------------------------------------

    #region IScrollInfo Implementation

    public bool CanHorizontallyScroll
    {
        get { return _canHorizontallyScroll; }
        set { _canHorizontallyScroll = value; }
    }

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

    /// <summary>
    /// Get the extent height
    /// </summary>
    public double ExtentHeight
    {
        get { return this._extent.Height; }
    }

    /// <summary>
    /// Get the extent width
    /// </summary>
    public double ExtentWidth
    {
        get { return this._extent.Width; }
    }

    /// <summary>
    /// Get the current horizontal offset
    /// </summary>
    public double HorizontalOffset
    {
        get { return this._offset.X; }
    }

    /// <summary>
    /// Get the current vertical offset
    /// </summary>
    public double VerticalOffset
    {
        get { return this._offset.Y; }
    }

    /// <summary>
    /// Get/Set the scrollowner
    /// </summary>
    public System.Windows.Controls.ScrollViewer ScrollOwner
    {
        get { return this._owner; }
        set { this._owner = value; }
    }

    /// <summary>
    /// Get the Viewport Height
    /// </summary>
    public double ViewportHeight
    {
        get { return _viewport.Height; }
    }

    /// <summary>
    /// Get the Viewport Width
    /// </summary>
    public double ViewportWidth
    {
        get { return _viewport.Width; }
    }



    public void LineLeft()
    {
        this.SetHorizontalOffset(this._offset.X - _scrollLength);
    }

    public void LineRight()
    {
        this.SetHorizontalOffset(this._offset.X + _scrollLength);
    }

    public void LineUp()
    {
        this.SetVerticalOffset(this._offset.Y - _scrollLength);
    }
    public void LineDown()
    {
        this.SetVerticalOffset(this._offset.Y + _scrollLength);
    }

    public Rect MakeVisible(System.Windows.Media.Visual visual, Rect rectangle)
    {
        return new Rect();
    }

    public void MouseWheelDown()
    {
        if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
        {
            this.SetHorizontalOffset(this._offset.X + _scrollLength);
        }
        else
        {
            this.SetVerticalOffset(this._offset.Y + _scrollLength);
        }
    }

    public void MouseWheelUp()
    {
        if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
        {
            this.SetHorizontalOffset(this._offset.X - _scrollLength);
        }
        else
        {
            this.SetVerticalOffset(this._offset.Y - _scrollLength);
        }
    }

    public void MouseWheelLeft()
    {
        return;
    }

    public void MouseWheelRight()
    {
        return;
    }

    public void PageDown()
    {
        this.SetVerticalOffset(this._offset.Y + _viewport.Width);
    }

    public void PageUp()
    {
        this.SetVerticalOffset(this._offset.Y - _viewport.Width);
    }

    public void PageLeft()
    {
        this.SetHorizontalOffset(this._offset.X - _viewport.Width);
    }

    public void PageRight()
    {
        this.SetHorizontalOffset(this._offset.X + _viewport.Width);
    }


    public void SetHorizontalOffset(double offset)
    {
        _offset.X = Math.Max(0, offset);

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

        InvalidateMeasure();
    }

    public void SetVerticalOffset(double offset)
    {
        _offset.Y = Math.Max(0, offset);

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

        InvalidateMeasure();
    }

    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 = MeasureExtent(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();
        }
    }

    #endregion IScrollInfo Implementation
}


使用

<ListBox ScrollViewer.VerticalScrollBarVisibility="Hidden">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <control:VirtualizingUniformGridPanel Rows="2" Columns="3" Orientation="Vertical"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>

    <ListBox.Items>
        <ListBoxItem Margin="1" BorderBrush="Red" BorderThickness="2">1</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="Green" BorderThickness="2">2</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="Blue" BorderThickness="2">3</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="BlueViolet" BorderThickness="2">4</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="RosyBrown" BorderThickness="2">5</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="RoyalBlue" BorderThickness="2">6</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="Orange" BorderThickness="2">7</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="OrangeRed" BorderThickness="2">8</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="GreenYellow" BorderThickness="2">9</ListBoxItem>
        <ListBoxItem Margin="1" BorderBrush="YellowGreen" BorderThickness="2">10</ListBoxItem>
    </ListBox.Items>
</ListBox>


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值