让 ViewModel 知道用户何时使用 MVVM 滚动到 XAML 中的 ListView 的末尾
Let a ViewModel know when the user scrolled to the end of a ListView in XAML with MVVM
有没有简单的方法来做到这一点?互联网上有一些示例,但没有用于 MVVM Windows Phone 8.1 运行时应用程序。
我的 ListView 原本有 20 个项目,当用户滚动到最后时,需要加载另外 20 个项目。但是如何让我的 ViewModel 知道已到达 ListView 的末尾?
如果您的案例在到达列表末尾以创建 "lazy loading" 时加载更多项目,请查看 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 你可以直接 "subscribe" 到事件),或者使用消息传递向 ViewModel 发送消息。
或者只是在代码隐藏中处理事件并从代码隐藏中调用 ViewModel 上所需的方法。
基于 WinRT Xaml 工具包中实现的助手 class,您可以获得对 ListView
控件内垂直 scrollBar
的引用,然后附加一个事件它的处理程序,以帮助您检测何时到达 ListView
底部,util 知道将在页面后面的代码中实现的所有内容,(这不会违反任何 mvvm 规则,因为这是与视图相关的逻辑),一旦到达底部,我们将使用 Mvvm-light Messenger
class 向页面的 ViewModel 发送一个 NotificationMessage
,这里是如何分步执行的:
步骤 1
将以下助手 class(来自 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
class
发送 NotificationMessage
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("LoadMore"));
步骤 4
在页面的视图模型中添加代码,在收到通知消息后将新项目加载到 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 .
更新
确实如评论中所述,上述代码在 WinRt Store 应用程序上完美运行,但由于某种原因,它无法在 windows phone (winrt 8.1) 项目上运行,并且它看起来垂直 ScrollBar
中的 scroll
事件由于某种原因没有触发,我有一个解决方法,而不是处理 ScrollViewer
内部的 ViewChanged
事件ListView
,将您页面的隐藏代码更改为以下内容:
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"));
}
}
}
其余保持原样。
有没有简单的方法来做到这一点?互联网上有一些示例,但没有用于 MVVM Windows Phone 8.1 运行时应用程序。
我的 ListView 原本有 20 个项目,当用户滚动到最后时,需要加载另外 20 个项目。但是如何让我的 ViewModel 知道已到达 ListView 的末尾?
如果您的案例在到达列表末尾以创建 "lazy loading" 时加载更多项目,请查看 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 你可以直接 "subscribe" 到事件),或者使用消息传递向 ViewModel 发送消息。
或者只是在代码隐藏中处理事件并从代码隐藏中调用 ViewModel 上所需的方法。
基于 WinRT Xaml 工具包中实现的助手 class,您可以获得对 ListView
控件内垂直 scrollBar
的引用,然后附加一个事件它的处理程序,以帮助您检测何时到达 ListView
底部,util 知道将在页面后面的代码中实现的所有内容,(这不会违反任何 mvvm 规则,因为这是与视图相关的逻辑),一旦到达底部,我们将使用 Mvvm-light Messenger
class 向页面的 ViewModel 发送一个 NotificationMessage
,这里是如何分步执行的:
步骤 1
将以下助手 class(来自 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
class
NotificationMessage
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("LoadMore"));
步骤 4
在页面的视图模型中添加代码,在收到通知消息后将新项目加载到 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 .
更新
确实如评论中所述,上述代码在 WinRt Store 应用程序上完美运行,但由于某种原因,它无法在 windows phone (winrt 8.1) 项目上运行,并且它看起来垂直 ScrollBar
中的 scroll
事件由于某种原因没有触发,我有一个解决方法,而不是处理 ScrollViewer
内部的 ViewChanged
事件ListView
,将您页面的隐藏代码更改为以下内容:
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"));
}
}
}
其余保持原样。