【问题标题】:Let a ViewModel know when the user scrolled to the end of a ListView in XAML with MVVM让 ViewModel 知道用户何时使用 MVVM 在 XAML 中滚动到 ListView 的末尾
【发布时间】:2015-11-24 08:49:22
【问题描述】:

有没有简单的方法来做到这一点?互联网上有一些示例,但没有针对 MVVM Windows Phone 8.1 运行时应用程序。

我的 ListView 最初有 20 个项目,当用户滚动到末尾时,需要加载另外 20 个项目。但是如何让我的 ViewModel 知道 ListView 已到达末尾?

【问题讨论】:

    标签: xaml listview windows-phone-8.1 mvvm-light fetch


    【解决方案1】:

    如果您的案例在到达列表末尾时加载更多项目以创建“延迟加载”,请查看ISupportIncrementalLoading 接口。它是您在集合中实现的接口,ListView 会在结束时自动使用它来加载更多项目。这是一个示例:https://marcominerva.wordpress.com/2013/05/22/implementing-the-isupportincrementalloading-interface-in-a-window-store-app/

    我个人不喜欢这种方法,因为集合知道如何加载数据。当到达列表末尾时,Tareq Ateik 创建了一个 ExtendedListViewControl (http://www.tareqateik.com/extendedlistview-control-for-universal-apps-pull-to-refresh-load-more-data-on-demand) 触发事件。因此,当事件被触发时,在您的 ViewModel 中处理它(如果您使用 Caliburn.Micro,您可以直接“订阅”该事件),或者使用消息传递向 ViewModel 发送消息。

    或者只是在代码隐藏中处理事件,然后从代码隐藏中调用 ViewModel 上所需的方法。

    【讨论】:

    • @Igor Kulman 好的,我会看看,谢谢。但实际上我唯一需要做的就是在到达 ListView 的末尾时向我的 ViewModel 发送一些东西,然后从那里我用不同的 startindex 进行另一个 api 调用。
    • 然后使用 ExtendedListViewControl,它会在到达列表末尾时触发一个事件
    • @IgorKulman 是否可以在 MVVM 灯中使用 MoreDataRequested?像这样: 跨度>
    • @IgorKulman 在 Windows Phone 8.1 运行时中没有 EvenToCommand,在这里他们使用了 Behaviors,但“MoreDataRequested”不是支持的 EventName,例如 Loaded、Tapped 或 Unchecked。
    • "MoreDataRequested" = ExtendedListView.MoreDataRequested 和所有其他事件,如 "Loaded"、"Tapped" = UIElement.(事件名称)。
    【解决方案2】:

    基于在WinRT Xaml Toolkit 中实现的Helper 类,您可以在ListView 控件中获取对垂直scrollBar 的引用,然后将事件处理程序附加到它,以帮助您检测何时到达底部到达ListView 时,util 知道将在页面后面的代码中实现的所有内容(这不会违反任何 mvvm 规则,因为这是与视图相关的逻辑)并且一旦到达底部 w'将使用 Mvvm-light Messenger 类将 NotificationMessage 发送到页面的 ViewModel,这里如何按步骤操作:

    第 1 步

    将以下帮助器类(来自 Xaml 工具包)添加到您的项目中,它有一些扩展方法,可以让您访问滚动条:

    public static class VisualTreeHelperExtensions
    {
        public static T GetFirstDescendantOfType<T>(this DependencyObject start) where T : DependencyObject
        {
            return start.GetDescendantsOfType<T>().FirstOrDefault();
        }
    
        public static IEnumerable<T> GetDescendantsOfType<T>(this DependencyObject start) where T : DependencyObject
        {
            return start.GetDescendants().OfType<T>();
        }
    
        public static IEnumerable<DependencyObject> GetDescendants(this DependencyObject start)
        {
            var queue = new Queue<DependencyObject>();
            var count = VisualTreeHelper.GetChildrenCount(start);
    
            for (int i = 0; i < count; i++)
            {
                var child = VisualTreeHelper.GetChild(start, i);
                yield return child;
                queue.Enqueue(child);
            }
    
            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);
                }
            }
        }
    
        public static T GetFirstAncestorOfType<T>(this DependencyObject start) where T : DependencyObject
        {
            return start.GetAncestorsOfType<T>().FirstOrDefault();
        }
    
        public static IEnumerable<T> GetAncestorsOfType<T>(this DependencyObject start) where T : DependencyObject
        {
            return start.GetAncestors().OfType<T>();
        }
    
        public static IEnumerable<DependencyObject> GetAncestors(this DependencyObject start)
        {
            var parent = VisualTreeHelper.GetParent(start);
    
            while (parent != null)
            {
                yield return parent;
                parent = VisualTreeHelper.GetParent(parent);
            }
        }
    
        public static bool IsInVisualTree(this DependencyObject dob)
        {
            return Window.Current.Content != null && dob.GetAncestors().Contains(Window.Current.Content);
        }
    
        public static Rect GetBoundingRect(this FrameworkElement dob, FrameworkElement relativeTo = null)
        {
            if (relativeTo == null)
            {
                relativeTo = Window.Current.Content as FrameworkElement;
            }
    
            if (relativeTo == null)
            {
                throw new InvalidOperationException("Element not in visual tree.");
            }
    
            if (dob == relativeTo)
                return new Rect(0, 0, relativeTo.ActualWidth, relativeTo.ActualHeight);
    
            var ancestors = dob.GetAncestors().ToArray();
    
            if (!ancestors.Contains(relativeTo))
            {
                throw new InvalidOperationException("Element not in visual tree.");
            }
    
            var pos =
                dob
                    .TransformToVisual(relativeTo)
                    .TransformPoint(new Point());
            var pos2 =
                dob
                    .TransformToVisual(relativeTo)
                    .TransformPoint(
                        new Point(
                            dob.ActualWidth,
                            dob.ActualHeight));
    
            return new Rect(pos, pos2);
        }
    }
    

    第 2 步

    listView 添加到您的 xaml 和 Loaded 事件处理程序中,

        DataContext="{Binding Main, Source={StaticResource Locator}}" Loaded="MainPage_OnLoaded">  
        <Grid>
            <ListView  ItemsSource="{Binding ItemsCollection}" x:Name="ListV"  VerticalAlignment="Center" Height="200" HorizontalAlignment="Center" >
    
            </ListView>
        </Grid>
    </common:PageBase>
    

    第 3 步

    在你的页面代码后面添加所需的处理程序

        public MainPage()
        {
            InitializeComponent();
        }
    
    
        private void MainPage_OnLoaded(object sender, RoutedEventArgs e)
        {
            var scrollViewer = ListV.GetFirstDescendantOfType<ScrollViewer>();
            var scrollbars = scrollViewer.GetDescendantsOfType<ScrollBar>().ToList();
            var verticalBar = scrollbars.FirstOrDefault(x => x.Orientation == Orientation.Vertical);
            if (verticalBar != null)
                verticalBar.Scroll += BarScroll;
        }
        async void BarScroll(object sender, ScrollEventArgs e)
        {
            if (e.ScrollEventType != ScrollEventType.EndScroll) return;
    
            var bar = sender as ScrollBar;
            if (bar == null)
                return;
    
            System.Diagnostics.Debug.WriteLine("Scrolling ended");
    
            if (e.NewValue >= bar.Maximum)
            {
                System.Diagnostics.Debug.WriteLine("We are at the bottom");
                Messenger.Default.Send<NotificationMessage>(new NotificationMessage("LoadMore"));                
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("We are away from the bottom");
            }
        }
    }
    

    在上面的代码中请注意,一旦检测到底部,将使用Messenger 类发送NotificationMessage

     Messenger.Default.Send<NotificationMessage>(new NotificationMessage("LoadMore")); 
    

    第 4 步

    在页面的 View 模型中添加代码,一旦收到通知消息就会加载新项目到 ListView

    public class MainViewModel : ViewModelBase
    {
        private ObservableCollection<String> _itemsCollection = new ObservableCollection<string>()
        {
            "Item 1",
            "Item 2",
            "Item 3",
            "Item 4"        
        };
    
        public ObservableCollection<String> ItemsCollection
        {
            get
            {
                return _itemsCollection;
            }
    
            set
            {
                if (_itemsCollection == value)
                {
                    return;
                }
    
                _itemsCollection = value;
                RaisePropertyChanged();
            }
        }
        public async Task LoadMoreItems()
        {
            ItemsCollection.Add("Item " + ItemsCollection.Count);
            ItemsCollection.Add("Item " + ItemsCollection.Count);
            ItemsCollection.Add("Item " + ItemsCollection.Count);
            ItemsCollection.Add("Item " + ItemsCollection.Count);
            ItemsCollection.Add("Item " + ItemsCollection.Count);
    
        }
        public MainViewModel()
        {   
            Messenger.Default.Register<NotificationMessage>(this, async m =>
            {
                switch (m.Notification)
                {
                    case "LoadMore":
                        await LoadMoreItems();
                        break;
    
                }
            });
        }  
    }
    

    如何检测底部的功劳归于以下article

    更新

    确实,如 cmets 中所述,上述代码在 WinRt Store 应用程序上完美运行,但由于某种原因,它不适用于 windows phone (winrt 8.1) 项目,它看起来像垂直方向的 scroll 事件ScrollBar 由于某种原因没有触发,我有一个解决方法,而是在 ListView 内处理 ScrollViewerViewChanged 事件,将页面后面的代码更改为以下代码:

    public sealed partial class MainPage
    {
    
        public MainPage()
        {
            InitializeComponent();
        }
    
    
        private void MainPage_OnLoaded(object sender, RoutedEventArgs e)
        {
            var scrollViewer = ListV.GetFirstDescendantOfType<ScrollViewer>();
            scrollViewer.ViewChanged+=BarScroll;          
        }
    
        private void BarScroll(object sender, ScrollViewerViewChangedEventArgs e)
        {
            var scrollbars = (sender as ScrollViewer).GetDescendantsOfType<ScrollBar>().ToList();
            var verticalBar = scrollbars.FirstOrDefault(x => x.Orientation == Orientation.Vertical);
            if (verticalBar.Value >= verticalBar.Maximum)
            {
                System.Diagnostics.Debug.WriteLine("We are at the bottom");
                Messenger.Default.Send<NotificationMessage>(new NotificationMessage("LoadMore"));
            }           
        }        
    }
    

    其余部分保持原样。

    【讨论】:

    • 嘿,这看起来很棒是的!但是当我滚动到底部时,什么也没有发生。应用程序永远不会出现在“async void BarScroll(object sender, ScrollEventArgs e)”中。
    • 顺便说一句,这段代码自动滚动到 ListView 的底部?
    • 嗨@Bayern 我显然在发布我的答案之前尝试了我所描述的方法,它在我的末尾完美运行gyazo.com/8381c92515b437f9245327b8021ff3ee 唯一的区别是我使用的是winrt商店应用程序而不是一个 windows phone 应用程序(由于我的机器中的一些硬件限制),我发布的代码应该适用于商店和手机应用程序,我会将示例项目上传到 oneDrive,以便您可以获得更好的示例项目进行自定义,然后告诉我问题是否仍然存在
    • 好的,我找到了一个可能很丑的解决方案。 if (((_Teller % 2) == 0) && (verticalBar.Value >= verticalBar.Maximum)), 所以现在它只触发一次事件:D。无论如何,感谢您提供此代码!
    猜你喜欢
    • 2015-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-26
    • 2020-09-07
    • 1970-01-01
    • 2019-03-28
    相关资源
    最近更新 更多