"Infinite Scrolling" 在 ListView 中 / 避免重入滚动事件

"Infinite Scrolling" in a ListView / Avoiding Re-entrant Scroll Events

我的目标是在 WPF ListView 中模拟 "infinite scrolling"。我用一些不太理想的方法完成了这个任务,我相信有更好的方法来完成它。

by "infinite scrolling" 我的意思是:
假设 ListView 有 20 个项目(已排序 1, 2, 3, 4, ... 17, 18, 19, 20)。当用户向下滚动一个项目时,ListView 顶部的项目被移除并放置在 ListView 的末尾,因此项目的顺序是 2, 3, 4, 5, ... 18, 19, 20, 1。现在,如果用户向下滚动两个项目,顶部的两个项目将被删除并放在末尾,因此项目的顺序是 4, 5, 6, 7, ... 20, 1, 2, 3。现在,类似地,如果用户向上滚动一个项目,ListView 底部的项目将被删除并放在开头,因此项目的顺序是 3, 4, 5, 6, ... 19, 20, 1, 2.

我已经通过分配给 ScrollViewerScrollChanged 事件的以下功能完成了这项任务,我希望成为 "infinite":

// sv - the ScrollViewer to which this event handler is listening
// lv - the ListView associated with "sv"
bool handle_scroll = true;
private void inf_scroll(object sender, ScrollChangedEventArgs e) {
    if (handle_scroll) {
        for (int i = 0; i < e.VerticalChange; i++) {
            object tmp = lv.Items[0];
            lv.Items.RemoveAt(0);
            lv.Items.Add(tmp);
            handle_scroll = false;
        }
        for (int i = 0; i > e.VerticalChange; i--) {
            object tmp = lv.Items[lv.Items.Count - 1];
            lv.Items.RemoveAt(lv.Items.Count - 1);
            lv.Items.Insert(0, tmp);
            handle_scroll = false;
        }
        if(!handle_scroll){
            sv.ScrollToVerticalOffset(sv.VerticalOffset - e.VerticalChange);
        }
    }
    else {
        handle_scroll = true;
    }
}

注意变量 handle_scroll。我把它放在适当的位置,因为对 sv.ScrollToVerticalOffset 的调用将导致整个 inf_scroll 函数在不存在的情况下被递归调用。

知道 ScrollChanged 事件处理程序中滚动 ScrollViewer 是不好的做法,所以这就是我问的原因:是否有更好的方法来做到这一点?如何防止对 inf_scroll 的递归调用?有没有更好的方法模拟"infinite scrolling"?

虽然它没有明确避免在 ScrollChanged 处理程序中滚动,但您可以尝试以下操作:

// sv - the ScrollViewer to which this event handler is listening
// lv - the ListView associated with "sv"
private void inf_scroll(object sender, ScrollChangedEventArgs e) {
    for (int i = 0; i < e.VerticalChange; i++) {
        object tmp = lv.Items[0];
        lv.Items.RemoveAt(0);
        lv.Items.Add(tmp);
    }
    for (int i = 0; i > e.VerticalChange; i--) {
        object tmp = lv.Items[lv.Items.Count - 1];
        lv.Items.RemoveAt(lv.Items.Count - 1);
        lv.Items.Insert(0, tmp);
    }
    lv.ScrollChanged -= inf_scroll;        // remove the handler temporarily
    sv.ScrollToVerticalOffset(sv.VerticalOffset - e.VerticalChange);
    Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() =>{
        sv.ScrollChanged += inf_scroll;    // add the handler back after the scrolling has occurred to avoid recursive scrolling
    }));
}