首页 > 其他分享 >WPF 集合控件虚拟化操作

WPF 集合控件虚拟化操作

时间:2024-12-17 17:23:40浏览次数:9  
标签:控件 虚拟化 int void availableSize new WPF public

一、虚拟化
WPF列表控件所提供的最重要的功能就是UI虚拟化。

1、UI虚拟化技术其实就是只为可见区域中能显示的项创建容器对象的一种技术,对提升列表控件的性能有着显卓的效果。假设有一个数万条记录的数据,其可见区域只能展示30条记录仪,此时如果使用虚拟化技术,那么界面只需要创建30个数据(或多几个已保持良好的滑动性能),如果没有使用虚拟化技术,则需要生成属数万个数据 ,这显然会占用更多的内存,且影响应用的流畅度

2、虚拟化的启用 

我们需要注意的是WPF控件显示其实是由三部分组成ControlTemplate、DataTemplate、ItemsPanel(有些不一定全部是由这几部分组成这里不做过多解释) 

     1、ControlTemplate 控制控件展示的外观

    2、DataTemplate 数据模板控制控件里面数据展示方式

    3、ItemsPanel 控件显示的容器(虚拟化实现的关键) 支持虚拟化的其实是VirtualizingStackPanel容器,此容器控件除了虚拟化支持外,其他的与StackPanel功能是类似的 宁外微软也提供了 VirtualizingPanel, IScrollInfo  自己可以根据自己需求自定义

这里我们说几个常见的设置

  1、项容器再循环

列表控件在启用虚拟化的情况下,进行滚动时,默认会创建新展示的子项控件,并销毁离开可视区域的子项控件。可以通过设置VirtualizingStackPanel.VirtualizationMode="Recycling"开启像容器再循环模式,这样列表控件会保留少量的子项控件,在滚动时进行服用,只根据新数据更新其中的内容,而不是每次都创建和销毁。这有助于减少内存使用并提高性能,特别是在滚动大量数据时。

VirtualizingStackPanel.VirtualizationMode的有效值为Standard 和 Recycling,默认情况下为Standard

  2、缓存长度

VirtualizingStackPanel会多创建几个超过显示范围的子项,以便在开始滚动时候就可以立即显示这些子项,以此来优化交互。我们可以通过使用VirtualizingStackPanel.CacheLength和VirtualizingStackPanel.CacheLengthUnit来进一步调整数量。

VirtualizingStackPanel.CacheLengthUnit:表示缓存的单位,有效值分别为Item、Page、Pixel,其中Page表示可视窗口所能展示的数量的项,Pixel表示像素,适用于项显示不同大小的图片时。
VirtualizingStackPanel.CacheLength:表示缓存指定单位的数量。
值得注意的是,在滚动时,VirtualizingStackPanel会先将可见的项创建并显示后,再在后台中进行缓存的填充,因此缓存对应用程序流畅度的影响很小。

缓存当前显示项的前一页和后一页

以ItemsControl举例

缓存当前显示项的前一页和后一页

<ItemsControlVirtualizingStackPanel.CacheLength="1" VirtualizingStackPanel.CacheLengthUnit="Page"/>

缓存当前显示项的前100项和后100项

<ItemsControl VirtualizingStackPanel.CacheLength="100" VirtualizingStackPanel.CacheLengthUnit="Item"/>

缓存当前显示项的前100项和后500项

<ItemsControl VirtualizingStackPanel.CacheLength="100,500"  VirtualizingStackPanel.CacheLengthUnit="Item" />

最后还有一种默认情况下,当用户在滚动条上拖动滑动块时,会实时刷新列表,为了进一步提高滚动性能,可以通过设置ScrollViewer.IsDeferredScrollingEnabled="True"开启延迟滚动,

只有当用户释放滚动滑块时才进行刷新。

<ItemsControl ScrollViewer.IsDeferredScrollingEnabled="True"/>

这个特性需要根据实际情况使用了,虽然可以提高性能,但是无法实时展示滑动到哪个位置了,而实际开发时,用户更喜欢能实时看到当前滑动块的展示位置。

滚动单位  

VirtualizingStackPanel默认是基于项进行滚动的,也就是说无论是单击滚动条、滚动箭头还是调用ScrollIntoView()方法,在面板上至少会滚动一个完整项,而无法在滚动时展示某个项的一部分。

可以通过VirtualizingStackPanel.ScrollUnlit="Pixel"(默认为Item),将VirtualizingStackPanel的滚动设置为基于像素,可以让滚动更加流畅。 

VirtualizingStackPanel.ScrollUnit="Pixel"
接下来我自己实现一个说明一下
新建一个VirtualizingWrapPanell类
 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
 }

