WPF MVVM模式下实现ListView下拉显示更多内容

在手机App中,如果有一个展示信息的列表,通常会展示很少一部分,当用户滑动到列表底部时,再加载更多内容。这样有两个好处,提高程序性能,减少网络流量。这篇博客中,将介绍如何在WPF ListView中实现这个功能。

实现思路:为ListView新增一个附加属性,用来绑定当下拉到底部时触发增加列表内容的功能。

XAML:

复制代码
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
    </Window.Resources>
    <Grid>
        <ListView ItemsSource="{Binding Items}" Height="150" Width="80" local:ScrollViewerMonitor.AtEndCommand="{Binding FetchMoreDataCommand}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding}"/>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <UserControl Opacity=".85" Background="Gray" Height="150" Width="80" Visibility="{Binding Busy, Converter={StaticResource BooleanToVisibilityConverter}}">
            <TextBlock Text="Loading..." Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </UserControl>
    </Grid>
复制代码

ScrollViewerMonitor:

复制代码
public class ScrollViewerMonitor
    {
        public static ICommand GetAtEndCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(AtEndCommandProperty);
        }

        public static void SetAtEndCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(AtEndCommandProperty, value);
        }

        public static readonly DependencyProperty AtEndCommandProperty =
            DependencyProperty.RegisterAttached("AtEndCommand", typeof(ICommand), 
                typeof(ScrollViewerMonitor), new PropertyMetadata(OnAtEndCommandChanged));

        public static void OnAtEndCommandChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement element = (FrameworkElement)d;
            if (element != null)
            {
                element.Loaded -= element_Loaded;
                element.Loaded += element_Loaded;
            }
        }

        private static void element_Loaded(object sender, RoutedEventArgs e)
        {
            FrameworkElement element = (FrameworkElement)sender;

            element.Loaded -= element_Loaded;

            ScrollViewer scrollViewer = FindChildOfType<ScrollViewer>(element);

            if(scrollViewer == null)
            {
                throw new InvalidOperationException("ScrollViewer not found.");
            }

            scrollViewer.ScrollChanged += delegate 
            {
                bool atBottom = scrollViewer.VerticalOffset 
                                 >= scrollViewer.ScrollableHeight;

                if(atBottom)
                {
                    var atEnd = GetAtEndCommand(element);
                    if(atEnd != null)
                    {
                        atEnd.Execute(null);
                    }
                }
            };
        }

        private static T FindChildOfType<T>(DependencyObject root) where T : class
        {
            var queue = new Queue<DependencyObject>();
            queue.Enqueue(root);

            while (queue.Count > 0)
            {
                DependencyObject current = queue.Dequeue();
                for (int i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
                {
                    var child = VisualTreeHelper.GetChild(current, i);
                    var typedChild = child as T;
                    if (typedChild != null)
                    {
                        return typedChild;
                    }
                    queue.Enqueue(child);
                }
            }
            return null;
        }
    }
复制代码

MainViewModel:

复制代码
public class MainViewModel : INotifyPropertyChanged
    {
        public MainViewModel()
        {
            _busy = false;

            AddMoreItems();

            fetchMoreDataCommand = new DelegateCommand(() => {

                ThreadPool.QueueUserWorkItem(
                    delegate
                    {
                        Busy = true;

                        Thread.Sleep(3000);

                        App.Current.Dispatcher.BeginInvoke(new Action(()=> {

                            AddMoreItems();

                            Busy = false;

                        }));
                    });
            });
        }

        private void AddMoreItems()
        {
            int start = items.Count;
            int end = start + 10;
            for (int i = start; i < end; i++)
            {
                items.Add("Item " + i);
            }
        }

        readonly DelegateCommand fetchMoreDataCommand;

        public ICommand FetchMoreDataCommand
        {
            get
            {
                return fetchMoreDataCommand;
            }
        }

        private ObservableCollection<string> items = new ObservableCollection<string>();

        public ObservableCollection<string> Items
        {
            get
            {
                return items;
            }
        }

        private bool _busy;

        public bool Busy
        {
            get
            {
                return _busy;
            }

            set
            {
                if(_busy != value)
                {
                    _busy = value;

                    OnPropertyChanged("Busy");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
复制代码

Busy属性用来决定是否显示Loading。

运行效果:

感谢您的阅读!代码点击这里下载。

原文地址:https://www.cnblogs.com/wangchaoyuana/p/7523426.html