【WP8】ScrollViewer滑动到底触发器(ListBox失效)
很多时候会有到底加载更多的需求,而ScrollViewer不支持继承,无法继承它进行扩展,只能通过触发器来控制到底的事件(当然,可以通过UserControl去扩展)
思路:定义一个Trigger,自定义依赖属性,绑定到该属性到ScrollViewer的VerticalOffset属性上,然后监听属性的变化,就能监控到滚动事件了,然后判断滚动的位置从而判断出是否到底
原理很简单,下面看实现
// ************************************************* // // 作者:bomo // 小组:WP开发组 // 创建日期:2014/7/9 0:10:32 // 版本号:V1.00 // 说明: // // ************************************************* // // 修改历史: // Date WhoChanges Made // 2014/7/9 0:10:32 bomo Initial creation // // ************************************************* using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Media; namespace XTuOne.Common.Helpers { /// <summary> /// 扩展依赖属性类 /// </summary> public static class DependencyObjectExtend { public static IEnumerable<DependencyObject> GetDescendant(this DependencyObject element) { var list = new List<DependencyObject>(); var count = VisualTreeHelper.GetChildrenCount(element); for (int i = 0; i < count; i++) { var child = VisualTreeHelper.GetChild(element, i); list.Add(child); list.AddRange(child.GetDescendant()); } return list; } /// <summary> /// 查找子孙节点中符合类型的首个节点 /// </summary> public static T GetFirstDescendantOfType<T>(this DependencyObject start) where T : DependencyObject { return start.GetDescendantsOfType<T>().FirstOrDefault(); } /// <summary> /// 查找子孙节点中符合类型的节点 /// </summary> public static IEnumerable<T> GetDescendantsOfType<T>(this DependencyObject start) where T : DependencyObject { return start.GetDescendants().OfType<T>(); } /// <summary> /// 获取所有的子孙阶段 /// </summary> public static IEnumerable<DependencyObject> GetDescendants(this DependencyObject start) { if (start == null) yield break; var queue = new Queue<DependencyObject>(); queue.Enqueue(start); yield return start; while (queue.Count > 0) { var parent = queue.Dequeue(); var count2 = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < count2; i++) { var child = VisualTreeHelper.GetChild(parent, i); yield return child; queue.Enqueue(child); } } } } }
// ************************************************* // // 作者:bomo // 小组:WP开发组 // 创建日期:2014/7/11 11:45:14 // 版本号:V1.00 // 说明: // // ************************************************* // // 修改历史: // Date WhoChanges Made // 2014/7/11 11:45:14 bomo Initial creation // // ************************************************* using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Interactivity; using XTuOne.Common.Helpers; namespace XTuOne.Utility.Helpers { /// <summary> /// ScrollViewer到底触发器 /// </summary> public class ScrollViewerToBottomTrigger : TriggerBase<DependencyObject> { public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register( "VerticalOffset", typeof(double), typeof(ScrollViewerToBottomTrigger), new PropertyMetadata(0.0, VerticalOffsetPropertyChanged)); private ScrollViewer scrollView; public double VerticalOffset { get { return (double)GetValue(VerticalOffsetProperty); } set { SetValue(VerticalOffsetProperty, value); } } public static void VerticalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = d as ScrollViewerToBottomTrigger; if (behavior != null) behavior.OnVerticalOffsetChanged(); } protected override void OnAttached() { base.OnAttached(); if (AssociatedObject is FrameworkElement) { (AssociatedObject as FrameworkElement).SizeChanged += control_SizeChanged; } } private void control_SizeChanged(object sender, SizeChangedEventArgs e) { if (!(AssociatedObject is FrameworkElement)) return; var scroll = AssociatedObject.GetFirstDescendantOfType<ScrollViewer>(); if (scroll != null) { AttachedScroll(scroll); (AssociatedObject as FrameworkElement).SizeChanged -= control_SizeChanged; } } private void AttachedScroll(ScrollViewer scroll) { if (scroll == null) return; scrollView = scroll; var binding = new Binding { Source = scroll, Path = new PropertyPath("VerticalOffset") }; BindingOperations.SetBinding(this, VerticalOffsetProperty, binding); } private void OnVerticalOffsetChanged() { var scroll = scrollView; if (scroll == null) return; if (scroll.ExtentHeight - scroll.VerticalOffset - scroll.ViewportHeight <= 5) { InvokeActions(null); } } } }
通过Trigger实现,由于Trigger是一个附加元素,可以附加到任何一个符合的元素上(子孙元素包含ScrollViewer的控件),同时触发的事件可以支持绑定
使用(使用CM进行绑定)
<ScrollViewer x:Name="ScrollViewer"> <i:Interaction.Triggers> <utilityHelpers:ScrollViewerToBottomTrigger> <micro:ActionMessage MethodName="LoadMessage"/> </utilityHelpers:ScrollViewerToBottomTrigger> </i:Interaction.Triggers> </ScrollViewer>
一个触发器只能对应触发一个事件(用于绑定),如果需要多个扩展,比如:到顶触发,滑动触发等,就需要编写多个触发器,如果这样可以考虑在UserControl扩展
发现一个问题(上面触发器在ListBox失效):
ListBox中的ScrollViewer的ExtentHeight是数据项的个数,比如ListBox有20条数据,那么ExtentHeight就是20
ListBox中的VerticalOffset是表示当前位置,如果当前屏幕中显示的第一条是第12条数据,那么VerticalOffset就是12.xxx
ListBox中的ViewportHeight是根据第一屏的数据计算的,把List滑动到顶部,然后计算出页面内有几个项,这个值是假定List内所有的项的大小都是一样的,如果ListBox中的ItemTemplate的高度不是相同大小的,就会出现误差,导致下面三种情况都可能出现
ExtentHeight == VerticalOffset + ViewportHeight
ExtentHeight > VerticalOffset + ViewportHeight
ExtentHeight < VerticalOffset + ViewportHeight
如果要用在ListBox上的话,需要保证每一项的高度都相同,否则会有误差(有时提前触发,有时候不触发)