程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 滾動條滑到底完美解決方案(適合任何帶滾動條或ScrollBar控件

滾動條滑到底完美解決方案(適合任何帶滾動條或ScrollBar控件

編輯:C++入門知識

  看見很多人在求滾動條滑倒底部自動加載數據的解決方案,各種各樣的方案很多,但令人滿意的確沒幾個。在這裡我分享一個我的自認為滿意的解決方案。
  首先說下大致原理:監視滾動條坐標的變化,在達到底部時觸發自己的處理事件。
  原理很簡單,但實現起來可沒這麼容易,先上代碼,邊看邊說。
[csharp]
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();   
        } 
    } 

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進行監控,那麼如何監控呢?
[csharp]
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 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中有一個加載更多數據的命令,使用如下
[html]
<ListBox  ItemsSource="{Binding List}"> 
      <i:Interaction.Triggers> 
          <local:ScrollBarTrigger> 
              <i:InvokeCommandAction Command="{Binding MoreItemCommand}"/> 
          </local:ScrollBarTrigger> 
      </i:Interaction.Triggers> 
  </ListBox> 

                  <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代碼:
[csharp]
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); 
 
        } 
    } 

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有不了解的朋友可以看我之前的博客,會有詳細解釋。
  示例裡的工程名譽我將的有寫不一樣,因為我復用了之前的示例,但不影響我博客裡的內容

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved