WPF ListBox滑动滚动条到底部时持续追加数据
背景
由于我们的数据展示中使用过的listBox,如果一下子展示过多的数据时,如果还需要加载图片等,可能会导致加载过程中等待时间过长,用户体验不不佳,因此我们采用分页的方式来实现更佳的用户体验,但是需要实现当滑动滚动条(这里是向底部滑动过程中)进行数据加载,也就是相当于点击下一页的效果。
实现思路
ListBox本身有一个ScrollView,ScrollView有几个属性可以标记是否当前已经滑到底部,然后进行数据的追加。
使用VisualTreeHelper来获取ListBox中的ScrollViewer
获取方式: scrollViewer = FindSimpleVisualChild<ScrollViewer>(listBox);
T FindSimpleVisualChild<T>(DependencyObject element) where T : class { while (element != null) { if (element is T) return element as T; element = VisualTreeHelper.GetChild(element, 0); } return null; }
然后监听: scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;当滑动到底部时去加载数据。
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { if (isBootomScrollView(scrollViewer)) { if (pageSize < Total) { page++; pageSize = page * 10; // NextPaged?.Invoke( page,10);
//追加数据的逻辑 } } } private bool isBootomScrollView(ScrollViewer view) { bool isBottom = false; double dVer = view.VerticalOffset; double vViewport = view.ViewportHeight; double eextent = view.ExtentHeight; if (dVer != 0) { if (dVer + vViewport == eextent) { isBottom = true; } else { isBottom = false; } } else { isBottom = false; } return isBottom; }
每次首页加载时需要注意,一定要将Scrollviewer设置滑动到顶部,否则数据会一直加载
完整代码如下:public event Action<object> SelectItemChanged;
private int Total = 0; int pageSize = 0; int page = 1; ScrollViewer scrollViewer = null; /// <summary> /// 刷新数据 /// </summary> /// <param name="total"></param> /// <param name="newlist"></param> public void ReFleshImage(int total, ObservableCollection<Data> newlist) { Task.Run(() => { Total = total; if (newlist==null) { pageSize = OldData.Count; } this.Dispatcher.InvokeAsync(() => { if (alreadyHookedScrollEvents) return; if (scrollViewer == null)//首次加载中去获取ScrollView可能会报异常,因此在这里再次判断获取 { scrollViewer = FindSimpleVisualChild<ScrollViewer>(listBox); if (scrollViewer != null) { scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged; scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; alreadyHookedScrollEvents = true; } } else { scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged; scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; alreadyHookedScrollEvents = true; } alreadyHookedScrollEvents = true;//标注下次不需要再获取 }); }); Task.Run(() => { this.Dispatcher.InvokeAsync(() => { try { List<BitmapSource> list = new List<BitmapSource>(); if (newlist == null) { foreach (var item in OldData) { var img = ImageService.GetImageByUrl(item.imagePath); list.Add(img); } for (int i = 0; i < list.Count; i++) { OldeData[i].ImageSource = list[i]; } } else { foreach (var item in newlist) { var img = ImageService.GetImagebyUrl(item.imagePath); list.Add(img); } for (int i = 0; i < list.Count; i++) { newlist[i].ImageSource = list[i]; } List<Data> oldlist = OldData.ToList(); oldlist.AddRange(newlist); OlData = oldlist.ToObservableCollection(); } } catch (Exception ex) { } }); }); } /// <summary> /// 再次加载 /// </summary> public void ResetPage() { try { page = 1; if (scrollViewer != null) { scrollViewer.ScrollToHome(); } } catch (Exception ex) { CommonLib.LogClass.SystemErrLog("", ex.ToString()); } } public event Action<int,int> NextPaged; bool alreadyHookedScrollEvents = false; void UserControl_Loaded(object sender, RoutedEventArgs e) { Task.Run(() => { this.Dispatcher.InvokeAsync(() => { try { scrollViewer = FindSimpleVisualChild<ScrollViewer>(listBox); } catch (Exception ex) { } }); }); } T FindSimpleVisualChild<T>(DependencyObject element) where T : class { while (element != null) { if (element is T) return element as T; element = VisualTreeHelper.GetChild(element, 0); } return null; } private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { if (isBootomScrollView(scrollViewer)) { if (pageSize < Total) { page++; pageSize = page * 10; NextPaged?.Invoke( page,10);
} } } private bool isBootomScrollView(ScrollViewer view) { bool isBottom = false; double dVer = view.VerticalOffset; double vViewport = view.ViewportHeight; double eextent = view.ExtentHeight; if (dVer != 0) { if (dVer + vViewport == eextent) { isBottom = true; } else { isBottom = false; } } else { isBottom = false; } return isBottom; } }
以上只是个人见解,应该有更好的方式。希望大家指点一下。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南