zoukankan      html  css  js  c++  java
  • WPF自定义虚拟化VirtualizingUniformGridWrapPanel

    自定义一个VirtualizingUniformGridWrapPanel。

    实现
    1. 新建类VirtualizingWrapPanel,继承VirtualizingPanel并实现IScrollInfo
    public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
    {
    }
    1
    2
    3
    然后添加一个TranslateTransform字段,这主要是滚动时需要用到。

    private TranslateTransform trans = new TranslateTransform();
    1
    接下来添加几个依赖属性,设置内部Child的宽、高和鼠标滚动一次的偏移量。

    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);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    2. 理解WPF中的布局定位流程
    WPF中布局定位的计算是通过Measure和Arrange方法构成的。以VirtualizingWrapPanel为例(以下简称VWP),VWP的父layout调用自身的Measure(Size availableSize) 方法,告诉VWP你有availableSize的大小可以使用,然后MeasureCore会根据一定的测量逻辑,告诉VWP的protected override Size MeasureOverride(Size availableSize) 方法,你有availableSize的大小可以用,在这里VWP调用其子元素的Measure方法,告诉子元素有多大的Size可以用(此例,因为我们子child的大小都是通过依赖属性设置好的,所以直接传入即可,子child的DesiredSize也不考虑)。子child都Measure完之后,返回一个Size,这个Size是VMP自身需要的Size,父layout会通过VWP.DesiredSize属性拿到这个值。然后ArrangeCore又会根据一定的逻辑,分配一个finalSize给VWP。VWP通过protected override Size ArrangeOverride(Size finalSize)方法就收到了这个值,然后在给定的finalSize里划分不同的区域,调用子child的Arrange方法,告诉每个child应该在哪个区域。

    /// <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;
    //availableSize更新后,获取当前viewport内可放置的item的开始和结束索引,
    //firstIdnex-lastIndex之间的item可能部分在viewport中也可能都不在viewport中。
    GetVisiableRange(ref firstVisiableIndex, ref lastVisiableIndex);

    //因为配置了虚拟化,所以children的个数一直是viewport区域内的个数,
    //如果没有虚拟化则是ItemSource的整个的个数
    UIElementCollection children = this.InternalChildren;
    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;
    //生成lastVisiableIndex-firstVisiableIndex个item
    while (itemIndex <= lastVisiableIndex)
    {
    bool newlyRealized = false;
    var child = generator.GenerateNext(out newlyRealized) as UIElement;
    if (newlyRealized)
    {
    if (childIndex >= children.Count)
    base.AddInternalChild(child);
    else
    {
    base.InsertInternalChild(childIndex, child);
    }
    generator.PrepareItemContainer(child);
    }
    else
    {
    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);
    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 = itemIndex / childPerRow;//current row
    int 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;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    3. 什么时候应该刷新UI
    MSDN文档 告诉我们,当ScrollViewer的offset, extent, or viewport 这三个属性发生变化时,应该当调用ScrollViewer的InvalidateScrollInfo方法,然后ScrollView就会自动更新滚动条长短和位置。此时也应该调用InvalidateMeasure方法,然后会重新Measure布局。
    offset,extent和viewport的表示区域如下图:

    黑色的表示实际显示到界面上的内容。如果不虚拟化则24个item都会在wrappanel中,虚拟化后只有需要显示的那部分(9-16)会在wrappanel中,其他的都删除了。

    更新UI操作:

    public void SetVerticalOffset(double offset)
    {
    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();//Scroll信息已过期
    this.trans.Y = -offset;
    this.InvalidateMeasure();//Measure信息已过期
    //接下来会触发MeasureOverride()
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    4. 虚拟化操作
    操作的第一步是获取当前VWP中已加载的所有的child和ListBox的数据源中所有的child。
    VWP中children的获取可通过this.InternalChildren拿到。
    数据源中children包含在this.ItemContainerGenerator 里面。
    这里sdk有个bug,如果你不先调用this.InternalChildren,直接用ItemContainerGenerator后续生成child操作会返回null。

    第二步,获取到应该显示到viewport区域内的第一个child和最后一个child的索引,此时viewport的大小可能已经是变化后的。(因为你可能滚动了鼠标,或者更改了VWP的宽高)

    /// <summary>
    /// 获取所有item,在可视区域内第一个item和最后一个item的索引
    /// </summary>
    /// <param name="firstIndex"></param>
    /// <param name="lastIndex"></param>
    void GetVisiableRange(ref int firstIndex, ref int lastIndex)
    {
    int childPerRow = CalculateChildrenPerRow(this.extent);
    firstIndex = Convert.ToInt32(Math.Floor(this.offset.Y / this.ChildHeight)) * childPerRow;
    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;

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    第三、通过Generator生成从firstIndex到lastIndex的项,并添加到ListBox(等ItemsControl中)。
    在开始之前先看几个定义和工作流程:

    定义:在这里我们把你绑定到ItemsControl上的数据称作DataItems,其子项称为DataItem。
    定义:把在UI上的Itemscontrol空间内的item称为UIItem,UIItem放到不同的control里有不同的名字,如ListViewItem、TreeViewItem等。
    获取generatorPostition流程:对应的是generator.GeneratorPositionFromIndex(dataItemIndex)方法,根据dataItemIndex从DataItems里找到对应的DataItem,然后再根据DataItem获取到它在generator里的索引。
    GeneratorPosition类:有两个属性index和offset。当offset==0时,表示此DataItem被Realized过,即存在对应的UIItem,index就是UIItem在generator中的位置索引。当offset!=0时,表示此DataItem是Virtualized的,没有被Realized过,此时index是-1。(不知道理解是否有误)
    生成流程:generator负责把DataItem加工成UIItem并显示(添加)到ItemsControl上。对应的是generator.GenerateNext()方法,返回值是一个UIElement(也就是UIItem),并把它添加/插入到Children中,并为它准备好容器PrepareItemContainer。
    其他:listBox.ItemContainerGenerator几个方法的对比:
    ListBox.ItemContainerGenerator.ContainerFromIndex():通过DataItem在DataItems里的index,查到在ItemContainerGenerator中对应的UIItem。
    ListBox.ItemContainerGenerator.ContainerFromItem(): 通过DataItem,查找在ItemContainerGenerator中对应的Item。
    generator.GeneratorPositionFromIndex():通过DataItem在DataItems里的index,获取它在generator里的位置。
    GeneratorPosition startPosi = generator.GeneratorPositionFromIndex(firstVisiableIndex);
    int childIndex = (startPosi.Offset == 0) ? startPosi.Index : startPosi.Index + 1;
    using (generator.StartAt(startPosi, GeneratorDirection.Forward, true))
    {
    int itemIndex = firstVisiableIndex;
    while (itemIndex <= lastVisiableIndex)
    {
    bool newlyRealized = false;
    //不断的从DataItems中获取DataItem并生成UIItem
    var child = generator.GenerateNext(out newlyRealized) as UIElement;
    if (newlyRealized)
    {
    if (childIndex >= children.Count)
    {
    base.AddInternalChild(child);
    }
    else
    {
    base.InsertInternalChild(childIndex, child);
    }
    generator.PrepareItemContainer(child);
    }
    else
    {//generator里已经有了
    if (!child.Equals(children[childIndex]))
    {
    //不相等表示children[childIndex]对应的DataItem已经不在DataItems里了,所以在Children里也要删除。
    base.RemoveInternalChildRange(childIndex, 1);
    }
    }
    child.Measure(new Size(this.ChildWidth, this.ChildHeight));
    itemIndex++;
    childIndex++;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    第四、将VWP中已不需在viewport内显示的child从children和generator的container中移除。(generator里的子项个数就是children里的子项个数。)

    /// <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);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    这里虽然调用了generator.Remove方法,将不需要显示的进行移除,但是我发现移除后generator中元素的个数与ListBox绑定的数据源中元素的个数始终是一致的。所以我觉得可能是将次child从generator的container中移除了。因为你从上面的MeasureOverride方法中也看到了,新加时是调用generator的PrepareItemContainer方法。

    5. UniformGrid效果的WrapPanel该怎么给child Arrange
    其实就在上面的ArrangeOverride方法里,很简单:

    xCorrdForItem = column * availableItemWidth + (availableItemWidth - this.ChildWidth) / 2;
    1
    6. 存在的Bug或者问题
    第一个bug你已经在上面的第4步看过了。
    还有个bug是VMP的ScrollOwner报null reference的异常,本质是找不到包裹它的ScrollViewer,其原因可能有两个:
    1. 为你的ListBox设置ItemsPanlTempalte是通过Bind的方式,会引发这个异常。如果你尝试着给它Bind一个scrollviewer,那么接下来你可能还会面临着滚动页面出现空白,但是明明新的child已经生成了,就是不会显示的UI上的问题。正确的解决方式是CodeBind,用C#代码为这个ListBox设置ItemsPanlTemplate。
    2. 自定义了Control的Tempalte,就像下面这个代码一样:

    <ControlTemplate TargetType="{x:Type ListView}">
    <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible">
    <Border Margin="10">
    <ItemsPresenter />
    </Border>
    </ScrollViewer>
    </ControlTemplate>
    1
    2
    3
    4
    5
    6
    7
    相当于你定义ControlTempate的时候重新设置了ScrollViewer,然后VMP就找不到了。所以看能不能找到不定义Tempalte的方法吧。

    7. 使用方法
    <ListBox Margin="0,50,0,0" Name="listB">
    <ListBox.ItemTemplate>
    <DataTemplate>
    <TextBlock Text="{Binding}" Width="70" Height="70"/>
    </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemsPanel>
    <ItemsPanelTemplate>
    <local:VirtualizingWrapPanel ScrollOffset="50" ChildHeight="70" ChildWidth="70"/>
    </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    </ListBox>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    8. 完整代码:
    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 VirtualizingPPanel
    {

    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);
    }
    #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;
    }
    /// <summary>
    /// width不超过availableSize的情况下,自身实际需要的Size(高度可能会超出availableSize)
    /// </summary>
    /// <param name="availableSize"></param>
    /// <param name="itemsCount"></param>
    /// <returns></returns>
    Size CalculateExtent(Size availableSize, int itemsCount)
    {
    int childPerRow = CalculateChildrenPerRow(availableSize);//现有宽度下 一行可以最多容纳多少个
    return new Size(childPerRow * this.ChildWidth, this.ChildHeight * Math.Ceiling(Convert.ToDouble(itemsCount) / childPerRow));
    }
    /// <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)
    {
    int childPerRow = CalculateChildrenPerRow(this.extent);
    firstIndex = Convert.ToInt32(Math.Floor(this.offset.Y / this.ChildHeight)) * childPerRow;
    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;

    }
    /// <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 (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);
    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 = itemIndex / childPerRow;//current row
    int 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;
    }
    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();
    int row = index / CalculateChildrenPerRow(RenderSize);
    SetVerticalOffset(row * this.ChildHeight);
    }
    #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.X;

    public double VerticalOffset => this.offset.Y;

    public ScrollViewer ScrollOwner { get; set; }

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

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

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

    public void LineUp()
    {
    this.SetVerticalOffset(this.VerticalOffset - 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);
    }

    public void PageDown()
    {
    this.SetVerticalOffset(this.VerticalOffset + this.viewPort.Height);
    }

    public void PageLeft()
    {
    throw new NotImplementedException();
    }

    public void PageRight()
    {
    throw new NotImplementedException();
    }

    public void PageUp()
    {
    this.SetVerticalOffset(this.VerticalOffset - this.viewPort.Height);
    }

    public void SetHorizontalOffset(double offset)
    {
    throw new NotImplementedException();
    }

    public void SetVerticalOffset(double offset)
    {
    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();
    //接下来会触发MeasureOverride()
    }
    #endregion
    }
    }
    ————————————————
    版权声明:本文为CSDN博主「JimCarter」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/catshitone/article/details/78813895

  • 相关阅读:
    Red Black Tree 红黑树 AVL trees 2-3 trees 2-3-4 trees B-trees Red-black trees Balanced search tree 平衡搜索树
    Red Black Tree java.util.TreeSet
    finding friends with mapreduce
    zabbix 监控jmx 需要--enable-java
    zabbix 监控jmx 需要--enable-java
    zabbix 监控jvm
    zabbix 监控jvm
    eval 捕获dbi错误
    eval 捕获dbi错误
    eval 捕获错误
  • 原文地址:https://www.cnblogs.com/xietianjiao/p/15217446.html
Copyright © 2011-2022 走看看