WPF如何实现仅VirtualizingStackPanel内可见元素才加载对应图片资源
核心原因
VirtualizingStackPanel仅对ItemsControl通过ItemsSource绑定生成的动态子项生效,你直接手动添加到面板的静态元素会被全部生成UI实例,自然会一次性触发所有图片下载,这也是你之前修改CanContentScroll、ScrollUnit都不生效的根本原因。
实现方案
1. 构建分层数据源
先定义两级ViewModel结构,不要手动在XAML中写死所有布局元素:
// 单个Widget对应的数据模型 public class WidgetItem { public string Text { get; set; } public string ImageUrl { get; set; } } // 每行对应的数据模型,一行包含3个Widget public class RowItem { public List<WidgetItem> Widgets { get; set; } = new List<WidgetItem>(); }
初始化所有数据到全局集合AllRows,绑定到ListBox的ItemsSource即可。
2. 替换为支持虚拟化的布局结构
用ListBox作为外层容器,开启所有虚拟化配置,所有UI元素通过数据模板动态生成:
<ListBox x:Name="ImageContainer" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.ScrollUnit="Pixel" ScrollViewer.CanContentScroll="True" ItemsSource="{Binding AllRows}"> <!-- 每行的布局模板 --> <ListBox.ItemTemplate> <DataTemplate> <ItemsControl ItemsSource="{Binding Widgets}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <local:Widget Text="{Binding Text}" Image="{Binding ImageUrl, IsAsync=True}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </DataTemplate> </ListBox.ItemTemplate> <ListBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Vertical"/> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox>
3. 优化图片按需加载逻辑
如果还需要实现只有控件完全进入视口才下载图片,可以修改Widget控件的Image依赖属性逻辑,监听控件可见性再触发下载:
// Widget控件的Image依赖属性定义 public static readonly DependencyProperty ImageProperty = DependencyProperty.Register( nameof(Image), typeof(string), typeof(Widget), new PropertyMetadata(default(string), OnImageUrlChanged)); private static void OnImageUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var widget = (Widget)d; // 控件不可见时暂不下载图片 if (!widget.IsVisible) { void LoadWhenVisible(object sender, DependencyPropertyChangedEventArgs args) { if ((bool)args.NewValue == true) { widget.IsVisibleChanged -= LoadWhenVisible; LoadImage(widget, e.NewValue.ToString()); } } widget.IsVisibleChanged += LoadWhenVisible; return; } LoadImage(widget, e.NewValue.ToString()); } private static void LoadImage(Widget widget, string imageUrl) { var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.UriSource = new Uri(imageUrl); bitmap.CacheOption = BitmapCacheOption.OnLoad; // 开启缓存避免重复下载 bitmap.EndInit(); // 给Widget内部的Image控件赋值Source widget.InternalImage.Source = bitmap; }
内容的提问来源于stack exchange,提问作者Ozballer31
相关产品推荐
相关产品推荐

