C# 过滤WPF虚拟化树状视图的性能方法
我有一个层次化的数据结构,我正试图用WPF树视图和层次化的数据模板来可视化它。项目的数量可以达到数百万,因此我决定尝试工作简单且良好的虚拟化功能,除了在虚拟化模式中使用“回收”而不是“标准”时出现的一个问题。很久以前,其他人似乎也有类似的问题: 然而,我很难实现工作和性能过滤 我尝试了两种不同的方法,基于互联网上的提示,比如C# 过滤WPF虚拟化树状视图的性能方法,c#,wpf,treeview,C#,Wpf,Treeview,我有一个层次化的数据结构,我正试图用WPF树视图和层次化的数据模板来可视化它。项目的数量可以达到数百万,因此我决定尝试工作简单且良好的虚拟化功能,除了在虚拟化模式中使用“回收”而不是“标准”时出现的一个问题。很久以前,其他人似乎也有类似的问题: 然而,我很难实现工作和性能过滤 我尝试了两种不同的方法,基于互联网上的提示,比如 第一种方法是使用转换器来设置基于过滤器的TreeView.Visibility,第二种方法是为所有元素创建ICollectionView即席视图,并为每个
- 项目WpfVirtualizedTreeViewPerItemVisibility用于第一个和第二个
- 第二种方法的项目WPFvirtualizedTreeViewPerItemVisibility 3
public class TvItemBase {}
public class TvItemType1 : TvItemBase
{
public string Name1 { get; set; }
public List<TvItemBase> Entries { get; } = new List<TvItemBase>();
}
public class TvItemType2 : TvItemBase
{
public string Name2 { get; set; }
public int i { get; set; }
}
public class TvItemBase {}
public class TvItemType1 : TvItemBase
{
public string Name1 { get; set; }
public List<TvItemBase> Entries { get; } = new List<TvItemBase>();
public ICollectionView FilteredEntries
{
get
{
var dv = CollectionViewSource.GetDefaultView(Entries);
dv.Filter = MainWindow.singleton.filterFunc; // todo:hack
return dv;
}
}
}
public class TvItemType2 : TvItemBase
{
public string Name2 { get; set; }
public int i { get; set; }
}
public类TvItemBase{}
公共类TVTItemType1:TVTItemBase
{
公共字符串名称1{get;set;}
公共列表项{get;}=new List();
}
公共类TVTItemType2:TVTItemBase
{
公共字符串名称2{get;set;}
公共整数i{get;set;}
}
转换器如下所示(它对应于第二种方法中的“filterFunc”)
公共类TVTItemType2VisibleConverter:IMultiValueConverter
{
公共TVTItemType2VisibleConverter(){}
公共对象转换(对象[]值,类型targetType,对象参数,System.Globalization.CultureInfo区域性)
{
如果(值。长度<2)
返回可见性。可见;
var TviItem=值[0]作为树项;
if(tviitem==null)
返回可见性。可见;
var entry=tviitem.DataContext作为tviitemtype2;
if(条目==null)
返回可见性。可见;
var模型=作为IFilterProvider的值[1];
if(model==null)
返回可见性。可见;
如果(!model.ShowA)
返回可见性。折叠;
else if(条目.i%2==0)
返回可见性。折叠;
其他的
返回可见性。可见;
}
公共对象[]ConvertBack(对象值,类型[]targetTypes,对象参数,System.Globalization.CultureInfo区域性){抛出新系统。NotImplementedException();}
}
窗口impl,也是视图模型,看起来像这样
public interface IFilterProvider
{
bool ShowA { get; }
}
public partial class MainWindow : Window, INotifyPropertyChanged, IFilterProvider
{
public event PropertyChangedEventHandler PropertyChanged;
void NotifyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
bool ShowA_ = true;
public bool ShowA
{
get { return ShowA_; }
set { ShowA_ = value; NotifyChanged(nameof(ShowA)); NotifyChanged(nameof(HackToForceVisibilityUpdate)); }
}
public bool HackToForceVisibilityUpdate { get { return true; } }
void generateTestItems(TvItemType1 parent, int nof1, int nof2, int levels)
{
for (int i = 0; i < nof1; i++)
{
var i1 = new TvItemType1 { Name1 = string.Format("F_{0}.{1}.{2}.{3}", levels, i, nof1, nof2) };
parent.Entries.Add(i1);
if (levels > 0)
generateTestItems(i1, nof1, nof2, levels - 1);
}
for (int i = 0; i < nof2; i++)
parent.Entries.Add(new TvItemType2 { Name2 = string.Format("{0}.{1}.{2}.{3}", levels, nof1 + i, nof1, nof2), i = nof1 + i });
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
var i1 = new TvItemType1 { Name1 = "root" };
generateTestItems(i1, 10, 1000, 3);
tv.ItemsSource = new List<TvItemBase> { i1 };
}
}
public interface IFilterProvider
{
bool ShowA { get; }
}
public partial class MainWindow : Window, INotifyPropertyChanged, IFilterProvider
{
public event PropertyChangedEventHandler PropertyChanged;
void NotifyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
bool ShowA_ = true;
public bool ShowA
{
get { return ShowA_; }
set
{
ShowA_ = value;
// todo:hack
// todo:why does this update the whole tree
(tv.ItemsSource as ICollectionView).Refresh();
NotifyChanged(nameof(ShowA));
}
}
void generateTestItems(TvItemType1 parent, int nof1, int nof2, int levels)
{
for (int i = 0; i < nof1; i++)
{
var i1 = new TvItemType1 { Name1 = string.Format("F_{0}.{1}.{2}.{3}", levels, i, nof1, nof2) };
parent.Entries.Add(i1);
if (levels > 0)
generateTestItems(i1, nof1, nof2, levels - 1);
}
for (int i = 0; i < nof2; i++)
parent.Entries.Add(new TvItemType2 { Name2 = string.Format("{0}.{1}.{2}.{3}", levels, nof1 + i, nof1, nof2), i = nof1 + i });
}
public bool filterFunc(object obj)
{
var entry = obj as TvItemType2;
if (entry == null)
return true;
var model = this;
if (!model.ShowA)
return false;
else if (entry.i % 2 == 0)
return false;
else
return true;
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
singleton = this; // todo:hack
var i1 = new TvItemType1 { Name1 = "root" };
generateTestItems(i1, 10, 1000, 3);
//generateTestItems(i1, 3, 10, 3);
var l = new List<TvItemBase> { i1 };
var dv = CollectionViewSource.GetDefaultView(l);
dv.Filter = filterFunc;
tv.ItemsSource = dv;
}
public static MainWindow singleton = null; // todo:[really big]hack
}
公共接口IFilterProvider
{
bool ShowA{get;}
}
公共部分类主窗口:窗口,INotifyPropertyChanged,IFilterProvider
{
公共事件属性更改事件处理程序属性更改;
void NotifyChanged(字符串名称){PropertyChanged?.Invoke(这个,新的PropertyChangedEventArgs(名称));}
bool ShowA=真;
昭和公馆
{
获取{return ShowA;}
设置{ShowA_uz=value;NotifyChanged(nameof(ShowA));NotifyChanged(nameof(hacktoforcevibilityupdate));}
}
公共bool hacktoforcevibilityupdate{get{return true;}}
void generateTestItems(TvItemType1父项、int-nof1、int-nof2、int-levels)
{
对于(int i=0;i0)
发电机系统(i1、nof1、nof2,级别-1);
}
对于(int i=0;i
最后,这里是xaml:
<Window.Resources>
<local:TvItemType2VisibleConverter x:Key="TvItemType2VisibleConverter"/>
<HierarchicalDataTemplate DataType="{x:Type local:TvItemType1}" ItemsSource="{Binding Entries}">
<TextBlock Text="{Binding Name1}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource TvItemType2VisibleConverter}">
<Binding RelativeSource="{RelativeSource Self}" />
<!-- todo: how to specify the filter provider through a view model property (using Path and Source?) -->
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:IFilterProvider}" />
<!-- todo: how to enforce filter reevaluation without this hack -->
<Binding Path="HackToForceVisibilityUpdate" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:IFilterProvider}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="IsExpanded" Value="True" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TvItemType2}">
<TextBlock Text="{Binding Name2}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<ToggleButton Content="A" IsChecked="{Binding ShowA, Mode=TwoWay}" Width="20" Height="20" HorizontalAlignment="Left" VerticalAlignment="Top" />
<TreeView x:Name="tv"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard" Margin="0,24,0,0" />
<!-- todo: when using VirtualizingStackPanel.VirtualizationMode="Recycling", a lot of
System.Windows.Data Error: 40 : BindingExpression path error: 'Entries' property not found on 'object' ''TvItemType2' (HashCode=...)'. BindingExpression:Path=Entries; DataItem='TvItemType2' (HashCode=...); target element is 'TreeViewItem' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
are flooding the output window.
See f.e. https://stackoverflow.com/questions/4950208/bindingexpression-path-error-using-itemscontrol-and-virtualizingstackpanel -->
</Grid>
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:TvItemType1}" ItemsSource="{Binding FilteredEntries}">
<TextBlock Text="{Binding Name1}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TvItemType2}">
<TextBlock Text="{Binding Name2}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<ToggleButton Content="A" IsChecked="{Binding ShowA, Mode=TwoWay}" Width="20" Height="20" HorizontalAlignment="Left" VerticalAlignment="Top" />
<TreeView x:Name="tv"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard" Margin="0,24,0,0" />
</Grid>
第二种方法的代码:
数据结构:
public class TvItemBase {}
public class TvItemType1 : TvItemBase
{
public string Name1 { get; set; }
public List<TvItemBase> Entries { get; } = new List<TvItemBase>();
}
public class TvItemType2 : TvItemBase
{
public string Name2 { get; set; }
public int i { get; set; }
}
public class TvItemBase {}
public class TvItemType1 : TvItemBase
{
public string Name1 { get; set; }
public List<TvItemBase> Entries { get; } = new List<TvItemBase>();
public ICollectionView FilteredEntries
{
get
{
var dv = CollectionViewSource.GetDefaultView(Entries);
dv.Filter = MainWindow.singleton.filterFunc; // todo:hack
return dv;
}
}
}
public class TvItemType2 : TvItemBase
{
public string Name2 { get; set; }
public int i { get; set; }
}
public类TvItemBase{}
公共类TVTItemType1:TVTItemBase
{
公共字符串名称1{get;set;}
公共列表项{get;}=new List();
公共ICollectionView过滤器设备
{
得到
{
var dv=CollectionViewSource.GetDefaultView(条目);
dv.Filter=MainWindow.sing