背景: 我正在开发应用程序。其中一个页面是新闻源。新闻中的数据是动态的:它可能包含多个文本块,根据内容为每个新闻项目计算许多图像和布局。
由于不可能(我不知道如何)使它与ListBox / DataTemplate / Binding一起工作,我采用了更低级别的方法并试图实现Virtualized Canvas: https://dl.dropbox.com/u/16063542/MyVirtualizingPanel.cs
想法很简单:
每个包含的项目都实现:
public interface IVirtualizable { void ChangeState(VirtualizableState newState);
VirtualizableState CurrentState { get; }
double FixedHeight { get; }
FrameworkElement View { get; }
Thickness Margin { get; }
}
-panel应放入ScrollViewer并初始化:
public void InitializeWithScrollViewer(ScrollViewer _scrollViewer)
{
_listScrollViewer = _scrollViewer;
EnsureBoundToScrollViewer();
}
protected void EnsureBoundToScrollViewer()
{
Binding binding = new Binding();
binding.Source = _listScrollViewer;
binding.Path = new PropertyPath("VerticalOffset");
binding.Mode = BindingMode.OneWay;
this.SetBinding(ListVerticalOffsetProperty, binding);
}
Panel实现AddItems方法:
public void AddItems(IEnumerable _itemsToBeAdded) {
double topMargin = 0;
foreach (var itemToBeAdded in _itemsToBeAdded)
{
itemToBeAdded.View.Margin = new Thickness(0, itemToBeAdded.Margin.Top + topMargin, 0, 0);
_virtualizableItems.Add(itemToBeAdded);
topMargin += itemHeightIncludingMargin;
}
Height = topMargin;
}
然后根据滚动位置的变化加载和卸载项目:
private void LoadItemsInSegment(Segment segment,VirtualizableState desiredState) { for(int i = segment.LowerBound; i< = segment.UpperBound; i ++) { var item = _virtualizableItems [i];
item.ChangeState(desiredState);
if (!Children.Contains(item.View))
{
Children.Add(item.View);
}
}
}
private void LoadItemsInSegment(Segment segment,VirtualizableState desiredState) { for(int i = segment.LowerBound; i< = segment.UpperBound; i ++) { var item = _virtualizableItems [i];
item.ChangeState(desiredState);
if (!Children.Contains(item.View))
{
Children.Add(item.View);
}
}
}
private void UnloadItemsInSegment(Segment segment)
{
for (int i = segment.LowerBound; i <= segment.UpperBound; i++)
{
var item = _virtualizableItems[i];
Children.Remove(item.View);
item.ChangeState(VirtualizableState.Unloaded);
}
}
或多或少,它有效。然而,随着布局变得更加复杂并且获得多个图像,滚动性能变得有些差。除此之外,在不时渲染时会出现明显的问题:突然(对于一帧)元素出现错位,看起来像是“眨眼”,不知道如何解释它。无论如何,我的问题是:这种方法最初是否存在缺陷? 或者我应该继续尝试使它工作?
答案 0 :(得分:0)
我认为您的虚拟化面板不会虚拟化任何内容。
在上下文中,虚拟化以下列方式工作。您需要显示10000个数据项。该屏幕仅适合15个UI元素。系统提供的VirtualizingStackPanel创建~17个UI元素,当用户滚动列表时重用这些元素。当UI元素离开屏幕时,面板会“释放”UI元素(如果需要,可以进行干涉,请参阅ClearContainerForItemOverride
方法)。并为以后的重用做好准备(同样,如果需要,可以进行干预,请参阅PrepareContainerForItemOverride
)。如果需要更多元素(例如,因为数据项变得更小),则创建新的UI元素。
重点是你有10000个虚拟物品,而实际物品只有~17个,因此虚拟化。
但是在我的代码中,我看到了
public interface IVirtualizable
{
FrameworkElement View { get; }
}
这意味着您的数据项和UI元素之间存在一对一的关系。 UI元素很昂贵,它们拥有GPU纹理和其他系统资源。这就是为什么当你添加更多元素时,你会遇到性能问题。
重新实现VirtualizingStackPanel很难。它的代码比MyVirtualizingPanel类多3-4倍。幸运的是你不需要它,它已经在框架中,并且它非常通用。如果由于选择/未选择的内容而不喜欢列表框,请使用普通的ItemsControl,并将ItemContainer设置为VirtualizingStackPanel。