XML代码如下:

 <ItemsControl Name="ItemsControllist" VirtualizingStackPanel.IsVirtualizing="True" Margin="10" VirtualizingStackPanel.CacheLength="100" VirtualizingStackPanel.CacheLengthUnit="Item" VirtualizingStackPanel.ScrollUnit="Pixel">
     <ItemsControl.Template>
         <ControlTemplate>
             <Border Style="{StaticResource ResourceKey=BaseBorder}">
                 <ScrollViewer  HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"   CanContentScroll="True">
                     <ItemsPresenter VirtualizingStackPanel.VirtualizationMode="Standard"/>
                 </ScrollViewer>
             </Border>
      
         </ControlTemplate>
     </ItemsControl.Template>
     <ItemsControl.ItemTemplate>
         <DataTemplate>
             <TextBlock Text="{Binding }"/>
         </DataTemplate>
     </ItemsControl.ItemTemplate>
     <ItemsControl.ItemsPanel>
         <ItemsPanelTemplate>
             <local:VirtualizingWrapPanel ScrollOffset="50" ChildHeight="70" ChildWidth="70"/>
         </ItemsPanelTemplate>
     </ItemsControl.ItemsPanel>
 </ItemsControl>

 

标签:控件,虚拟化,int,void,availableSize,new,WPF,public
From: https://www.cnblogs.com/lisghxfdfgh/p/18612983

相关文章

  • 安卓开发学习5 - 安卓简单控件+部分androidStudio快捷键+去除默认主题+实战简单计算器
    按钮-Button按钮控件button由textview派生而来,二者区别:button拥有默认的按钮背景,而textview默认无背景button的内部文本默认居中对齐,而textview的内部文本默认靠左对齐button会默认将英文字母转为大写,而textview保持原始的英文大小写与textview相比,button增加了两个新......
  • 在 Windows Server 2022 中配置和使用 iSCSI 服务器是一项常见的任务,尤其是在虚拟化、
     在WindowsServer2022中配置和使用iSCSI服务器是一项常见的任务,尤其是在虚拟化、存储管理和备份等场景中。以下是一个初级使用教程的大纲,帮助你从头开始配置和使用iSCSI服务器。WindowsServer2022iSCSI服务器初级使用教程大纲1. 介绍与概念iSCSI概述什么是......
  • [OS] 计算机资源虚拟化技术
    1定义:计算机资源虚拟化服务器虚拟化主要通过软件技术将物理服务器的硬件资源抽象化,创建多个独立的虚拟服务器环境。2虚拟化技术方向以下是一些常见的服务器虚拟化方式和工具:基于hypervisor的虚拟化Hypervisor技术:也称为虚拟机监视器(VirtualMachineMonitor,VMM),是一种......
  • 安卓移动设备软件开发期末复习(1) 控件
    监听器监听器是事件监听机制的重要组成部分。在Java中每类事件都定义了一个相应的监听器接口,该接口定义了接收和处理事件的方法。实现该接口的类,其对象可作为监听器对象注册在事件源组件上。在图形用户界面中,需要响应用户操作的相关组件要注册一个或多个相应事件的监听器......
  • XSYL10103利用控件获取值也可以用自定义
    stringCKID=this._page.GetControlValue("btnCKMC");pu10103,btnCKMC是一个下拉框namespaceXSYLKCGL{publicclassCKWLDY:ISuwfBus{///<summary>///initialization///</summary>privateSlnS......
  • WPF开发框架Caliburn.Micro详解
    随着项目的发展,功能越来越复杂,解耦已经是每一个项目都会遇到的问题。在WPF开发中,MVVM开发模式是主要就是为了将UI页面和业务逻辑分离开来,从而便于测试,提升开发效率。当前比较流行的MVVM框架,主要有Prism,Community.Toolkit,以及今天介绍的Caliburn.Micro。而Caliburn.Micro框架是一款......
  • 记录下WPF中如何进行itescontrol中进行分隔符的代码
     XAML的代码<ItemsControlx:Name="If"lternationCount="{BindingPath=LocationNums.Count}"ItemsSource="{BindingLocationNums}"><ItemsControl.......
  • 测试使用自己编译的WPF框架(本地nuget 包引用)
    上一篇博客 本地编译WPF框架源码-wuty007-博客园 说到自己在本地编译WPF框架源码,并在本地源码的\wpf\artifacts\packages\Debug\NonShipping路径下打包处了对应的nuget包 接下来实操测试一下如何使用这些编译出来的包一、首先为了方便看到测试的效果,我在WPF源码......
  • WPF cvs draw rectangle and line
    1//xaml2<Windowx:Class="WpfApp67.MainWindow"3xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"4xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"5xmlns:d=&quo......
  • PresentationFontCache.exe 是与 Windows Presentation Foundation (WPF) 相关的一个
    PresentationFontCache.exe是与WindowsPresentationFoundation(WPF)相关的一个系统进程,它用于缓存字体信息,以提高WPF应用程序的启动和运行速度。具体来说,它是WindowsPresentationFoundationFontCache3.0.0.0的一部分,通常会在运行WPF应用程序时启动。下面是对这个......