我有ListView
(默认情况下虚拟化是打开的),ItemsSource
绑定到ObservableCollection<Item>
属性。
当数据被填充时(属性被设置并且通知被提升),我在分析器中看到2个布局峰值,第二个峰值发生在调用listView.ScrollIntoView()
之后。
我的理解是:
ListView
通过绑定加载数据,并从索引0开始为屏幕上的项目创建ListViewItem
。
1.然后调用listView.ScrollIntoView()
。
1.现在ListView
第二次执行该操作(创建ListViewItem
s)。
如何防止去虚拟化发生两次(我不希望在ScrollIntoView
之前发生一次)?
我试着用ListBox
做一个repro。
示例1:
<Grid>
<ListBox x:Name="listBox" ItemsSource="{Binding Items}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Content="Fill" VerticalAlignment="Top" HorizontalAlignment="Center" Click="Button_Click" />
</Grid>
cs:
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public class ViewModel : NotifyPropertyChanged
{
public class Item : NotifyPropertyChanged
{
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
}
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
void Button_Click(object sender, RoutedEventArgs e)
{
var list = new List<ViewModel.Item>(1234567);
for (int i = 0; i < 1234567; i++)
list.Add(new ViewModel.Item());
list.Last().IsSelected = true;
_vm.Items = new ObservableCollection<ViewModel.Item>(list);
listBox.ScrollIntoView(list.Last());
}
}
调试-性能分析器-应用程序时间轴...稍等一下,点击按钮,稍等一下,关闭窗口。您将看到2个布局过程,其中VirtualizingStackPanel
。我的目标是只生一个,但我不知道怎么做。
repro的问题是模拟负载(在创建ListViewItem
is expensive时),但我希望它现在能更清楚地演示这个问题。
2条答案
按热度按时间px9o7tmv1#
这是我在chatgpt找到的,希望能帮到你。
您观察到的行为是由于ListBox控件的虚拟化机制造成的。默认情况下,当调用ScrollIntoView时,ListBox需要确保所请求的项在屏幕上可见。这可能导致生成附加的ListBoxItem并触发第二布局通道。
为了防止这种行为并且只有一次布局传递,可以在填充ListBox之前暂时禁用虚拟化,然后重新启用它。下面是包含这种方法的代码的更新版本:
通过在填充ListBox之前将
VirtualizingStackPanel.IsVirtualizing
attached属性设置为false
,可以有效地禁用该特定操作的虚拟化。填充ListBox后,通过将IsVirtualizing
设置回true
来重新启用虚拟化。这样,您就可以确保在填充过程中只发生一次布局过程,并且随后的ScrollIntoView不会触发额外的布局过程。siotufzp2#
Scroll方法通常在
VirtualizingStackPanel
上不能很好地工作。为了解决这个问题,我使用以下解决方案。1.删除
VirtualizingStackPanel
。使用普通的StackPanel作为面板模板。1.从这里将DataTemplate的外层设置为LazyControl:http://blog.angeloflogic.com/2014/08/lazycontrol-in-junglecontrols.html
1.确保在LazyControl上设置了高度。
我通常从这种方法中获得良好的性能。要使它完全按照您的要求执行,您可能需要向LazyControl添加一些额外的逻辑,以等待设置某个标志(在调用scroll方法之后)。