WinUI 3:如何在ItemsControl中编程滚动到虚拟化项(非ListView)
问题描述
我正在创建一个自定义ItemsControl子类MyControl,其样式模板如下:
<Style TargetType="local:MyControl"> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <ItemsStackPanel Orientation="Vertical" /> <!-- or VirtualizingStackPanel; not really sure why there are two different seemingly identical virtualization panels --> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:MyControl"> <ScrollViewer x:Name="ScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"> <ItemsPresenter /> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style>
我需要实现编程方式滚动到可能尚未加载的项(支持按内容或索引定位),且项的高度不统一。
遇到的困境:
- ListView有现成的
ScrollIntoView方法,但我不想继承ListView,因为它附带太多需要替换/重写的冗余功能; - WPF里的
VirtualizingStackPanel有BringIndexIntoViewPublic方法,但WinUI的VirtualizingStackPanel或ItemsStackPanel没有等效功能; - 查看WinUI源码发现,ListView的
ScrollIntoView依赖ItemsPresenter内部的原生功能,这些我无法直接访问。
请问有没有不继承ListView的实现方法?
可行解决方案
方案1:利用ItemContainerGenerator结合滚动触发
- 先在
MyControl类中获取模板内的核心控件:
private ScrollViewer _scrollViewer; private ItemsPresenter _itemsPresenter; protected override void OnApplyTemplate() { base.OnApplyTemplate(); _scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer; _itemsPresenter = GetTemplateChild("ItemsPresenter") as ItemsPresenter; }
- 实现按索引滚动的方法:
public void ScrollToIndex(int index) { if (index < 0 || index >= Items.Count) return; var generator = _itemsPresenter.ItemContainerGenerator; // 先尝试获取已加载的项容器 var container = generator.ContainerFromIndex(index) as FrameworkElement; if (container != null) { container.StartBringIntoView(); return; } // 容器未加载时,触发生成后再滚动 generator.StatusChanged += OnGeneratorStatusChanged; generator.PrepareItemContainer(generator.GenerateItem(index)); void OnGeneratorStatusChanged(object sender, EventArgs e) { if (generator.Status == GeneratorStatus.ContainersGenerated) { generator.StatusChanged -= OnGeneratorStatusChanged; var newContainer = generator.ContainerFromIndex(index) as FrameworkElement; newContainer?.StartBringIntoView(); } } }
- 按内容滚动的话,先通过
Items.IndexOf(item)拿到对应索引,再调用上面的ScrollToIndex即可。
方案2:手动计算滚动偏移(适合高度可预估场景)
如果项的高度虽不统一但有明确的计算逻辑(比如数据源中存储了高度值),可以直接累加目标项之前所有项的高度,设置滚动偏移:
public void ScrollToItem(object item) { int index = Items.IndexOf(item); if (index < 0) return; double totalOffset = 0; for (int i = 0; i < index; i++) { // 替换为你的数据模型类型及高度属性 var dataItem = Items[i] as YourDataModel; totalOffset += dataItem?.ItemHeight ?? 0; } _scrollViewer?.ChangeView(null, totalOffset, null); }
这个方法的局限性是必须能准确预估项高度,否则滚动位置会有偏差。
方案3:反射调用VirtualizingPanel内部方法
WinUI的VirtualizingStackPanel和ItemsStackPanel内部有未公开的滚动方法,可通过反射调用:
public void ScrollToIndexViaReflection(int index) { if (_itemsPresenter == null) return; var panel = VisualTreeHelper.GetChild(_itemsPresenter, 0) as VirtualizingPanel; if (panel == null) return; var method = panel.GetType().GetMethod("BringIndexIntoView", BindingFlags.Instance | BindingFlags.NonPublic); method?.Invoke(panel, new object[] { index }); }
注意:反射调用内部非公开方法可能会随WinUI版本更新失效,属于非官方方案,谨慎使用。
内容的提问来源于stack exchange,提问作者Emperor Eto
相关产品推荐
相关产品推荐

