原文:
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。
运行效果:
感谢您的阅读!代码点击这里下载。