VirtualizingStackPanel 在覆盖 ScrollViewer 的默认控件模板时停止工作

发布于 2024-10-03 14:46:52 字数 6160 浏览 9 评论 0原文

我有一个列表框,其中包含很多渲染成本高昂的项目。然而,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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

不寐倦长更 2024-10-10 14:46:52

此 ScrollViewer 样式是从 Blend 4 复制的,在输出窗口中看起来不错

视口高度:10
范围高度:10000
实际高度:308
是否虚拟化:True
虚拟化模式:标准

<Style TargetType="{x:Type ScrollViewer}">
    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="false">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
        </Trigger>
    </Style.Triggers>
    <Setter Property="Template" Value="{DynamicResource ScrollViewerControlTemplate1}"/>
</Style>
<ControlTemplate x:Key="ScrollViewerControlTemplate1" TargetType="{x:Type ScrollViewer}">
    <Grid x:Name="Grid" Background="{TemplateBinding Background}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/>
        <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" Margin="{TemplateBinding Padding}" Grid.Row="0"/>
        <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/>
        <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
    </Grid>
</ControlTemplate>

This ScrollViewer Style is copied from Blend 4 and it looks good in the output window

ViewportHeight: 10
ExtentHeight: 10000
ActualHeight: 308
IsVirtualizing: True
VirtualizationMode: Standard

<Style TargetType="{x:Type ScrollViewer}">
    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="false">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
        </Trigger>
    </Style.Triggers>
    <Setter Property="Template" Value="{DynamicResource ScrollViewerControlTemplate1}"/>
</Style>
<ControlTemplate x:Key="ScrollViewerControlTemplate1" TargetType="{x:Type ScrollViewer}">
    <Grid x:Name="Grid" Background="{TemplateBinding Background}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/>
        <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" Margin="{TemplateBinding Padding}" Grid.Row="0"/>
        <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/>
        <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
    </Grid>
</ControlTemplate>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文