zoukankan      html  css  js  c++  java
  • [置顶] 滚动条滑到底完美解决方案(适合任何带滚动条或ScrollBar控件

      看见很多人在求滚动条滑倒底部自动加载数据的解决方案,各种各样的方案很多,但令人满意的确没几个。在这里我分享一个我的自认为满意的解决方案。

      首先说下大致原理:监视滚动条坐标的变化,在达到底部时触发自己的处理事件。
      原理很简单,但实现起来可没这么容易,先上代码,边看边说。
    public class ScrollViewerTrigger:TriggerBase<DependencyObject>
        {
            ScrollViewer ScrollView;
            public static readonly DependencyProperty DirectionTypeProperty = DependencyProperty.Register("DirectionType", typeof(DirectionType), typeof(ScrollViewerTrigger), new PropertyMetadata(DirectionType.Bottom));
    
            public DirectionType DirectionType
            {
                get
                {
                    return (DirectionType)base.GetValue(ScrollViewerTrigger.DirectionTypeProperty);
                }
                set
                {
                    base.SetValue(ScrollViewerTrigger.DirectionTypeProperty, value);
                }
            }
    
            public event EventHandler ScrollTrigger;
    
            public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register("VerticalOffset", typeof(double), typeof(ScrollViewerTrigger), new PropertyMetadata(0.0, new PropertyChangedCallback(VerticalOffsetPropertyChanged)));
    
            public double VerticalOffset
            {
                get
                {
                    return (double)base.GetValue(ScrollViewerTrigger.VerticalOffsetProperty);
                }
                set
                {
                    base.SetValue(ScrollViewerTrigger.VerticalOffsetProperty, value);
                }
    
            }
    
            public static void VerticalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var behavior = d as ScrollViewerTrigger;
                if (behavior != null)
                    behavior.OnVerticalOffsetChanged();
            }
    
            protected override void OnAttached()
            {
                base.OnAttached();
    
                if (this.AssociatedObject != null && this.AssociatedObject is FrameworkElement)
                {
                    (this.AssociatedObject as FrameworkElement).SizeChanged += control_SizeChanged;
                }
            }
    
            void control_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                if (this.AssociatedObject == null || !(this.AssociatedObject is FrameworkElement))
                    return;
    
                ScrollViewer Scroll = this.AssociatedObject.GetFirstDescendantOfType<ScrollViewer>();
                if (Scroll != null)
                {
                    AttachedScroll(Scroll);
                    (this.AssociatedObject as FrameworkElement).SizeChanged -= control_SizeChanged;
                }
            }
    
            void AttachedScroll(ScrollViewer Scroll)
            {
                if (Scroll == null)
                    return;
                ScrollView = Scroll;
    
                Binding binding = new Binding();
                binding.Source = Scroll;
                binding.Path = new PropertyPath("VerticalOffset");
    
                BindingOperations.SetBinding(this, ScrollViewerTrigger.VerticalOffsetProperty, binding);
    
            }
    
            void OnVerticalOffsetChanged()
            {
                ScrollViewer Scroll = ScrollView;
                if (Scroll == null)
                    return;
    
                switch (DirectionType)
                {
                    case DirectionType.Top:
                        {
                            if (Scroll.ScrollableHeight < double.Epsilon || Scroll.VerticalOffset > double.Epsilon)
                                return;
                        }
                        break;
                    case DirectionType.Bottom:
                        {
                            if (Scroll.ScrollableHeight < double.Epsilon || Scroll.VerticalOffset + Scroll.ExtentHeight - Scroll.ScrollableHeight < Scroll.ScrollableHeight)
                                return;
                        }
                        break;
                    case DirectionType.Left:
                        {
                            if (Scroll.ScrollableWidth < double.Epsilon || Scroll.HorizontalOffset > double.Epsilon)
                                return;
                        }
                        break;
                    case DirectionType.Right:
                        {
                            if (Scroll.ScrollableWidth < double.Epsilon || Scroll.HorizontalOffset < Scroll.ScrollableWidth)
                                return;
                        }
                        break;
                    default: break;
                }
    
                if (ScrollTrigger != null)
                {
                    ScrollTrigger(this.AssociatedObject, new EventArgs());
                }
                base.InvokeActions(null);
    
            }
    
    
            protected override void OnDetaching()
            {
                base.OnDetaching();  
            }
        }

       整个实现是Trigger的扩展,这样灵活性和通用性非常高,不管是什么控件。因为Trigger本身是附加元素,对于任何控件都可以附加上去,不要要改动原有代码就可以实现功能扩展。Trigger自带的Actions则有很强的灵活性,动作触发后可以执行多个Action.

      DirectionType是触发方向,分上下左右4个方向,默认是在底部触发,想做下拉刷新功能时把DirectionType改成Top就可以了了。
      VerticalOffset则是实现滚动条监视的关键,什么作用稍后说明。
      OnAttached()是整个Trigger的入口,在这里面我监控了一个通用事件SizeChanged,主要是通过这个事件查找控件内的ScrollViewer控件。这里不能使用loaded事件,因为有些带滚动条的控件是在加载元素时才建立ScrollViewer控件, loaded事件触发时内部元素不一定会被加载,而   SizeChanged触发时 ScrollViewer已经建立好了。
      control_SizeChanged中关键语句在于 this.AssociatedObject.GetFirstDescendantOfType<ScrollViewer>();这语句是从当前控件的子控件中查找ScrollViewer控件,查找到后就会调用AttachedScroll对 ScrollViewer的滚动进行监视。
      下面就是关键了,AttachedScroll函数要对 ScrollViewer进行监控,那么如何监控呢?
           void AttachedScroll(ScrollViewer Scroll)
            {
                if (Scroll == null)
                    return;
                ScrollView = Scroll;
    
                Binding binding = new Binding();
                binding.Source = Scroll;
                binding.Path = new PropertyPath("VerticalOffset");
    
                BindingOperations.SetBinding(this, ScrollViewerTrigger.VerticalOffsetProperty, binding);
    
            }

      还记得我之前提到的VerticalOffset属性吗,我通过绑定将ScrollViewer中的 VerticalOffset属性与这个 Trigger中的VerticalOffset绑定在一起,这样ScrollViewer种的 VerticalOffset一旦有变化我这边可以实时指导。ScrollViewer中是没有 VerticalOffset变化通知的事件的,但通过绑定则可以解决这个问题。若做横向监控功能, binding.Path = new PropertyPath("VerticalOffset");换成 binding.Path = new PropertyPath("HorizontalOffset");就可以了。

      关键一步解决了剩下的就好办了, VerticalOffset一旦有变化则会调用到VerticalOffsetPropertyChanged函数,然后再调用OnVerticalOffsetChanged对坐标进行处理。
      OnVerticalOffsetChanged中会对滚动条的当前位置进行筛选,若滚动条滑倒了底部就会触发Trigger自带的Actions,我这里提供一个事件ScrollTrigger,在达到底部时也会触发,方便外部使用。
      使用起来很简单,假如我们的viewmodel中有一个加载更多数据的命令,使用如下
                      <ListBox  ItemsSource="{Binding List}">
                            <i:Interaction.Triggers>
                                <local:ScrollBarTrigger>
                                    <i:InvokeCommandAction Command="{Binding MoreItemCommand}"/>
                                </local:ScrollBarTrigger>
                            </i:Interaction.Triggers>
                        </ListBox>

      有些控件中没有ScrollViewer控件,但是有ScrollBar控件,我这里提供一个ScrollBarTrigger,原理与ScrollViewerTrigger稍有不同,哪里不同自己研究。另外ScrollBarTrigger比ScrollViewerTrigger更具通用性,因为ScrollViewer中包含ScrollBar。

      下面是 ScrollBarTrigger代码:
    public enum DirectionType { Top, Bottom, Left, Right }
        public class ScrollBarTrigger : TriggerBase<DependencyObject>
        {
            ScrollBar ScrollView;
            public static readonly DependencyProperty DirectionTypeProperty = DependencyProperty.Register("DirectionType", typeof(DirectionType), typeof(ScrollBarTrigger), new PropertyMetadata(DirectionType.Bottom));
    
            public DirectionType DirectionType
            {
                get
                {
                    return (DirectionType)base.GetValue(ScrollBarTrigger.DirectionTypeProperty);
                }
                set
                {
                    base.SetValue(ScrollBarTrigger.DirectionTypeProperty, value);
                }
    
            }
    
            public event EventHandler ScrollTrigger;
    
            protected override void OnAttached()
            {
                base.OnAttached();
    
                if (this.AssociatedObject != null && this.AssociatedObject is FrameworkElement)
                {
                    (this.AssociatedObject as FrameworkElement).SizeChanged += control_SizeChanged;
                }
            }
    
            protected override void OnDetaching()
            {
                base.OnDetaching();
    
                if (ScrollView != null)
                    ScrollView.ValueChanged -= ScrollView_ValueChanged;
    
                if (this.AssociatedObject != null && this.AssociatedObject is FrameworkElement)
                {
                    (this.AssociatedObject as FrameworkElement).SizeChanged -= control_SizeChanged;
                }
            }
    
            void control_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                if (this.AssociatedObject == null || !(this.AssociatedObject is FrameworkElement))
                    return;
    
                ScrollBar Scroll = this.AssociatedObject.GetFirstDescendantOfType<ScrollBar>();
                if (Scroll != null)
                {
                    AttachedScroll(Scroll);
                    (this.AssociatedObject as FrameworkElement).SizeChanged -= control_SizeChanged;
                }
            }
    
            void AttachedScroll(ScrollBar Scroll)
            {
                if (Scroll != null)
                {
                    ScrollView = Scroll;
                    ScrollView.ValueChanged += ScrollView_ValueChanged;
                }
            }
    
            void ScrollView_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
            {
                OnOffsetChanged();
            }
    
            void OnOffsetChanged()
            {
                if (ScrollView == null)
                    return;
    
                ScrollBar Scroll = ScrollView;
    
                switch (DirectionType)
                {
                    case DirectionType.Top:
                    case DirectionType.Left:
                        {
                            if (Scroll.Maximum < double.Epsilon || Scroll.Value - Scroll.ViewportSize > 0)
                                return;
                        }
                        break;
                    case DirectionType.Bottom:
                    case DirectionType.Right:
                        {
                            if (Scroll.Maximum < double.Epsilon || Scroll.Value + Scroll.ViewportSize < Scroll.Maximum)
                                return;
                        }
                        break;
                    default: break;
                }
    
                if (ScrollTrigger != null)
                {
                    ScrollTrigger(this.AssociatedObject,new EventArgs());
                }
                base.InvokeActions(null);
    
            }
        }

      如果对Action,Trigger,Behavior有不了解的朋友可以看我之前的博客,会有详细解释。

      示例里的工程名誉我将的有写不一样,因为我复用了之前的示例,但不影响我博客里的内容
      示例: Test.zip
  • 相关阅读:
    ASP.NET,flexpaper,SWFTools 实现简单的PDF显示(一)
    ASP.NET,flexpaper,SWFTools 实现简单的PDF显示(三)
    一个获取远程客户端真实IP的例子
    用微软Chart制作图表
    快速读取xml节点内容
    ASP.NET 网站路径【摘自MSDN】
    SqlServer连接字符串相关记录
    视图研究一二
    天大计算机研究生的求职总结
    一个计算机系研究生毕业以后的人生规划(转)
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3071810.html
Copyright © 2011-2022 走看看