Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListView scroll control - scroll to bottom if user isn't scrolling?

I have a .NET 3.5 WinForm that has a ListView with the View set in Details mode. It functions as a scrollable list of status items on a long background task. I have the most recent ListViewItem (status entry) added to the bottom. To assure that it is seen, I ensure the visibility of the new item after adding. This all works fine; the list view automatically scrolls to the bottom to show the most recent item.

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();
}

The problem is if the user is scrolling up to look at older messages, the ListView will be scrolled down to make the new item visible as it comes in. Is there a way to control this behavior to check if the user is interacting with the scrollbar (specifically, if they're holding down the mouse button on the scrollbar)? It is probably also acceptable to detected if the scroll is always at the bottom. if it is not at the bottom, then I would not ensure the visibility of the latest item. Something like:

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();
    }
}

What's strange is that when the user is holding down the scrollbar "handle" in place, the handle doesn't move (implying that the view is not actually being scrolled down programatically), but in infact is.

Update: Is it possible to detect the position of the scrollbar, i.e., if i'ts at the bottom or not?

like image 213
Stealth Rabbi Avatar asked Sep 30 '11 16:09

Stealth Rabbi


2 Answers

Two steps to solving this problem:

  1. The WinForms ListView doesn't have a Scrolled event. We'll need to define one.
  2. Determining when the ListView is idle, and calling EnsureVisible only when it's been idle for awhile.

For the first problem, inherit a new class from ListView, override the Windows message pump, and raise an event when the user scrolls it:

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());
        }
    }
}

Now we know when the user scrolls the list view. Your next problem is to determine whether the list view is idle; that is, if the user hasn't scrolled it in awhile.

There are multiple ways to do that. For this purpose, I'm just going to use a time stamp to indicate the last scroll time:

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();
    }
}
like image 99
Judah Gabriel Himango Avatar answered Nov 02 '22 18:11

Judah Gabriel Himango


Compare to, say, SysInternals' ProcMon. Add a checkbox labeled "Auto scroll" so the user can turn it off.

like image 3
Hans Passant Avatar answered Nov 02 '22 18:11

Hans Passant