在 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 相同的结果,而且效率更高。
这里没有问题。外部 ItemsControl
的 ItemTemplate
本身包含一个 ItemsControl
。在 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 相同的结果,而且效率更高。
这里没有问题。外部 ItemsControl
的 ItemTemplate
本身包含一个 ItemsControl
。在 ItemsControl
中,每个元素要么被渲染,要么不被渲染。虚拟化可能有助于不渲染不可见元素,但第一个元素是部分可见的,因此它不符合虚拟化条件,并且完全以无限高度呈现。