VirtualizingStackPanel 在覆盖 ScrollViewer 的默认控件模板时停止工作
我有一个列表框,其中包含很多渲染成本高昂的项目。然而,VirtualizingStackPanel 通过仅渲染可见项来解决这个问题。我想覆盖 ScrollViewer 的控件模板,因为默认模板在水平和垂直滚动条之间有灰色矩形。我刚刚复制了微软提供的一个(ScrollViewer ControlTemplate示例),不存在灰色矩形问题。
然而,该控制模板通过为 VirtualizingStackPanel 提供无限的高度来禁用虚拟化。这意味着 VirtualizingStackPanel 将渲染所有项目,因为它认为所有项目都是可见的。
在下面的演示代码中,我在列表框中显示了 10000 个项目。我通过比较运行 ScrollViewer 样式和不使用 ScrollViewer 样式来验证问题。使用它,演示运行速度非常慢,调整大小需要很多秒。没有样式,速度非常快。我输出一些有关 VirtualizingStackPanel 的信息来证明我的观点:
没有 ScrollViewer 样式(在 XAML 中注释掉样式):
视口高度:8
范围高度:10000
实际高度:245
是否虚拟化:True
虚拟化模式:标准
使用 ScrollViewer 样式:
视口高度:0
范围高度:0
实际高度:272766.666666707
是否虚拟化:True
虚拟化模式:标准
知道如何为 ScrollViewer 编写一个不与虚拟化混淆的控件模板吗?
XAML:
<Window x:Class="VirtualTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="{x:Type ScrollViewer}" TargetType="{x:Type ScrollViewer}">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.Row="0" Grid.Column="0" />
<ScrollBar
Name="PART_VerticalScrollBar"
Grid.Row="0" Grid.Column="1"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
<ScrollBar
Name="PART_HorizontalScrollBar"
Orientation="Horizontal"
Grid.Row="1" Grid.Column="0"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ListBox
ItemsSource="{Binding Numbers}"
ScrollViewer.ScrollChanged="ListBox_ScrollChanged"
Background="Orange">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="2" Margin="5">
<TextBlock Text="{Binding .}" Width="400"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
隐藏代码:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace VirtualTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
public IEnumerable<double> Numbers
{
get
{
for (int i = 0; i < 10000; i++)
{
yield return i;
}
}
}
private void ListBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ListBox listBox = sender as ListBox;
VirtualizingStackPanel virtualizingStackPanel = FindVirtualizingStackPanel(listBox);
Debug.WriteLine("ViewportHeight: " + virtualizingStackPanel.ViewportHeight);
Debug.WriteLine("ExtentHeight: " + virtualizingStackPanel.ExtentHeight);
Debug.WriteLine("ActualHeight: " + virtualizingStackPanel.ActualHeight);
Debug.WriteLine("IsVirtualizing: " + VirtualizingStackPanel.GetIsVirtualizing(virtualizingStackPanel));
Debug.WriteLine("VirtualizationMode: " + VirtualizingStackPanel.GetVirtualizationMode(virtualizingStackPanel));
}
private VirtualizingStackPanel FindVirtualizingStackPanel(Visual visual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual child = VisualTreeHelper.GetChild(visual, i) as Visual;
if (child != null)
{
if (child is VirtualizingStackPanel)
{
return child as VirtualizingStackPanel;
}
VirtualizingStackPanel found = FindVirtualizingStackPanel(child);
if (found != null)
{
return found;
}
}
}
return null;
}
}
}
I have a listbox with a lot of items which are expensive to render. However the VirtualizingStackPanel takes care of that by only rendering the visible items. I want to override the control template for ScrollViewer since the default one has grey rectangle between the horizontal and vertical scrollbar. I just copied the one provided by Microsoft (ScrollViewer ControlTemplate Example) which do not have the grey rectangle issue.
This controltemplate however disables virtualization by giving the VirtualizingStackPanel endless height. That means that the VirtualizingStackPanel will render all items, since it thinks all items are visible.
In the below demo code I show 10000 items in a listbox. I verify the issue by comparing running it with the ScrollViewer style and without it. With it the demo runs very slow, resizing takes many seconds. Without the style it's very fast. I output some info about the VirtualizingStackPanel to prove my point:
Without the ScrollViewer style (Comment out the style in the XAML):
ViewportHeight: 8
ExtentHeight: 10000
ActualHeight: 245
IsVirtualizing: True
VirtualizationMode: Standard
With the ScrollViewer style:
ViewportHeight: 0
ExtentHeight: 0
ActualHeight: 272766.666666707
IsVirtualizing: True
VirtualizationMode: Standard
Any idea how to write a control template for a ScrollViewer that doesn't mess with virtualization?
XAML:
<Window x:Class="VirtualTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="{x:Type ScrollViewer}" TargetType="{x:Type ScrollViewer}">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.Row="0" Grid.Column="0" />
<ScrollBar
Name="PART_VerticalScrollBar"
Grid.Row="0" Grid.Column="1"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
<ScrollBar
Name="PART_HorizontalScrollBar"
Orientation="Horizontal"
Grid.Row="1" Grid.Column="0"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ListBox
ItemsSource="{Binding Numbers}"
ScrollViewer.ScrollChanged="ListBox_ScrollChanged"
Background="Orange">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="2" Margin="5">
<TextBlock Text="{Binding .}" Width="400"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace VirtualTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
public IEnumerable<double> Numbers
{
get
{
for (int i = 0; i < 10000; i++)
{
yield return i;
}
}
}
private void ListBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ListBox listBox = sender as ListBox;
VirtualizingStackPanel virtualizingStackPanel = FindVirtualizingStackPanel(listBox);
Debug.WriteLine("ViewportHeight: " + virtualizingStackPanel.ViewportHeight);
Debug.WriteLine("ExtentHeight: " + virtualizingStackPanel.ExtentHeight);
Debug.WriteLine("ActualHeight: " + virtualizingStackPanel.ActualHeight);
Debug.WriteLine("IsVirtualizing: " + VirtualizingStackPanel.GetIsVirtualizing(virtualizingStackPanel));
Debug.WriteLine("VirtualizationMode: " + VirtualizingStackPanel.GetVirtualizationMode(virtualizingStackPanel));
}
private VirtualizingStackPanel FindVirtualizingStackPanel(Visual visual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual child = VisualTreeHelper.GetChild(visual, i) as Visual;
if (child != null)
{
if (child is VirtualizingStackPanel)
{
return child as VirtualizingStackPanel;
}
VirtualizingStackPanel found = FindVirtualizingStackPanel(child);
if (found != null)
{
return found;
}
}
}
return null;
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
此 ScrollViewer 样式是从 Blend 4 复制的,在输出窗口中看起来不错
This ScrollViewer Style is copied from Blend 4 and it looks good in the output window