【问题标题】:ListView scroll control - scroll to bottom if user isn't scrolling?ListView 滚动控件 - 如果用户不滚动,则滚动到底部?
【发布时间】:2011-11-28 15:25:00
【问题描述】:

我有一个 .NET 3.5 WinForm,它有一个 ListView,其 View 设置为 Details 模式。它用作长后台任务的状态项的可滚动列表。我在底部添加了最新的 ListViewItem(状态条目)。为了确保它被看到,我确保添加后新项目的可见性。这一切都很好;列表视图会自动滚动到底部以显示最近的项目。

private void AddListItem(DateTime timestamp, string message, int index)
{
    var listItem = new ListViewItem(timestamp.ToString());
    listItem.SubItems.Add(message);
    statusList.Items.Insert(index, listItem);
    statusList.Items[statusList.Items.Count - 1].EnsureVisible();
}

问题是如果用户向上滚动查看旧消息,ListView 将向下滚动以使新项目在进入时可见。有没有办法控制这种行为来检查用户是否正在交互使用滚动条(特别是,如果他们按住滚动条上的鼠标按钮)?如果滚动总是在底部,检测也可能是可以接受的。如果它不在底部,那么我将无法确保最新项目的可见性。比如:

private void AddListItem(DateTime timestamp, string message, int index)
{
    var listItem = new ListViewItem(timestamp.ToString());
    listItem.SubItems.Add(message);
    statusList.Items.Insert(index, listItem);
    if (!statusList.IsScrollbarUserControlled)
    {
        statusList.Items[statusList.Items.Count - 1].EnsureVisible();
    }
}

奇怪的是,当用户按住滚动条“手柄”时,手柄并没有移动(这意味着视图实际上并没有以编程方式向下滚动),但实际上是。

更新:是否可以检测滚动条的位置,即是否在底部?

【问题讨论】:

    标签: c# .net winforms scrollbar


    【解决方案1】:

    解决这个问题的两个步骤:

    1. WinForms ListView 没有滚动事件。我们需要定义一个。
    2. 确定 ListView 何时空闲,并仅在空闲一段时间后调用 EnsureVisible。

    对于第一个问题,从 ListView 继承一个新类,覆盖 Windows 消息泵,并在用户滚动时引发事件:

    public class MyListView : ListView
    {
        public event EventHandler<EventArgs> Scrolled;
    
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
    
            const int wm_vscroll = 0x115;
            if (m.Msg == wm_vscroll && Scrolled != null)
            {
                Scrolled(this, new EventArgs());
            }
        }
    }
    

    现在我们知道用户何时滚动列表视图。你的下一个问题是判断列表视图是否空闲;也就是说,如果用户有一段时间没有滚动它。

    有多种方法可以做到这一点。为此,我将使用时间戳来指示上次滚动时间:

    private DateTime lastScrollTime;
    
    ...
    
    listView.Scrolled += delegate { lastScrollTime = DateTime.Now };
    
    ...
    
    private void AddListItem(DateTime timestamp, string message, int index)
    {
        var listItem = new ListViewItem(timestamp.ToString());
        listItem.SubItems.Add(message);
        statusList.Items.Insert(index, listItem);
    
        // Scroll down only if the list view is idle.
        var idleTime = TimeSpan.FromSeconds(5);
        var isListViewIdle = DateTime.Now.Subtract(this.lastScrollTime) > idleTime;
        if (isListViewIdle)
        {
           statusList.Items[statusList.Items.Count - 1].EnsureVisible();
        }
    }
    

    【讨论】:

    • 这对性能有多大影响?这是你过去用过的东西吗?
    • 我很好奇这个 0x115 常量是从哪里来的。
    • @JulienGuertault 它来自 Windows SDK 中的 Windows 消息常量,在 CommCtrl.h 和 WinUser.h 中。您可以在 MSDN 以及 P/Invoke.NET 上找到它们:pinvoke.net/default.aspx/Constants.WM
    • 好的,谢谢你的链接。我认为这是某种神奇的数字。是否有理由不使用宏,从而使代码更易于阅读?
    • 宏是什么意思?这是 C#。我将 WM_SCROLL 定义为常量并使用该常量的名称。我不知道你怎么能让它更清楚。
    【解决方案2】:

    与 SysInternals 的 ProcMon 进行比较。添加一个标记为“自动滚动”的复选框,以便用户可以将其关闭。

    【讨论】:

    • 不知道为什么这是-1'd。虽然这不能回答问题,但您提供了一个非常合理的替代方案
    • @Stealth - 一些马鞋系统性地否定了我的答案。不知道为什么,不用担心。感谢您的投票。
    • 这里的两个答案似乎都是正确的,但我最终还是会接受你的建议,因为我认为它更能满足用户的需求,并且对它的功能不会感到惊讶。我按照您的建议使用了处理器监视器,它非常适合 UX 体验。
    猜你喜欢
    • 2016-11-03
    • 2016-11-22
    • 1970-01-01
    • 1970-01-01
    • 2018-02-27
    • 2021-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多