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?
Two steps to solving this problem:
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();
}
}
Compare to, say, SysInternals' ProcMon. Add a checkbox labeled "Auto scroll" so the user can turn it off.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With