在 ItemsControl 中虚拟化高度为 * 的 ItemsControl

Virtualize ItemsControl with a height of * inside an ItemsControl

我最近遇到了一个虚拟化问题 运行,我已将其缩小到以下代码。

虚拟化在以下代码段中不起作用的原因是 child 没有特定高度。所以我的猜测是它会永远扩展并且虚拟化会中断。

给 child 一个特定的高度解决了这个问题,但是当我想要一个滚动条时,界面变成了两个难看的滚动条,以滚动项目控件生成的整个内容(如果它的 child, 或者不是).

我的问题是,这可能吗?如果是这样我怎么能做到这一点? child 需要在不破坏虚拟化的情况下计算自身的大小。似乎设置高度 * 不起作用。

MainWindow.xaml

<Window x:Class="WpfItemsControlVirtualization.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="500" >
<Window.Resources>
    <ResourceDictionary>
        <!--Virtualised ItemsControl-->
        <Style x:Key="ItemsControlVirtialisedStyle" TargetType="ItemsControl">
            <Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
            <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel />
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ItemsControl">
                        <ScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False">
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                        </ScrollViewer>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>
</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Content="Go" Click="ButtonBase_OnClick"/>
    <Button Grid.Row="1" Content="Expand" Click="ButtonBase_OnClick2"/>
    <Expander Grid.Row="2" >
        <ItemsControl ItemsSource="{Binding Collection}" Style="{StaticResource ItemsControlVirtialisedStyle}" VirtualizingPanel.ScrollUnit="Pixel">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <!-- <RowDefinition Height="*"></RowDefinition> --> <!-- VIRTUALIZATION BREAK -->
                            <RowDefinition Height="500"></RowDefinition>
                        </Grid.RowDefinitions>
                        <ItemsControl ItemsSource="{Binding Collection}" Style="{StaticResource ItemsControlVirtialisedStyle}" VirtualizingPanel.ScrollUnit="Pixel">
                                <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <TextBox Text="{Binding Test}" />
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Expander>
</Grid>

MainWindow.xaml.cs

    using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;

namespace WpfItemsControlVirtualization
{
    /// <summary>
    /// Implements the INotifyPropertyChanged interface for data binding purposes. 
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged, INotifyPropertyChanging
    {
        #region Abstract

        public void AlertPropertyChanging(string propertyName)
        {
            OnPropertyChanging(propertyName);
        }

        public void AlertPropertyChanged(string propertyName)
        {
            OnPropertyChanged(propertyName);
        }

        protected void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        protected void OnPropertyChanging(string propertyName)
        {
            var handler = PropertyChanging;
            if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
        }

        protected bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            OnPropertyChanging(propertyName);
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        protected void Set(Action action, string propertyName = null)
        {
            OnPropertyChanging(propertyName);
            if (action != null) action();
            OnPropertyChanged(propertyName);
        }

        #endregion

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion Implementation of INotifyPropertyChanged

        #region Implementation of INotifyPropertyChanging

        public event PropertyChangingEventHandler PropertyChanging;

        #endregion Implementation of INotifyPropertyChanging
    }

    public class MySubDataTest : ViewModelBase
    {
        public MySubDataTest()
        {
        }

        public string Test
        {
            get { return "SubTest"; }
            set { }
        }


        public bool IsExpanded
        {
            get { return m_IsExpanded; }
            set { Set(ref m_IsExpanded, value); }
        }

        private bool m_IsExpanded = false;


    }


    public class MyDataTest : ViewModelBase
    {
        public MyDataTest()
        {
            int test = 1000;
            for (int i = 0; i < test; i++)
            {
                Collection.Add(new MySubDataTest());
            }
        }

        public string Test
        {
            get { return "Test"; }
            set {  }
        }


        public bool IsExpanded
        {
            get { return m_IsExpanded; }
            set { Set(ref m_IsExpanded, value); }
        }

        private bool m_IsExpanded = false;

        public ObservableCollection<MySubDataTest> Collection
        {
            get { return m_Collection; }
        }

        ObservableCollection<MySubDataTest> m_Collection = new ObservableCollection<MySubDataTest>();
    }

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public ObservableCollection<MyDataTest> Collection
        {
            get { return m_Collection; }
        }

        ObservableCollection<MyDataTest> m_Collection = new ObservableCollection<MyDataTest>();

        private void ButtonBase_OnClick(object _sender, RoutedEventArgs _e)
        {
            int count = 1;
            for (var i = 0; i < count; i++)
            {
                    Collection.Add(new MyDataTest());
            }
            DataContext = this;
        }

        private void ButtonBase_OnClick2(object _sender, RoutedEventArgs _e)
        {
            foreach (MyDataTest test in Collection)
            {
                foreach (MySubDataTest sub in test.Collection)
                {
                    sub.IsExpanded = true;
                }
                test.IsExpanded = true;
            }
        }
    }
}

提前致谢。

没有真正开箱即用的方法来做到这一点。我最后的做法是完全自定义 TreeView 的模板,因为它支持分层虚拟化。

您可以将其与 DataTemplates 一起使用,以产生与使用递归 ItemsTemplates 相同的结果,而且效率更高。

这里没有问题。外部 ItemsControlItemTemplate 本身包含一个 ItemsControl。在 ItemsControl 中,每个元素要么被渲染,要么不被渲染。虚拟化可能有助于不渲染不可见元素,但第一个元素是部分可见的,因此它不符合虚拟化条件,并且完全以无限高度呈现。