@目錄前言一、VirtualizingStackPanel1.1 虛擬化功能介紹1、在Window中添加一個(gè)ListBox控件。2、在設(shè)計(jì)視圖中用鼠標(biāo)選中ListBox控件并右健依次單擊“編輯其他模板”-“編輯項(xiàng)的布局模板”-“編輯副本”。3、查看生成的模板代碼。1.2 虛擬化參數(shù)介紹二、Custo
@
相信很多WPF開(kāi)發(fā)者都碰到過(guò)這種情況,當(dāng)在一個(gè)ItemsControl(或繼承自ItemsControl)控件中綁定一個(gè)集合的時(shí)候,如果集合中的條目過(guò)多,那么界面就會(huì)變得卡頓甚至停止響應(yīng),特別是在容器或窗口大小發(fā)生改變時(shí),界面的渲染就會(huì)給人一種慢半拍的感覺(jué),體驗(yàn)感非常差,這時(shí)我們就可以用虛擬化技術(shù)來(lái)解決這個(gè)問(wèn)題。
UI虛擬化的核心思想就是只渲染可視范圍內(nèi)的控件,所以它通常會(huì)搭配ScrollViewer控件一起使用,通過(guò)ScrollViewer控件中的VerticalOffset、HorizontalOffset、ViewportWidth、ViewportHeight等參數(shù)可以計(jì)算出在可視范圍內(nèi)應(yīng)該顯示的控件,當(dāng)控件不被顯示時(shí)將它從Panel中移出,這樣就可以保證同一時(shí)間只渲染了有限的控件,而不是渲染所有控件,從而達(dá)到性能提升的目的。
VirtualizingStackPanel是WPF中的一個(gè)內(nèi)置控件,它提供了UI虛擬化的功能,在ListBox、ListView、DataGrid等控件中它是默認(rèn)布局控件,我們可以通過(guò)查看控件模板的方式來(lái)看看它是如何定義的。
通過(guò)以上代碼可以看出,ListBox有一個(gè)名為ItemsPanel的屬性,在該屬性中指定了一個(gè)Panel控件,ListBox在渲染時(shí)用該P(yáng)anel來(lái)布局子項(xiàng),我們要實(shí)現(xiàn)虛擬化只需要在ItemsPanel中指定VirtualizingStackPanel控件即可。
如果你自己實(shí)現(xiàn)一個(gè)繼承自ItemsControl的控件,并按1.1的步驟操作,你會(huì)發(fā)現(xiàn)還是無(wú)法實(shí)現(xiàn)虛擬化功能,原因是沒(méi)有開(kāi)啟虛擬化功能(ListBox、ListView、DataGrid等控件是默認(rèn)開(kāi)啟的),要開(kāi)啟ItemsControl控件的虛擬化功能我們還需要設(shè)置VirtualizingPanel.IsVirtualizing附加屬性,以下為示例:
VirtualizingPanel中除了IsVirtualizing參數(shù)以外還有很多其它參數(shù)可以控制更多的虛擬化細(xì)節(jié),以下是參數(shù)說(shuō)明:
要開(kāi)發(fā)自己的虛擬化Panel我們需要繼承自VirtualizingPanel類(lèi),并實(shí)現(xiàn)IScrollInfo接口,VirtualizingPanel中提供了操作Panel子控件的相關(guān)的方法,IScrollInfo接口定義了ScrollViewer控件的自定義行為,我們實(shí)現(xiàn)了IScrollInfo就可以接管ScrollViewer控件的相關(guān)操作。
代碼如下(示例):
public class CustomVirtualizingPanel : VirtualizingPanel, IScrollInfo
{
}
VirtualizingPanel中有一個(gè)名為“ItemContainerGenerator”的屬性,該屬性提供了對(duì)虛擬化Panel子控件創(chuàng)建及銷(xiāo)毀的方法,它的工作流程大致如下:
public class CustomVirtualizingPanel : VirtualizingPanel, IScrollInfo
{
public ScrollViewer ScrollOwner { get; set; } //當(dāng)前ScrollViewer控件
public bool CanVerticallyScroll { get; set; } //是否可以在垂直方向滾動(dòng)
public bool CanHorizontallyScroll { get; set; } //是否可以在水平方向滾動(dòng)
public double ExtentWidth { get; } //滾動(dòng)內(nèi)容的總寬度(包括可見(jiàn)部分和不可見(jiàn)部分)
public double ExtentHeight { get; } //滾動(dòng)內(nèi)容的總高度(包括可見(jiàn)部分和不可見(jiàn)部分)
public double ViewportWidth { get; } // ScrollViewer控件可以看到的那部分區(qū)域的寬度
public double ViewportHeight { get; } //ScrollViewer控件可以看到的那部分區(qū)域的高度
public double HorizontalOffset { get; } //水平滾動(dòng)條的偏移量
public double VerticalOffset { get; } //垂直滾動(dòng)條的偏移量
public void LineDown() { } //鼠標(biāo)點(diǎn)擊滾動(dòng)條下箭頭的操作
public void LineLeft() { } //鼠標(biāo)點(diǎn)擊滾動(dòng)條左箭頭的操作
public void LineRight() { } //鼠標(biāo)點(diǎn)擊滾動(dòng)條右箭頭的操作
public void LineUp() { } //鼠標(biāo)點(diǎn)擊滾動(dòng)條上箭頭的操作
public void MouseWheelDown() { } //鼠標(biāo)滾輪向下時(shí)的操作
public void MouseWheelLeft() { } //鼠標(biāo)滾輪向左時(shí)的操作
public void MouseWheelRight() { } //鼠標(biāo)滾輪向右時(shí)的操作
public void MouseWheelUp() { } //鼠標(biāo)滾輪向上時(shí)的操作
public void PageDown() { } //在滾動(dòng)條上按鍵盤(pán)上下頁(yè)的操作
public void PageLeft() { } //在滾動(dòng)條上按鍵盤(pán)上左頁(yè)的操作
public void PageRight() { } //在滾動(dòng)條上按鍵盤(pán)上右頁(yè)的操作
public void PageUp() { } //在滾動(dòng)條上按鍵盤(pán)上上頁(yè)的操作
public void SetHorizontalOffset(double offset) { } //設(shè)置滾動(dòng)條水平偏移量
public void SetVerticalOffset(double offset) { } //設(shè)置滾動(dòng)條垂直偏移量
public Rect MakeVisible(Visual visual, Rect rectangle) { return default; } //強(qiáng)制滾動(dòng)Panel子控件(比如只有部分區(qū)域顯示在可視范圍內(nèi),點(diǎn)擊之后完全滾動(dòng)到可視范圍內(nèi))
}
通過(guò)分析要想實(shí)現(xiàn)以上效果,最好的方法就是將CustomVirtualizingPanel中需要計(jì)算的關(guān)鍵部分抽象出來(lái)做成一個(gè)接口,當(dāng)需要布局計(jì)算的時(shí)候我們可以直接通過(guò)接口獲取到關(guān)鍵計(jì)算結(jié)果。
1) 定義接口
public interface IVirtualizingPanelBuilder
{
void Initialize(CustomVirtualizingPanel virtualizingPanel);
double GetItemWidth(Size availableSize);
double GetItemHeight(Size availableSize);
int CalculateItemsPerRowCount(Size availableSize);
int CalculateRowCount(Size availableSize);
Size CalculateExtent(Size availableSize);
ItemRange CalculateItemRange(Size availableSize);
}
2) 在CustomVirtualizingPanel類(lèi)中添加屬性VirtualizingPanelBuilder
public IVirtualizingPanelBuilder VirtualizingPanelBuilder
{
get { return (IVirtualizingPanelBuilder)GetValue(VirtualizingPanelBuilderProperty); }
set { SetValue(VirtualizingPanelBuilderProperty, value); }
}
3) 實(shí)現(xiàn)VirtualizingPanelBuilder
public class VirtualizingStackPanelBuilder : DependencyObject, IVirtualizingPanelBuilder
{
///
/// 虛擬面板
///
private CustomVirtualizingPanel _virtualizingPanel;
///
/// 初始化
///
///
public void Initialize(CustomVirtualizingPanel virtualizingPanel)
{
_virtualizingPanel = virtualizingPanel;
}
///
/// 獲取Item高度
///
///
///
public double GetItemHeight(Size availableSize)
{
if (ItemHeight > 0)
return ItemHeight;
else if (_virtualizingPanel.Children.Count != 0)
return _virtualizingPanel.Children[0].DesiredSize.Height;
else
return _virtualizingPanel.CalculateChildSize(availableSize).Height;
}
///
/// 獲取Item寬度
///
///
///
public double GetItemWidth(Size availableSize)
{
return availableSize.Width;
}
///
/// 計(jì)算每行顯示的Item數(shù)
///
///
///
public int CalculateItemsPerRowCount(Size availableSize)
{
return 1;
}
///
/// 計(jì)算行數(shù)
///
///
///
public int CalculateRowCount(Size availableSize)
{
return _virtualizingPanel.Items.Count;
}
///
/// 計(jì)算滾動(dòng)面積
///
///
///
public Size CalculateExtent(Size availableSize)
{
var height = GetItemHeight(availableSize);
var rowCount = CalculateRowCount(availableSize);
return new Size(availableSize.Width, height * rowCount);
}
///
/// 計(jì)算可見(jiàn)區(qū)域內(nèi)的Item范圍
///
///
///
public ItemRange CalculateItemRange(Size availableSize)
{
if (!this._virtualizingPanel.IsVirtualizing)
{
return new ItemRange(0, this._virtualizingPanel.Items.Count - 1);
}
var viewportHeight = _virtualizingPanel.ViewportHeight;
var offsetY = _virtualizingPanel.VerticalOffset;
var rowCount = this.CalculateRowCount(availableSize);
var itemHeight = this.GetItemHeight(availableSize);
var firstVisibleItemIndex = (int)Math.Floor(offsetY / itemHeight);
var lastVisibleItemIndex = (int)Math.Ceiling((offsetY + viewportHeight) / itemHeight) - 1;
if (lastVisibleItemIndex >= rowCount)
lastVisibleItemIndex = rowCount - 1;
return new ItemRange(firstVisibleItemIndex, lastVisibleItemIndex);
}
///
/// Item高度
///
public double ItemHeight
{
get { return (double)GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingStackPanelBuilder), new PropertyMetadata(ItemHeightPropertyChangedCallback));
public static void ItemHeightPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((VirtualizingStackPanelBuilder)d)._virtualizingPanel?.InvalidateVisual();
}
}
4) 設(shè)置參數(shù)
為了能夠演示布局切換的過(guò)渡效果,這里除了上面的StackPanel布局以外還實(shí)現(xiàn)了UniformGrid布局,以下分別演示1億條數(shù)據(jù)布局切換及非虛擬化狀態(tài)下的布局切換過(guò)渡效果。
1) 虛擬化切換布局
2) 非虛擬化切換過(guò)渡效果
機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)
閱讀華為Mate品牌盛典:HarmonyOS NEXT加持下游戲性能得到充分釋放
閱讀實(shí)現(xiàn)對(duì)象集合與DataTable的相互轉(zhuǎn)換
閱讀鴻蒙NEXT元服務(wù):論如何免費(fèi)快速上架作品
閱讀算法與數(shù)據(jù)結(jié)構(gòu) 1 - 模擬
閱讀升訊威在線(xiàn)客服與營(yíng)銷(xiāo)系統(tǒng)介紹
閱讀基于鴻蒙NEXT的血型遺傳計(jì)算器開(kāi)發(fā)案例
閱讀5. Spring Cloud OpenFeign 聲明式 WebService 客戶(hù)端的超詳細(xì)使用
閱讀Java代理模式:靜態(tài)代理和動(dòng)態(tài)代理的對(duì)比分析
閱讀Win11筆記本“自動(dòng)管理應(yīng)用的顏色”顯示規(guī)則
閱讀本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請(qǐng)發(fā)郵件[email protected]
湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2025 haote.com 好特網(wǎng)