| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 | using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Controls.Primitives;using System.Windows.Media;namespace MECF.Framework.UI.Client.Ctrlib.Controls{    public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo    {        private TranslateTransform trans = new TranslateTransform();        public VirtualizingWrapPanel()        {            this.RenderTransform = trans;        }        #region DependencyProperties        public static readonly DependencyProperty ChildWidthProperty = DependencyProperty.RegisterAttached("ChildWidth", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(200.0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));        public static readonly DependencyProperty ChildHeightProperty = DependencyProperty.RegisterAttached("ChildHeight", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(200.0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));        //鼠标每一次滚动 UI上的偏移        public static readonly DependencyProperty ScrollOffsetProperty = DependencyProperty.RegisterAttached("ScrollOffset", typeof(int), typeof(VirtualizingWrapPanel), new PropertyMetadata(10));        public int ScrollOffset        {            get { return Convert.ToInt32(GetValue(ScrollOffsetProperty)); }            set { SetValue(ScrollOffsetProperty, value); }        }        public double ChildWidth        {            get => Convert.ToDouble(GetValue(ChildWidthProperty));            set => SetValue(ChildWidthProperty, value);        }        public double ChildHeight        {            get => Convert.ToDouble(GetValue(ChildHeightProperty));            set => SetValue(ChildHeightProperty, value);        }        public Orientation Orientation        {            get => (Orientation)(GetValue(OrientationProperty));            set => SetValue(OrientationProperty, value);        }        public static readonly DependencyProperty OrientationProperty = DependencyProperty.RegisterAttached("Orientation", typeof(Orientation), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));        #endregion        int GetItemCount(DependencyObject element)        {            var itemsControl = ItemsControl.GetItemsOwner(element);            return itemsControl.HasItems ? itemsControl.Items.Count : 0;        }        int CalculateChildrenPerRow(Size availableSize)        {            int childPerRow = 0;            if (availableSize.Width == double.PositiveInfinity)                childPerRow = this.Children.Count;            else            {                childPerRow = Math.Max(1, Convert.ToInt32(Math.Floor(availableSize.Width / this.ChildWidth)));            }            return childPerRow;        }        int CalculateChildrenPerColumn(Size availableSize)        {            int childPerColumn = 0;            if (availableSize.Height == double.PositiveInfinity)                childPerColumn = this.Children.Count;            else            {                childPerColumn = Math.Max(1, Convert.ToInt32(Math.Floor(availableSize.Height / this.ChildHeight)));            }            return childPerColumn;        }        /// <summary>        /// width不超过availableSize的情况下,自身实际需要的Size(高度可能会超出availableSize)        /// </summary>        /// <param name="availableSize"></param>        /// <param name="itemsCount"></param>        /// <returns></returns>        Size CalculateExtent(Size availableSize, int itemsCount)        {            if (Orientation == Orientation.Horizontal)            {                int childPerRow = CalculateChildrenPerRow(availableSize);//现有宽度下 一行可以最多容纳多少个                return new Size(childPerRow * this.ChildWidth, this.ChildHeight * Math.Ceiling(Convert.ToDouble(itemsCount) / childPerRow));            }            else if (Orientation == Orientation.Vertical)            {                int childPerColumn = CalculateChildrenPerColumn(availableSize);//现有高度下 一行可以最多容纳多少个                return new Size(Math.Ceiling(Convert.ToDouble(itemsCount) / childPerColumn) * this.ChildWidth, this.ChildHeight * childPerColumn);            }            else            {                return availableSize;            }        }        /// <summary>        /// 更新滚动条        /// </summary>        /// <param name="availableSize"></param>        void UpdateScrollInfo(Size availableSize)        {            var extent = CalculateExtent(availableSize, GetItemCount(this));//extent 自己实际需要            if (extent != this.extent)            {                this.extent = extent;                this.ScrollOwner.InvalidateScrollInfo();            }            if (availableSize != this.viewPort)            {                this.viewPort = availableSize;                this.ScrollOwner.InvalidateScrollInfo();            }        }        /// <summary>        /// 获取所有item,在可视区域内第一个item和最后一个item的索引        /// </summary>        /// <param name="firstIndex"></param>        /// <param name="lastIndex"></param>        void GetVisiableRange(ref int firstIndex, ref int lastIndex)        {            if (Orientation == Orientation.Horizontal)            {                int childPerRow = CalculateChildrenPerRow(this.extent);                firstIndex = Convert.ToInt32(Math.Floor(this.offset.Y / this.ChildHeight)) * childPerRow;                if (!double.IsInfinity(this.viewPort.Height))                {                    lastIndex = Convert.ToInt32(Math.Ceiling((this.offset.Y + this.viewPort.Height) / this.ChildHeight)) * childPerRow - 1;                    int itemsCount = GetItemCount(this);                    if (lastIndex >= itemsCount)                        lastIndex = itemsCount - 1;                }            }            else if (Orientation == Orientation.Vertical)            {                int childPerColumn = CalculateChildrenPerColumn(this.extent);                firstIndex = Convert.ToInt32(Math.Ceiling(this.offset.X / this.ChildWidth)) * childPerColumn;                if (!double.IsInfinity(this.viewPort.Width))                {                    lastIndex = Convert.ToInt32(Math.Ceiling((this.offset.X + this.viewPort.Width) / this.ChildWidth)) * childPerColumn - 1;                    int itemsCount = GetItemCount(this);                    if (lastIndex >= itemsCount)                        lastIndex = itemsCount - 1;                }            }        }        /// <summary>        /// 将不在可视区域内的item 移除        /// </summary>        /// <param name="startIndex">可视区域开始索引</param>        /// <param name="endIndex">可视区域结束索引</param>        void CleanUpItems(int startIndex, int endIndex)        {            var children = this.InternalChildren;            var generator = this.ItemContainerGenerator;            for (int i = children.Count - 1; i >= 0; i--)            {                var childGeneratorPosi = new GeneratorPosition(i, 0);                int itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPosi);                if (itemIndex < startIndex || itemIndex > endIndex)                {                    generator.Remove(childGeneratorPosi, 1);                    RemoveInternalChildRange(i, 1);                }            }        }        /// <summary>        /// scroll/availableSize/添加删除元素 改变都会触发  edit元素不会改变        /// </summary>        /// <param name="availableSize"></param>        /// <returns></returns>        protected override Size MeasureOverride(Size availableSize)        {            this.UpdateScrollInfo(availableSize);//availableSize更新后,更新滚动条            int firstVisiableIndex = 0, lastVisiableIndex = 0;            GetVisiableRange(ref firstVisiableIndex, ref lastVisiableIndex);//availableSize更新后,获取当前viewport内可放置的item的开始和结束索引  firstIdnex-lastIndex之间的item可能部分在viewport中也可能都不在viewport中。            UIElementCollection children = this.InternalChildren;//因为配置了虚拟化,所以children的个数一直是viewport区域内的个数,如果没有虚拟化则是ItemSource的整个的个数            IItemContainerGenerator generator = this.ItemContainerGenerator;            //获得第一个可被显示的item的位置            GeneratorPosition startPosi = generator.GeneratorPositionFromIndex(firstVisiableIndex);            int childIndex = (startPosi.Offset == 0) ? startPosi.Index : startPosi.Index + 1;//startPosi在chilren中的索引            using (generator.StartAt(startPosi, GeneratorDirection.Forward, true))            {                int itemIndex = firstVisiableIndex;                while (itemIndex <= lastVisiableIndex)//生成lastVisiableIndex-firstVisiableIndex个item                {                    bool newlyRealized = false;                    var child = generator.GenerateNext(out newlyRealized) as UIElement;                    if (child == null) return availableSize;                    if (newlyRealized)                    {                        if (childIndex >= children.Count)                            base.AddInternalChild(child);                        else                        {                            base.InsertInternalChild(childIndex, child);                        }                        generator.PrepareItemContainer(child);                    }                    else                    {                        //处理 正在显示的child被移除了这种情况                        if (!child.Equals(children[childIndex]))                        {                            base.RemoveInternalChildRange(childIndex, 1);                        }                    }                    child.Measure(new Size(this.ChildWidth, this.ChildHeight));                    //child.DesiredSize;//child想要的size                    itemIndex++;                    childIndex++;                }            }            CleanUpItems(firstVisiableIndex, lastVisiableIndex);            return new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width, double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height);//自身想要的size        }        protected override Size ArrangeOverride(Size finalSize)        {            //Debug.WriteLine("----ArrangeOverride");            var generator = this.ItemContainerGenerator;            UpdateScrollInfo(finalSize);            if (Orientation == Orientation.Horizontal)            {                int childPerRow = CalculateChildrenPerRow(finalSize);                double availableItemWidth = finalSize.Width / childPerRow;                for (int i = 0; i <= this.Children.Count - 1; i++)                {                    var child = this.Children[i];                    int itemIndex = generator.IndexFromGeneratorPosition(new GeneratorPosition(i, 0));                    int row = 0;                    int column = 0;                    row = itemIndex / childPerRow;//current row                    column = itemIndex % childPerRow;                    double xCorrdForItem = 0;                    xCorrdForItem = column * availableItemWidth + (availableItemWidth - this.ChildWidth) / 2;                    Rect rec = new Rect(xCorrdForItem, row * this.ChildHeight, this.ChildWidth, this.ChildHeight);                    child.Arrange(rec);                }                return finalSize;            }            else if (Orientation == Orientation.Vertical)            {                int childPerColumn = CalculateChildrenPerColumn(finalSize);                double availableItemHeight = finalSize.Height / childPerColumn;                for (int i = 0; i <= this.Children.Count - 1; i++)                {                    var child = this.Children[i];                    int itemIndex = generator.IndexFromGeneratorPosition(new GeneratorPosition(i, 0));                    int row = 0;                    int column = 0;                    row = itemIndex % childPerColumn;                    column = i / childPerColumn;                    double xCorrdForItem = 0;                    xCorrdForItem = row * availableItemHeight + (availableItemHeight - this.ChildHeight) / 2;                    Rect rec = new Rect(column * this.ChildWidth, row * ChildHeight, this.ChildWidth, this.ChildHeight);                    child.Arrange(rec);                }                return finalSize;            }            else { return finalSize; }        }        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)        {            base.OnRenderSizeChanged(sizeInfo);            this.SetVerticalOffset(this.VerticalOffset);        }        protected override void OnClearChildren()        {            base.OnClearChildren();            this.SetVerticalOffset(0);        }        protected override void BringIndexIntoView(int index)        {            if (index < 0 || index >= Children.Count)                throw new ArgumentOutOfRangeException();            if (Orientation == Orientation.Horizontal)            {                int row = index / CalculateChildrenPerRow(RenderSize);                SetVerticalOffset(row * this.ChildHeight);            }            else if (Orientation == Orientation.Vertical)            {                int column = index / CalculateChildrenPerColumn(RenderSize);                SetVerticalOffset(column * this.ChildWidth);            }        }        #region IScrollInfo Interface        public bool CanVerticallyScroll { get; set; }        public bool CanHorizontallyScroll { get; set; }        private Size extent = new Size(0, 0);        public double ExtentWidth => this.extent.Width;        public double ExtentHeight => this.extent.Height;        private Size viewPort = new Size(0, 0);        public double ViewportWidth => this.viewPort.Width;        public double ViewportHeight => this.viewPort.Height;        private Point offset;        public double HorizontalOffset => this.offset.Y;        public double VerticalOffset => this.offset.X;        public ScrollViewer ScrollOwner { get; set; } = new ScrollViewer();        public void LineDown()        {            this.SetVerticalOffset(this.HorizontalOffset + this.ScrollOffset);        }        public void LineLeft()        {            this.SetVerticalOffset(this.VerticalOffset - this.ChildWidth);        }        public void LineRight()        {            this.SetVerticalOffset(this.VerticalOffset + this.ChildWidth);        }        public void LineUp()        {            this.SetVerticalOffset(this.HorizontalOffset - this.ScrollOffset);        }        public Rect MakeVisible(Visual visual, Rect rectangle)        {            return new Rect();        }        public void MouseWheelDown()        {            this.SetVerticalOffset(this.VerticalOffset + this.ScrollOffset);        }        public void MouseWheelLeft()        {            throw new NotImplementedException();        }        public void MouseWheelRight()        {            throw new NotImplementedException();        }        public void MouseWheelUp()        {            this.SetVerticalOffset(this.VerticalOffset - this.ScrollOffset);        }        //临时借用到ScrollToLeftEnd        public void PageDown()        {            this.SetVerticalOffset(this.extent.Width - this.viewPort.Width);            //this.SetVerticalOffset(this.HorizontalOffset + this.viewPort.Height);        }        public void PageLeft()        {            this.SetVerticalOffset(this.VerticalOffset - this.viewPort.Width);        }        public void PageRight()        {            this.SetVerticalOffset(this.VerticalOffset + this.viewPort.Width);        }        //临时借用到ScrollToRightEnd        public void PageUp()        {            this.SetVerticalOffset(0);            //  this.SetVerticalOffset(this.HorizontalOffset - this.viewPort.Height);        }        public void ScrollToLeftEnd()        {            this.SetVerticalOffset(0);        }        public void SetHorizontalOffset(double offset)        {                   }        public void SetVerticalOffset(double offset)        {            if (Orientation == Orientation.Horizontal)            {                if (offset < 0 || this.viewPort.Height >= this.extent.Height)                    offset = 0;                else                    if (offset + this.viewPort.Height >= this.extent.Height)                    offset = this.extent.Height - this.viewPort.Height;                this.offset.Y = offset;                this.ScrollOwner?.InvalidateScrollInfo();                this.trans.Y = -offset;                this.InvalidateMeasure();            }            else if (Orientation == Orientation.Vertical)            {                if (offset < 0 || this.viewPort.Width >= this.extent.Width)                    offset = 0;                else                       if (offset + this.viewPort.Width >= this.extent.Width)                    offset = this.extent.Width - this.viewPort.Width;                this.offset.X = offset;                this.ScrollOwner?.InvalidateScrollInfo();               // this.trans.X = -offset;                this.InvalidateMeasure();            }            //接下来会触发MeasureOverride()        }        #endregion    }}
 |